Appearance
| 1 | namespace Syntax.Process is | |
| 2 | use System.Reflection.Metadata.ILOpCode | |
| 3 | use IO.Std | |
| 4 | ||
| 5 | use System.Text.StringBuilder | |
| 6 | ||
| 7 | use Logging | |
| 8 | use Trees | |
| 9 | use Source | |
| 10 | ||
| 11 | use IR | |
| 12 | use IR.Values | |
| 13 | ||
| 14 | use Ghul.Pipes | |
| 15 | ||
| 16 | ||
| 17 | // Loop and exception IL: try, catch, do and for walks, and the fused for loop. | |
| 18 | partial GENERATE_IL is | |
| 19 | pre(`try: Statements.TRY) -> bool is | |
| 20 | super.pre(`try) | |
| 21 | ||
| 22 | return true | |
| 23 | si | |
| 24 | ||
| 25 | get_exception_handler_temps() -> (outer_try: LOOP_LABELS?, return_needed: TEMP, return_value: TEMP?) is | |
| 26 | let return_type = current_function!.return_type | |
| 27 | ||
| 28 | let return_needed: TEMP mut | |
| 29 | let return_value: TEMP? mut = _ | |
| 30 | ||
| 31 | let outer_try = _loops.get_current_try() | |
| 32 | ||
| 33 | if outer_try? then | |
| 34 | // get_current_try returns only try-form labels, whose init sets return_needed | |
| 35 | return_needed = outer_try.return_needed! | |
| 36 | return_value = outer_try.return_value | |
| 37 | else | |
| 38 | return_needed = TEMP(current_block, "need_return", 1, _innate_symbol_lookup.get_bool_type()) | |
| 39 | ||
| 40 | if return_type? /\ !return_type.matches(_innate_symbol_lookup.get_void_type()) then | |
| 41 | return_value = TEMP(current_block, "return", return_type) | |
| 42 | fi | |
| 43 | fi | |
| 44 | ||
| 45 | return (outer_try, return_needed, return_value) | |
| 46 | si | |
| 47 | ||
| 48 | gen_exception_handler_exit(outer_try: LOOP_LABELS?, label: LOOP_LABELS, return_value: TEMP?) is | |
| 49 | let brancher = get_brancher_for_block() | |
| 50 | ||
| 51 | brancher.label(label.start) | |
| 52 | brancher.branch(BRANCH.Z, label.return_needed!.load(), label.end) | |
| 53 | ||
| 54 | if outer_try? then | |
| 55 | if return_value? then | |
| 56 | assert outer_try.return_value? else "outer try has no return value temporary" | |
| 57 | outer_try.return_value.store(return_value.load()) | |
| 58 | fi | |
| 59 | ||
| 60 | if outer_try.is_in_finally then | |
| 61 | outer_try.return_needed!.store(Literal.NUMBER(1, _innate_symbol_lookup.get_bool_type())) | |
| 62 | brancher.branch(outer_try.middle) | |
| 63 | else | |
| 64 | brancher.leave(outer_try.start) | |
| 65 | fi | |
| 66 | elif | |
| 67 | _current_async_state_machine? /\ | |
| 68 | _current_async_success_label? /\ | |
| 69 | current_function == _current_async_state_machine!.function | |
| 70 | then | |
| 71 | // State-machine async: a return inside a try block | |
| 72 | // accumulates the value in `return_value` and the | |
| 73 | // "need return" flag. The post-try emit reaches here | |
| 74 | // — for state-machine async we stash the value to | |
| 75 | // `_result` and `leave success_label` (NOT `ret`), | |
| 76 | // so the trailer's `builder.SetResult` fires. | |
| 77 | let frame = _current_async_state_machine!.frame | |
| 78 | ||
| 79 | assert frame? else "async state machine has no frame at exception handler exit emission" | |
| 80 | ||
| 81 | let result_field = frame.result_field | |
| 82 | ||
| 83 | if return_value? /\ result_field? then | |
| 84 | add(_build_frame_field_store(frame, result_field, return_value.load())) | |
| 85 | fi | |
| 86 | ||
| 87 | add(Values.BRANCH_TO(ILOpCode.LEAVE, _current_async_success_label!)) | |
| 88 | else | |
| 89 | if return_value? then | |
| 90 | add(return_value.load()) | |
| 91 | fi | |
| 92 | ||
| 93 | add(Values.RET()) | |
| 94 | fi | |
| 95 | ||
| 96 | brancher.label(label.end) | |
| 97 | si | |
| 98 | ||
| 99 | visit(`try: Statements.TRY) is | |
| 100 | if | |
| 101 | `try.catches.count == 0 /\ | |
| 102 | !`try.`finally? | |
| 103 | then | |
| 104 | `try.body.walk(self) | |
| 105 | ||
| 106 | super.visit(`try) | |
| 107 | ||
| 108 | return | |
| 109 | fi | |
| 110 | ||
| 111 | let brancher = get_brancher_for_block() | |
| 112 | ||
| 113 | let return_type = current_function!.return_type | |
| 114 | ||
| 115 | let (outer_try, return_needed, return_value) = get_exception_handler_temps() | |
| 116 | ||
| 117 | let label = _loops.enter_try(return_needed, return_value) | |
| 118 | ||
| 119 | let need_double_try = `try.catches.count > 0 /\ `try.`finally? | |
| 120 | ||
| 121 | // The extent each handler is attached to. With both | |
| 122 | // catches and a finally the two `.try` blocks are separate | |
| 123 | // extents, the finally's covering the catches; with only | |
| 124 | // one kind of handler there is a single block and both | |
| 125 | // names stand for it. | |
| 126 | let body_extent = Values.TRY_EXTENT() | |
| 127 | ||
| 128 | let finally_extent = | |
| 129 | if need_double_try then | |
| 130 | Values.TRY_EXTENT() | |
| 131 | else | |
| 132 | body_extent | |
| 133 | fi | |
| 134 | ||
| 135 | let outer_dispatch_holder: ASYNC_DISPATCH_HOLDER? mut = null | |
| 136 | let body_dispatch_holder: ASYNC_DISPATCH_HOLDER? mut = null | |
| 137 | ||
| 138 | if need_double_try then | |
| 139 | add(Values.TRY_START(finally_extent)) | |
| 140 | outer_dispatch_holder = _maybe_push_dispatch_holder() | |
| 141 | fi | |
| 142 | ||
| 143 | add(Values.TRY_START(body_extent)) | |
| 144 | ||
| 145 | body_dispatch_holder = _maybe_push_dispatch_holder() | |
| 146 | ||
| 147 | `try.body.walk(self) | |
| 148 | ||
| 149 | _maybe_pop_dispatch_holder(body_dispatch_holder) | |
| 150 | ||
| 151 | brancher.leave(label.start) | |
| 152 | ||
| 153 | add(Values.TRY_END(body_extent)) | |
| 154 | ||
| 155 | _try_extents.push(body_extent) | |
| 156 | ||
| 157 | for c in `try.catches do | |
| 158 | c.walk(self) | |
| 159 | od | |
| 160 | ||
| 161 | _try_extents.pop() | |
| 162 | ||
| 163 | if need_double_try then | |
| 164 | _maybe_pop_dispatch_holder(outer_dispatch_holder) | |
| 165 | add(Values.TRY_END(finally_extent)) | |
| 166 | fi | |
| 167 | ||
| 168 | let `finally = `try.`finally | |
| 169 | ||
| 170 | if `finally? then | |
| 171 | label.is_in_finally = true | |
| 172 | ||
| 173 | let region = Values.PROTECTED_REGION(finally_extent, null) | |
| 174 | ||
| 175 | add(Values.HANDLER_START(region)) | |
| 176 | ||
| 177 | let skip_finally_label = _open_finally_state_guard() | |
| 178 | ||
| 179 | `finally.walk(self) | |
| 180 | ||
| 181 | _close_finally_state_guard(skip_finally_label) | |
| 182 | ||
| 183 | brancher.label(label.middle) | |
| 184 | ||
| 185 | add(Values.INSTRUCTION(ILOpCode.ENDFINALLY)) | |
| 186 | add(Values.HANDLER_END(region)) | |
| 187 | fi | |
| 188 | ||
| 189 | _loops.leave_loop() | |
| 190 | ||
| 191 | // let outer_try = _loops.get_current_try(); | |
| 192 | gen_exception_handler_exit(outer_try, label, return_value) | |
| 193 | ||
| 194 | super.visit(`try) | |
| 195 | si | |
| 196 | ||
| 197 | pre(`catch: Statements.CATCH) -> bool is | |
| 198 | super.pre(`catch) | |
| 199 | ||
| 200 | return true | |
| 201 | si | |
| 202 | ||
| 203 | visit(`catch: Statements.CATCH) is | |
| 204 | let variable = `catch.variable! | |
| 205 | ||
| 206 | let region = | |
| 207 | Values.PROTECTED_REGION( | |
| 208 | _try_extents.peek(), | |
| 209 | variable.type_expression.type!) | |
| 210 | ||
| 211 | add(Values.HANDLER_START(region)) | |
| 212 | ||
| 213 | let brancher = get_brancher_for_block() | |
| 214 | ||
| 215 | // Catch handler enters with the exception on the stack | |
| 216 | // and immediately stores it into the catch variable. The | |
| 217 | // loop-scoped default-store added for issue #483 must be | |
| 218 | // suppressed here — it would clobber the caught value. | |
| 219 | _in_catch_variable = true | |
| 220 | variable.walk(self) | |
| 221 | _in_catch_variable = false | |
| 222 | ||
| 223 | let symbol = find(variable.name!) | |
| 224 | ||
| 225 | // State-machine-resident catch variable (async function): | |
| 226 | // the exception's on the stack but `stloc <name>` would | |
| 227 | // reference an undeclared CLR local since pre(VARIABLE) | |
| 228 | // skipped the `.locals init` directive. Route through | |
| 229 | // the frame field via a temp instead. | |
| 230 | if let v: Semantic.Symbols.Variable = symbol, sm_field = v.state_machine_field then | |
| 231 | let exception_type = variable.type_expression.type! | |
| 232 | let caught = ".catch_exc" | |
| 233 | ||
| 234 | add(Values.DECLARE_LOCAL(caught, exception_type)) | |
| 235 | add(Values.STORE_TEMP(caught, exception_type)) | |
| 236 | ||
| 237 | add( | |
| 238 | _build_frame_field_store( | |
| 239 | cast Semantic.Symbols.STATE_MACHINE_FRAME_BASE?(sm_field.owner)!, | |
| 240 | sm_field, | |
| 241 | Values.Load.TEMP(caught, exception_type))) | |
| 242 | else | |
| 243 | add( | |
| 244 | Values.STORE_TEMP( | |
| 245 | symbol!.il_name, | |
| 246 | variable.type_expression.type!)) | |
| 247 | fi | |
| 248 | ||
| 249 | `catch.body.walk(self) | |
| 250 | ||
| 251 | brancher.leave(_loops.get_current_try()!.start) | |
| 252 | ||
| 253 | add(Values.HANDLER_END(region)) | |
| 254 | ||
| 255 | super.visit(`catch) | |
| 256 | si | |
| 257 | ||
| 258 | pre(`do: Statements.DO) -> bool is | |
| 259 | super.pre(`do) | |
| 260 | ||
| 261 | return true | |
| 262 | si | |
| 263 | ||
| 264 | visit(`do: Statements.DO) is | |
| 265 | let loop = _loops.enter_loop(`do) | |
| 266 | ||
| 267 | // Loop-as-expression: compile-expressions decided capture- | |
| 268 | // vs-spill (a suspend point in the body routes the result | |
| 269 | // through a frame field). Every exit converges at one | |
| 270 | // label carrying the result — see _gen_loop_result. | |
| 271 | let value_block: Values.BLOCK? mut = null | |
| 272 | ||
| 273 | if `do.want_value /\ `do.value? then | |
| 274 | if isa Values.BLOCK(`do.value) then | |
| 275 | value_block = `do.value | |
| 276 | loop.wants_value = true | |
| 277 | loop.result_type = `do.value!.type | |
| 278 | fi | |
| 279 | fi | |
| 280 | ||
| 281 | let spiller = COMPOSITE_VALUE_SPILLER(self, `do, `do.value, _current_state_machine_frame()) | |
| 282 | ||
| 283 | spiller.enter() | |
| 284 | ||
| 285 | // The brancher binds to the block current NOW — after the | |
| 286 | // capture switch, so the loop's labels and branches land in | |
| 287 | // the same stream as its body. | |
| 288 | let brancher = get_brancher_for_block() | |
| 289 | ||
| 290 | // Breaks emit during the body walk, so every label and | |
| 291 | // slot they reach is allocated before it - mirroring how | |
| 292 | // VAL_BLOCK pre-allocates its cross-try TEMP at block head. | |
| 293 | if let vb = value_block, rt = loop.result_type then | |
| 294 | if spiller.is_spilling then | |
| 295 | loop.spill_field = spiller.spill_field | |
| 296 | else | |
| 297 | loop.cross_try_temp = TEMP(vb, "loop_cross_try", rt) | |
| 298 | loop.cross_try_join = LABEL() | |
| 299 | fi | |
| 300 | ||
| 301 | loop.result_label = LABEL() | |
| 302 | fi | |
| 303 | ||
| 304 | brancher.label(loop.start) | |
| 305 | ||
| 306 | if let `do.binding? then | |
| 307 | // `while let` clauses: emit the same per-clause | |
| 308 | // test / bind sequence as `if let`, but branch | |
| 309 | // failures to `loop.end` (loop exit) instead of an | |
| 310 | // arm-`next` label. Each iteration re-evaluates | |
| 311 | // every clause's scrutinee and re-binds — the | |
| 312 | // bound names are freshly stored on each pass. | |
| 313 | let first = _emit_if_let_clause_test(binding.clauses[0], loop.end, true, brancher) | |
| 314 | ||
| 315 | self.pre(binding) | |
| 316 | ||
| 317 | if first? then | |
| 318 | _emit_if_let_clause_bind(binding.clauses[0], first, loop.end) | |
| 319 | fi | |
| 320 | ||
| 321 | if binding.clauses[0].guard? then | |
| 322 | binding.clauses[0].guard!.walk(self) | |
| 323 | brancher.branch(BRANCH.Z, binding.clauses[0].guard!.value!, loop.end, "while-let-guard") | |
| 324 | fi | |
| 325 | ||
| 326 | let i mut = 1 | |
| 327 | while i < binding.clauses.count do | |
| 328 | let clause = binding.clauses[i] | |
| 329 | ||
| 330 | let temp = _emit_if_let_clause_test(clause, loop.end, false, brancher) | |
| 331 | ||
| 332 | if temp? then | |
| 333 | _emit_if_let_clause_bind(clause, temp, loop.end) | |
| 334 | fi | |
| 335 | ||
| 336 | if clause.guard? then | |
| 337 | clause.guard.walk(self) | |
| 338 | brancher.branch(BRANCH.Z, clause.guard!.value!, loop.end, "while-let-guard") | |
| 339 | fi | |
| 340 | ||
| 341 | i = i + 1 | |
| 342 | od | |
| 343 | elif `do.condition? then | |
| 344 | `do.condition.walk(self) | |
| 345 | ||
| 346 | brancher.branch(BRANCH.Z, `do.condition!.value!, loop.end, "while") | |
| 347 | fi | |
| 348 | ||
| 349 | `do.body.walk(self) | |
| 350 | ||
| 351 | brancher.branch(loop.start) | |
| 352 | ||
| 353 | brancher.label(loop.end) | |
| 354 | ||
| 355 | if let vb = value_block, rt = loop.result_type then | |
| 356 | _gen_loop_result(loop, rt, spiller) | |
| 357 | fi | |
| 358 | ||
| 359 | super.visit(`do) | |
| 360 | ||
| 361 | spiller.leave() | |
| 362 | ||
| 363 | _loops.leave_loop() | |
| 364 | si | |
| 365 | ||
| 366 | pre(`for: Statements.FOR) -> bool is | |
| 367 | super.pre(`for) | |
| 368 | ||
| 369 | return true | |
| 370 | si | |
| 371 | ||
| 372 | visit(`for: Statements.FOR) is | |
| 373 | // A recognised, fusible Pipe[T] chain is lowered to one | |
| 374 | // inline loop over the pinned base, applying the map/filter | |
| 375 | // stages per element, instead of iterating the built pipe | |
| 376 | // objects. Not in a state machine, whose iterator must live | |
| 377 | // on the frame to survive yield/await re-entry - those fall | |
| 378 | // through to the normal loop below. A loop in expression | |
| 379 | // position also takes the normal path: its result-value | |
| 380 | // convergence hangs off this method's shape. | |
| 381 | if | |
| 382 | !`for.want_value /\ | |
| 383 | `for.fusion? /\ | |
| 384 | !Semantic.Symbols.state_machine_for(current_function)? /\ | |
| 385 | !Semantic.Symbols.async_state_machine_for(current_function)? | |
| 386 | then | |
| 387 | _visit_fused_for(`for) | |
| 388 | ||
| 389 | return | |
| 390 | fi | |
| 391 | ||
| 392 | let loop = _loops.enter_loop(`for) | |
| 393 | ||
| 394 | let value_block: Values.BLOCK? mut = null | |
| 395 | ||
| 396 | if `for.want_value /\ `for.value? then | |
| 397 | if isa Values.BLOCK(`for.value) then | |
| 398 | value_block = `for.value | |
| 399 | loop.wants_value = true | |
| 400 | loop.result_type = `for.value!.type | |
| 401 | fi | |
| 402 | fi | |
| 403 | ||
| 404 | let spiller = COMPOSITE_VALUE_SPILLER(self, `for, `for.value, _current_state_machine_frame()) | |
| 405 | ||
| 406 | spiller.enter() | |
| 407 | ||
| 408 | // The brancher binds to the block current NOW — after the | |
| 409 | // capture switch, so the loop's labels and branches land in | |
| 410 | // the same stream as its body. | |
| 411 | let brancher = get_brancher_for_block() | |
| 412 | ||
| 413 | // Breaks emit during the body walk, so every label and | |
| 414 | // slot they reach is allocated before it. | |
| 415 | if let vb = value_block, rt = loop.result_type then | |
| 416 | if spiller.is_spilling then | |
| 417 | loop.spill_field = spiller.spill_field | |
| 418 | else | |
| 419 | loop.cross_try_temp = TEMP(vb, "loop_cross_try", rt) | |
| 420 | loop.cross_try_join = LABEL() | |
| 421 | fi | |
| 422 | ||
| 423 | loop.result_label = LABEL() | |
| 424 | fi | |
| 425 | ||
| 426 | let expression = `for.expression! | |
| 427 | let variable = `for.variable! | |
| 428 | let body = `for.body! | |
| 429 | ||
| 430 | let iterator: Value mut | |
| 431 | ||
| 432 | if `for.read_iterator? then | |
| 433 | iterator = `for.read_iterator.call(expression.location, expression.value!, Collections.LIST[Value](0), null, _function_caller) | |
| 434 | else | |
| 435 | iterator = expression.value! | |
| 436 | fi | |
| 437 | ||
| 438 | variable.walk(self) | |
| 439 | ||
| 440 | // Generator / async FOR: the iterator value has to survive | |
| 441 | // every yield / await in the body, so it is read and written | |
| 442 | // through the frame field compile-expressions allocated for | |
| 443 | // it. The regular CLR-local TEMP path would reset on every | |
| 444 | // MoveNext re-entry. | |
| 445 | let iterator_field = `for.iterator_field | |
| 446 | let iterator_frame = _current_state_machine_frame() | |
| 447 | ||
| 448 | if iterator_field? /\ iterator_frame? then | |
| 449 | add(_build_frame_field_store(iterator_frame, iterator_field, iterator)) | |
| 450 | fi | |
| 451 | ||
| 452 | let temp: TEMP? mut = null | |
| 453 | ||
| 454 | if !iterator_field? then | |
| 455 | temp = TEMP(current_block, "iterator", iterator) | |
| 456 | fi | |
| 457 | ||
| 458 | brancher.label(loop.start) | |
| 459 | ||
| 460 | let load_iter_a = | |
| 461 | if iterator_field? then | |
| 462 | cast Value(IR.Values.Load.INSTANCE_FIELD( | |
| 463 | IR.Values.Load.REFERENCE_SELF(iterator_frame!, iterator_frame.type), | |
| 464 | iterator_field | |
| 465 | )) | |
| 466 | else | |
| 467 | cast Value(temp!.load()) | |
| 468 | fi | |
| 469 | ||
| 470 | let has_next = `for.move_next!.call(expression.location, load_iter_a, Collections.LIST[Value](0), null, _function_caller) | |
| 471 | ||
| 472 | brancher.branch(BRANCH.Z, has_next, loop.end) | |
| 473 | ||
| 474 | let load_iter_b = | |
| 475 | if iterator_field? then | |
| 476 | cast Value(IR.Values.Load.INSTANCE_FIELD( | |
| 477 | IR.Values.Load.REFERENCE_SELF(iterator_frame!, iterator_frame.type), | |
| 478 | iterator_field | |
| 479 | )) | |
| 480 | else | |
| 481 | cast Value(temp!.load()) | |
| 482 | fi | |
| 483 | ||
| 484 | let current = `for.read_current!.call(expression.location, load_iter_b, Collections.LIST[Value](0), null, _function_caller) | |
| 485 | ||
| 486 | gen_destructuring_initialize(variable.left, current) | |
| 487 | ||
| 488 | // not to generate code for the expression itself, but to capture IL for any anonymous function bodies: | |
| 489 | // TODO check that if expressions within the expression are emitted correctly | |
| 490 | expression.walk(self) | |
| 491 | ||
| 492 | body.walk(self) | |
| 493 | ||
| 494 | brancher.branch(loop.start) | |
| 495 | ||
| 496 | brancher.label(loop.end) | |
| 497 | ||
| 498 | if let vb = value_block, rt = loop.result_type then | |
| 499 | _gen_loop_result(loop, rt, spiller) | |
| 500 | fi | |
| 501 | ||
| 502 | super.visit(`for) | |
| 503 | ||
| 504 | spiller.leave() | |
| 505 | ||
| 506 | _loops.leave_loop() | |
| 507 | si | |
| 508 | ||
| 509 | // Declare the per-stage locals a fused chain needs before its loop, | |
| 510 | // filling `stage_delegates` / `stage_counters` parallel to the | |
| 511 | // stages (null where a slot does not apply): an inlined stage's | |
| 512 | // parameter local, a delegate stage's hoisted function value, an | |
| 513 | // index stage's running counter initialised to its start. | |
| 514 | _setup_fused_stages( | |
| 515 | fusion: Syntax.Process.PIPE_FUSION, | |
| 516 | stage_delegates: Collections.LIST[TEMP?], | |
| 517 | stage_counters: Collections.LIST[TEMP?] | |
| 518 | ) is | |
| 519 | for stage in fusion.stages_outermost_first do | |
| 520 | if stage.is_countdown then | |
| 521 | // take/skip: evaluate the count once into a running | |
| 522 | // counter the loop decrements per pulled element. | |
| 523 | let counter = TEMP(current_block, "fused_countdown", _innate_symbol_lookup.get_int_type()) | |
| 524 | counter.store(stage.argument!.value!) | |
| 525 | stage_delegates.add(null) | |
| 526 | stage_counters.add(counter) | |
| 527 | elif stage.is_inlined then | |
| 528 | add( | |
| 529 | Values.DECLARE_LOCAL( | |
| 530 | stage.param_local!.il_name, stage.param_local!.storage_type!)) | |
| 531 | stage_delegates.add(null) | |
| 532 | stage_counters.add(null) | |
| 533 | elif stage.is_index then | |
| 534 | // The counter starts at the caller's start where one | |
| 535 | // was given, and at zero otherwise. | |
| 536 | let int_type = _innate_symbol_lookup.get_int_type() | |
| 537 | let counter = TEMP(current_block, "fused_index", int_type) | |
| 538 | ||
| 539 | if let start = stage.argument then | |
| 540 | counter.store(start.value!) | |
| 541 | else | |
| 542 | counter.store(Literal.NUMBER(0, int_type)) | |
| 543 | fi | |
| 544 | ||
| 545 | stage_delegates.add(null) | |
| 546 | stage_counters.add(counter) | |
| 547 | else | |
| 548 | stage_delegates.add(TEMP(current_block, "fused_stage", stage.argument!.value!)) | |
| 549 | stage_counters.add(null) | |
| 550 | fi | |
| 551 | od | |
| 552 | si | |
| 553 | ||
| 554 | // Before the loop pulls an element, leave via `take_exit` when any | |
| 555 | // take stage has spent its count: a fused take then pulls exactly | |
| 556 | // the elements it yields, as the TAKE_PIPE it replaces does, and | |
| 557 | // never advances the source past them. | |
| 558 | _gen_fused_take_guards( | |
| 559 | fusion: Syntax.Process.PIPE_FUSION, | |
| 560 | stage_counters: Collections.List[TEMP?], | |
| 561 | brancher: BLOCK_BRANCHER, | |
| 562 | take_exit: LABEL | |
| 563 | ) is | |
| 564 | let int_type = _innate_symbol_lookup.get_int_type() | |
| 565 | ||
| 566 | for index in 0..fusion.stages_outermost_first.count do | |
| 567 | if fusion.stages_outermost_first[index].is_take then | |
| 568 | brancher.branch(BRANCH.LE, stage_counters[index]!.load(), cast Value(Literal.NUMBER(0, int_type)), take_exit) | |
| 569 | fi | |
| 570 | od | |
| 571 | si | |
| 572 | ||
| 573 | // Apply a fused chain's stages to `element` innermost-first, | |
| 574 | // returning the fully-transformed value. A map result and an index | |
| 575 | // result each land in a fresh temp so a downstream stage can reload | |
| 576 | // them; a filter reject branches to `loop_start` (the next pull); a | |
| 577 | // take spends one of its count, checked before the next pull, which | |
| 578 | // leaves via `take_exit` - `loop_end` unless the caller has a source | |
| 579 | // to reset first; an index stage builds | |
| 580 | // INDEXED_VALUE(counter++, current) directly. | |
| 581 | _apply_fused_stages( | |
| 582 | fusion: Syntax.Process.PIPE_FUSION, | |
| 583 | element: Value, | |
| 584 | stage_delegates: Collections.List[TEMP?], | |
| 585 | stage_counters: Collections.List[TEMP?], | |
| 586 | brancher: BLOCK_BRANCHER, | |
| 587 | loop_start: LABEL, | |
| 588 | loop_end: LABEL, | |
| 589 | take_exit: LABEL | |
| 590 | ) -> Value is | |
| 591 | let int_type = _innate_symbol_lookup.get_int_type() | |
| 592 | ||
| 593 | let current_value: Value mut = element | |
| 594 | ||
| 595 | let index mut = fusion.stages_outermost_first.count - 1 | |
| 596 | ||
| 597 | while index >= 0 do | |
| 598 | let stage = fusion.stages_outermost_first[index] | |
| 599 | ||
| 600 | if stage.is_take then | |
| 601 | // This element spends one of the take's count; the loop | |
| 602 | // checks the count before pulling the next element (see | |
| 603 | // _gen_fused_take_guards). The element passes through | |
| 604 | // unchanged. | |
| 605 | add(IR.Values.DECREMENT(stage_counters[index]!.il_name, int_type)) | |
| 606 | elif stage.is_skip then | |
| 607 | // Decrement the counter; while it stays non-negative this | |
| 608 | // is a leading element to drop, so pull the next one. | |
| 609 | brancher.branch(BRANCH.GE, cast Value(IR.Values.PRE_DECREMENT(stage_counters[index]!.il_name, int_type)), cast Value(Literal.NUMBER(0, int_type)), loop_start) | |
| 610 | else | |
| 611 | let stage_result: Value mut | |
| 612 | ||
| 613 | if stage.is_inlined then | |
| 614 | // Assign the incoming element to the parameter local; | |
| 615 | // the pre-harvested body IR reads it (and any captured | |
| 616 | // outer locals) as ordinary locals - no delegate, no | |
| 617 | // frame. | |
| 618 | add(IR.Values.Store.LOCAL_VARIABLE(stage.param_local!, current_value)) | |
| 619 | ||
| 620 | stage_result = stage.inline_body! | |
| 621 | elif stage.is_index then | |
| 622 | // INDEXED_VALUE(counter, current) with the counter | |
| 623 | // post-incremented in place - no INDEX_PIPE object. | |
| 624 | let arguments = Collections.LIST[Value]() | |
| 625 | arguments.add(cast Value(IR.Values.POST_INCREMENT(stage_counters[index]!.il_name, int_type))) | |
| 626 | arguments.add(current_value) | |
| 627 | ||
| 628 | stage_result = IR.Values.NEW(stage.indexed_value_type!, stage.indexed_value_constructor!, arguments) | |
| 629 | else | |
| 630 | let delegate = stage_delegates[index]!.load() | |
| 631 | let func_type = stage.argument!.value!.type! | |
| 632 | ||
| 633 | let result_type = | |
| 634 | if func_type.is_action then | |
| 635 | _innate_symbol_lookup.get_void_type() | |
| 636 | else | |
| 637 | func_type.arguments[func_type.arguments.count - 1] | |
| 638 | fi | |
| 639 | ||
| 640 | let call_arguments = Collections.LIST[Value]() | |
| 641 | call_arguments.add(current_value) | |
| 642 | ||
| 643 | stage_result = IR.Values.Call.CLOSURE(delegate, result_type, func_type.is_action, func_type, call_arguments) | |
| 644 | fi | |
| 645 | ||
| 646 | if stage.is_filter then | |
| 647 | // Rejected elements skip straight to the next pull. | |
| 648 | brancher.branch(BRANCH.Z, stage_result, loop_start) | |
| 649 | else | |
| 650 | current_value = TEMP(current_block, "fused_mapped", stage_result).load() | |
| 651 | fi | |
| 652 | fi | |
| 653 | ||
| 654 | index = index - 1 | |
| 655 | od | |
| 656 | ||
| 657 | return current_value | |
| 658 | si | |
| 659 | ||
| 660 | // Lower a recognised fusible Pipe[T] chain to a single loop: | |
| 661 | // drive the pinned base's own iterator, apply each map/filter/index | |
| 662 | // stage inline per element (map/index assign a temp, filter skips to | |
| 663 | // the next element), then bind the loop variable and run the | |
| 664 | // body. The built MAP_PIPE / FilterPipe / INDEX_PIPE objects and | |
| 665 | // their per-element virtual dispatch are never emitted. | |
| 666 | // Convergence for a loop used as an expression (see LOOP_ | |
| 667 | // LABELS): the natural exit yields absence; valued breaks | |
| 668 | // arrive having pushed or stored their value; a cross-try | |
| 669 | // join reloads the stashed temp. One result-typed value is | |
| 670 | // available to the consumer afterwards. The result label was | |
| 671 | // allocated at loop entry — breaks target it from inside the | |
| 672 | // body walk. | |
| 673 | _gen_loop_result(loop: LOOP_LABELS, result_type: Semantic.Types.Type, spiller: COMPOSITE_VALUE_SPILLER) is | |
| 674 | let brancher = get_brancher_for_block() | |
| 675 | let result = loop.result_label! | |
| 676 | ||
| 677 | spiller.emit_value(IR.Values.DEFAULT(result_type)) | |
| 678 | brancher.branch(result) | |
| 679 | ||
| 680 | if loop.cross_try_used then | |
| 681 | brancher.label(loop.cross_try_join!) | |
| 682 | add(loop.cross_try_temp!.load()) | |
| 683 | fi | |
| 684 | ||
| 685 | brancher.label(result) | |
| 686 | si | |
| 687 | ||
| 688 | _visit_fused_for(`for: Statements.FOR) is | |
| 689 | let fusion = `for.fusion! | |
| 690 | let source = fusion.source | |
| 691 | let variable = `for.variable! | |
| 692 | let body = `for.body! | |
| 693 | ||
| 694 | let brancher = get_brancher_for_block() | |
| 695 | let loop = _loops.enter_loop(`for) | |
| 696 | ||
| 697 | // Iterate the source directly - the wrapping pipe objects are | |
| 698 | // never built. read_iterator is null when the source is its | |
| 699 | // own iterator (e.g. a range). | |
| 700 | let iterator: Value mut | |
| 701 | ||
| 702 | if fusion.source_read_iterator? then | |
| 703 | iterator = fusion.source_read_iterator.call(source.location, source.value!, Collections.LIST[Value](0), null, _function_caller) | |
| 704 | else | |
| 705 | iterator = source.value! | |
| 706 | fi | |
| 707 | ||
| 708 | variable.walk(self) | |
| 709 | ||
| 710 | let temp = TEMP(current_block, "fused_iterator", iterator) | |
| 711 | ||
| 712 | // Per-stage setup: an inlined stage needs a slot for its | |
| 713 | // parameter local; a delegate stage hoists its function value | |
| 714 | // into a local so the delegate is built once, not per element; | |
| 715 | // an index stage needs a running counter, initialised to its | |
| 716 | // start. | |
| 717 | let stage_delegates = Collections.LIST[TEMP?]() | |
| 718 | let stage_counters = Collections.LIST[TEMP?]() | |
| 719 | ||
| 720 | _setup_fused_stages(fusion, stage_delegates, stage_counters) | |
| 721 | ||
| 722 | // A spent take leaves the loop with the cursor part way | |
| 723 | // through, so it lands on its own label to rewind it before | |
| 724 | // falling into the loop exit. Nothing else needs it: an | |
| 725 | // exhausted source has already rewound itself, and a `break` | |
| 726 | // in the body abandons the chain exactly as the unfused | |
| 727 | // lowering does. | |
| 728 | let pipe_reset = fusion.pipe_reset | |
| 729 | let take_exit = if pipe_reset? then LABEL() else loop.end fi | |
| 730 | ||
| 731 | brancher.label(loop.start) | |
| 732 | ||
| 733 | _gen_fused_take_guards(fusion, stage_counters, brancher, take_exit) | |
| 734 | ||
| 735 | let has_next = fusion.source_move_next!.call(source.location, temp.load(), Collections.LIST[Value](0), null, _function_caller) | |
| 736 | ||
| 737 | brancher.branch(BRANCH.Z, has_next, loop.end) | |
| 738 | ||
| 739 | let element = fusion.source_read_current!.call(source.location, temp.load(), Collections.LIST[Value](0), null, _function_caller) | |
| 740 | ||
| 741 | let current_value = _apply_fused_stages(fusion, element, stage_delegates, stage_counters, brancher, loop.start, loop.end, take_exit) | |
| 742 | ||
| 743 | gen_destructuring_initialize(variable.left, current_value) | |
| 744 | ||
| 745 | // Emit any anonymous-function bodies in the chain (the | |
| 746 | // map/filter lambdas, plus anything inside the source). | |
| 747 | // Walking these nodes emits the nested closure method bodies | |
| 748 | // without emitting the pipe-construction IL itself. | |
| 749 | source.walk(self) | |
| 750 | ||
| 751 | for stage in fusion.stages_outermost_first do | |
| 752 | if stage.argument? then | |
| 753 | stage.argument.walk(self) | |
| 754 | fi | |
| 755 | od | |
| 756 | ||
| 757 | body.walk(self) | |
| 758 | ||
| 759 | brancher.branch(loop.start) | |
| 760 | ||
| 761 | // Rewinding asks the cursor at run time whether it is a | |
| 762 | // `Pipe[E]`, exactly as a stage pipe's own rewind does: one is | |
| 763 | // reset in place so every holder sees it start over, and | |
| 764 | // anything else is left where it stopped. | |
| 765 | if let reset = pipe_reset, pipe_type = fusion.pipe_type then | |
| 766 | brancher.label(take_exit) | |
| 767 | ||
| 768 | let as_pipe = TEMP(current_block, "fused_pipe", cast Value(CAST(pipe_type, temp.load(), false))) | |
| 769 | ||
| 770 | brancher.branch(BRANCH.Z, as_pipe.load(), loop.end) | |
| 771 | ||
| 772 | add(reset.call(source.location, as_pipe.load(), Collections.LIST[Value](0), null, _function_caller)) | |
| 773 | fi | |
| 774 | ||
| 775 | brancher.label(loop.end) | |
| 776 | ||
| 777 | super.visit(`for) | |
| 778 | ||
| 779 | _loops.leave_loop() | |
| 780 | si | |
| 781 | ||
| 782 | si | |
| 783 | si |