Appearance
| 1 | namespace IR.Values is | |
| 2 | use Semantic.Types.Type | |
| 3 | ||
| 4 | // One map/filter stage of a fused-consumer loop. The stage's lambda | |
| 5 | // delegate is hoisted into a local (built once) and `apply` invokes it | |
| 6 | // on the current element, yielding either a bool (a `filter` stage - | |
| 7 | // branch to the next element when false) or the transformed element (a | |
| 8 | // `map` stage - stored into `output_il_name` for the next stage). The | |
| 9 | // delegate is called rather than inlined so a lambda that captures an | |
| 10 | // enclosing local (or nests another closure) keeps its normal capture | |
| 11 | // context. | |
| 12 | // | |
| 13 | // A stage is one shape or the other, so each constructor fills its | |
| 14 | // own fields and leaves the other shape's alone; `is_countdown` | |
| 15 | // says which are live. | |
| 16 | @suppress("field-definite-assignment") | |
| 17 | class FUSED_CONSUMER_STAGE is | |
| 18 | is_filter: bool public | |
| 19 | delegate_il_name: string public | |
| 20 | delegate_type: Type public | |
| 21 | delegate_init: Value public | |
| 22 | apply: Value public | |
| 23 | output_il_name: string public | |
| 24 | output_type: Type? public | |
| 25 | ||
| 26 | // A `take`/`skip` stage carries no delegate: the count is evaluated | |
| 27 | // once into `counter_il_name` and each element decrements it (the | |
| 28 | // loop stops pulling once a take's runs out, skip drops the element | |
| 29 | // while its remains). `is_countdown` marks either. | |
| 30 | is_take: bool public | |
| 31 | is_skip: bool public | |
| 32 | counter_il_name: string public | |
| 33 | count_init: Value? public | |
| 34 | ||
| 35 | // An index stage carries a running counter like a countdown does, | |
| 36 | // but produces an output local as a map stage does: `apply` builds | |
| 37 | // the INDEXED_VALUE from the counter and the incoming element. | |
| 38 | is_index: bool public | |
| 39 | ||
| 40 | is_countdown: bool => is_take \/ is_skip | |
| 41 | ||
| 42 | init(is_filter: bool, delegate_il_name: string, delegate_type: Type, delegate_init: Value, apply: Value, output_il_name: string, output_type: Type?) is | |
| 43 | self.is_filter = is_filter | |
| 44 | self.delegate_il_name = delegate_il_name | |
| 45 | self.delegate_type = delegate_type | |
| 46 | self.delegate_init = delegate_init | |
| 47 | self.apply = apply | |
| 48 | self.output_il_name = output_il_name | |
| 49 | self.output_type = output_type | |
| 50 | si | |
| 51 | ||
| 52 | init(is_take: bool, is_skip: bool, counter_il_name: string, count_init: Value) is | |
| 53 | self.is_take = is_take | |
| 54 | self.is_skip = is_skip | |
| 55 | self.counter_il_name = counter_il_name | |
| 56 | self.count_init = count_init | |
| 57 | si | |
| 58 | ||
| 59 | init(counter_il_name: string, start_init: Value, output_il_name: string, output_type: Type, apply: Value) is | |
| 60 | self.is_index = true | |
| 61 | self.counter_il_name = counter_il_name | |
| 62 | self.count_init = start_init | |
| 63 | self.output_il_name = output_il_name | |
| 64 | self.output_type = output_type | |
| 65 | self.apply = apply | |
| 66 | si | |
| 67 | si | |
| 68 | ||
| 69 | // A terminal Pipe consumer (`count`, `any`, `all`, `each`, `reduce`, | |
| 70 | // `find`, `first`, `collect_list`) fused with its chain: drives the | |
| 71 | // chain's source iterator directly, applies the map/filter stages per | |
| 72 | // element by calling each stage's hoisted delegate, runs the consumer's | |
| 73 | // per-element body, and leaves the consumer's result on the stack - | |
| 74 | // emitted in place of the consumer method call, so it flows through any | |
| 75 | // expression context (like NULL_COALESCE_VALUE). | |
| 76 | class FUSED_CONSUMER: Value is | |
| 77 | _result_type: Type | |
| 78 | ||
| 79 | ||
| 80 | // Loop skeleton, pre-built against the '.fc_iter.N' iterator local. | |
| 81 | // `iterator_init` produces the source's own iterator (or is the | |
| 82 | // source itself when it is already an iterator); move_next / | |
| 83 | // read_current drive it. | |
| 84 | has_iterator: bool public | |
| 85 | iterator_init: Value public | |
| 86 | iterator_il_name: string public | |
| 87 | iterator_type: Type public | |
| 88 | move_next: Value public | |
| 89 | read_current: Value public | |
| 90 | ||
| 91 | // The rewind a spent take owes the cursor. `pipe_cast` tests it | |
| 92 | // for `Pipe[E]` into `pipe_il_name`, and `pipe_reset` resets that | |
| 93 | // when it is one - exactly what a stage pipe rewinding its own | |
| 94 | // cursor does. Unset when the cursor is a value type, which the | |
| 95 | // loop holds a copy of and owes nothing. | |
| 96 | pipe_cast: Value? public | |
| 97 | pipe_reset: Value? public | |
| 98 | pipe_il_name: string public | |
| 99 | pipe_type: Type? public | |
| 100 | ||
| 101 | // The element local the innermost stage reads. | |
| 102 | element_il_name: string public | |
| 103 | element_type: Type public | |
| 104 | ||
| 105 | stages: Collections.List[FUSED_CONSUMER_STAGE] public | |
| 106 | ||
| 107 | // The surviving element after every stage - what the consumer body | |
| 108 | // operates on (`collect` adds it, `find` returns it, ...). | |
| 109 | final_element_il_name: string public | |
| 110 | final_element_type: Type public | |
| 111 | ||
| 112 | // Consumer specifics. | |
| 113 | consumer_kind: string public | |
| 114 | ||
| 115 | // The result local, named in compile-expressions so pre-built | |
| 116 | // values (a `reduce` accumulator) can reference it. | |
| 117 | result_il_name: string public | |
| 118 | ||
| 119 | // A consumer with a function argument (`any`/`all`/`find`/`each` | |
| 120 | // predicate/action, `reduce` accumulator) hoists it into a local so | |
| 121 | // the delegate is built once. `consumer_apply` is that argument | |
| 122 | // applied per element (to the final element, and for `reduce` the | |
| 123 | // running result too), pre-built against the hoisted local. | |
| 124 | consumer_arg_il_name: string? public | |
| 125 | consumer_arg_type: Type? public | |
| 126 | consumer_arg_init: Value? public | |
| 127 | consumer_apply: Value? public | |
| 128 | ||
| 129 | // `reduce` seeds the result with its first argument; `find`/`first` | |
| 130 | // seed the result with an empty MAYBE; `collect` seeds it with a | |
| 131 | // fresh list. | |
| 132 | seed_init: Value? public | |
| 133 | ||
| 134 | // `find`/`first` wrap the surviving element in a MAYBE; `collect` | |
| 135 | // adds it to the result list. Pre-built against the element / result | |
| 136 | // locals. | |
| 137 | wrap_element: Value? public | |
| 138 | add_element: Value? public | |
| 139 | ||
| 140 | type: Type => _result_type | |
| 141 | is_lightweight_pure: bool => false | |
| 142 | ||
| 143 | init( | |
| 144 | result_type: Type, | |
| 145 | iterator_init: Value, | |
| 146 | has_iterator: bool, | |
| 147 | iterator_il_name: string, | |
| 148 | iterator_type: Type, | |
| 149 | move_next: Value, | |
| 150 | read_current: Value, | |
| 151 | element_il_name: string, | |
| 152 | element_type: Type, | |
| 153 | stages: Collections.List[FUSED_CONSUMER_STAGE], | |
| 154 | final_element_il_name: string, | |
| 155 | final_element_type: Type, | |
| 156 | consumer_kind: string, | |
| 157 | result_il_name: string | |
| 158 | ) is | |
| 159 | super.init() | |
| 160 | ||
| 161 | // Left empty unless a rewind is wired up; nothing else reads it. | |
| 162 | pipe_il_name = "" | |
| 163 | ||
| 164 | self._result_type = result_type | |
| 165 | self.iterator_init = iterator_init | |
| 166 | self.has_iterator = has_iterator | |
| 167 | self.iterator_il_name = iterator_il_name | |
| 168 | self.iterator_type = iterator_type | |
| 169 | self.move_next = move_next | |
| 170 | self.read_current = read_current | |
| 171 | self.element_il_name = element_il_name | |
| 172 | self.element_type = element_type | |
| 173 | self.stages = stages | |
| 174 | self.final_element_il_name = final_element_il_name | |
| 175 | self.final_element_type = final_element_type | |
| 176 | self.consumer_kind = consumer_kind | |
| 177 | self.result_il_name = result_il_name | |
| 178 | si | |
| 179 | ||
| 180 | gen(context: IR.CONTEXT) => | |
| 181 | _gen_fused_loop(context) | |
| 182 | ||
| 183 | is_void: bool => Syntax.Process.PIPE_CONSUMER_KIND.is_void(consumer_kind) | |
| 184 | ||
| 185 | // The loop below is one body of control flow with two ways of | |
| 186 | // being written down, so the choice of back end is made per | |
| 187 | // instruction here rather than by keeping a second copy of the | |
| 188 | // loop. The logic is where the complexity is, and a duplicate of | |
| 189 | // it would be the thing that drifts. | |
| 190 | // | |
| 191 | // The loop names its locals and labels rather than numbering | |
| 192 | // them, because SRM_METHOD_BODY_EMITTER maps a name to a slot | |
| 193 | // and to a label handle, forward references included. | |
| 194 | _declare(context: IR.CONTEXT, name: string, type: Semantic.Types.Type) is | |
| 195 | let body = context.current_srm_body_emitter! | |
| 196 | body.declare_local(name, type) | |
| 197 | si | |
| 198 | ||
| 199 | _stloc(context: IR.CONTEXT, name: string) is | |
| 200 | let body = context.current_srm_body_emitter! | |
| 201 | body.stloc(name) | |
| 202 | si | |
| 203 | ||
| 204 | _ldloc(context: IR.CONTEXT, name: string) is | |
| 205 | let body = context.current_srm_body_emitter! | |
| 206 | body.ldloc(name) | |
| 207 | si | |
| 208 | ||
| 209 | _mark(context: IR.CONTEXT, label: IR.LABEL) is | |
| 210 | let body = context.current_srm_body_emitter! | |
| 211 | body.mark_label(label) | |
| 212 | si | |
| 213 | ||
| 214 | _branch( | |
| 215 | context: IR.CONTEXT, | |
| 216 | mnemonic: string, | |
| 217 | op_code: System.Reflection.Metadata.ILOpCode, | |
| 218 | label: IR.LABEL | |
| 219 | ) is | |
| 220 | let body = context.current_srm_body_emitter! | |
| 221 | body.branch(op_code, label) | |
| 222 | si | |
| 223 | ||
| 224 | _ldc_i4(context: IR.CONTEXT, value: int) is | |
| 225 | let body = context.current_srm_body_emitter! | |
| 226 | body.ldc_i4(value) | |
| 227 | si | |
| 228 | ||
| 229 | _op( | |
| 230 | context: IR.CONTEXT, | |
| 231 | mnemonic: string, | |
| 232 | op_code: System.Reflection.Metadata.ILOpCode | |
| 233 | ) is | |
| 234 | let body = context.current_srm_body_emitter! | |
| 235 | body.op(op_code) | |
| 236 | si | |
| 237 | ||
| 238 | // Emit the fused loop, leaving the consumer's result on the stack | |
| 239 | // (nothing for a void `each`). | |
| 240 | _gen_fused_loop(context: IR.CONTEXT) is | |
| 241 | let loop_start = IR.LABEL() | |
| 242 | let loop_end = IR.LABEL() | |
| 243 | let take_exit = if pipe_reset? then IR.LABEL() else loop_end fi | |
| 244 | ||
| 245 | if !is_void then | |
| 246 | _declare(context, result_il_name, _result_type) | |
| 247 | _gen_result_init(context) | |
| 248 | fi | |
| 249 | ||
| 250 | // A consumer's function argument is built once into a local. | |
| 251 | if let arg_name = consumer_arg_il_name, arg_type = consumer_arg_type, arg_init = consumer_arg_init then | |
| 252 | _declare(context, arg_name, arg_type) | |
| 253 | gen(arg_init, context) | |
| 254 | _stloc(context, arg_name) | |
| 255 | fi | |
| 256 | ||
| 257 | // Each stage's lambda delegate is built once before the loop; a | |
| 258 | // take/skip stage instead seeds its running counter with the | |
| 259 | // count evaluated once. | |
| 260 | for stage in stages do | |
| 261 | if stage.is_countdown \/ stage.is_index then | |
| 262 | _declare( | |
| 263 | context, | |
| 264 | stage.counter_il_name, | |
| 265 | IoC.CONTAINER.instance.innate_symbol_lookup.get_int_type()) | |
| 266 | ||
| 267 | gen(stage.count_init!, context) | |
| 268 | _stloc(context, stage.counter_il_name) | |
| 269 | else | |
| 270 | _declare(context, stage.delegate_il_name, stage.delegate_type) | |
| 271 | gen(stage.delegate_init, context) | |
| 272 | _stloc(context, stage.delegate_il_name) | |
| 273 | fi | |
| 274 | od | |
| 275 | ||
| 276 | // The iterator always lives in a local - whether it came from | |
| 277 | // the source's iterator member or the source is itself an | |
| 278 | // iterator (a value-type range), move_next / current drive it | |
| 279 | // through the local (by address for a value-type iterator). | |
| 280 | _declare(context, iterator_il_name, iterator_type) | |
| 281 | gen(iterator_init, context) | |
| 282 | _stloc(context, iterator_il_name) | |
| 283 | ||
| 284 | _mark(context, loop_start) | |
| 285 | ||
| 286 | // Leave before pulling once any take stage has spent its count, | |
| 287 | // so the loop pulls exactly the elements it yields and never | |
| 288 | // advances the source past them. | |
| 289 | for stage in stages do | |
| 290 | if stage.is_take then | |
| 291 | _ldloc(context, stage.counter_il_name) | |
| 292 | _ldc_i4(context, 0) | |
| 293 | _branch(context, "ble", System.Reflection.Metadata.ILOpCode.BLE, take_exit) | |
| 294 | fi | |
| 295 | od | |
| 296 | ||
| 297 | gen(move_next, context) | |
| 298 | _branch(context, "brfalse", System.Reflection.Metadata.ILOpCode.BRFALSE, loop_end) | |
| 299 | ||
| 300 | _declare(context, element_il_name, element_type) | |
| 301 | gen(read_current, context) | |
| 302 | _stloc(context, element_il_name) | |
| 303 | ||
| 304 | for stage in stages do | |
| 305 | if stage.is_take then | |
| 306 | // This element spends one of the take's count; the check | |
| 307 | // at the top of the loop decides when to stop. | |
| 308 | _ldloc(context, stage.counter_il_name) | |
| 309 | _ldc_i4(context, 1) | |
| 310 | _op(context, "sub", System.Reflection.Metadata.ILOpCode.SUB) | |
| 311 | _stloc(context, stage.counter_il_name) | |
| 312 | elif stage.is_skip then | |
| 313 | // Decrement; while the counter stays non-negative this is | |
| 314 | // a leading element to drop, so pull the next one. | |
| 315 | _gen_countdown(context, stage) | |
| 316 | _branch(context, "bge", System.Reflection.Metadata.ILOpCode.BGE, loop_start) | |
| 317 | elif stage.is_filter then | |
| 318 | gen(stage.apply, context) | |
| 319 | _branch(context, "brfalse", System.Reflection.Metadata.ILOpCode.BRFALSE, loop_start) | |
| 320 | else | |
| 321 | _declare(context, stage.output_il_name, stage.output_type!) | |
| 322 | gen(stage.apply, context) | |
| 323 | _stloc(context, stage.output_il_name) | |
| 324 | fi | |
| 325 | od | |
| 326 | ||
| 327 | _gen_element_body(context, loop_start, loop_end) | |
| 328 | ||
| 329 | _branch(context, "br", System.Reflection.Metadata.ILOpCode.BR, loop_start) | |
| 330 | ||
| 331 | // The rewind falls straight into the loop exit. Only a spent | |
| 332 | // take reaches it: an exhausted source has already rewound | |
| 333 | // itself, and a consumer that stops at its first hit abandons | |
| 334 | // the chain exactly as the unfused lowering does. Whether the | |
| 335 | // cursor is rewound at all is asked of it at run time, as a | |
| 336 | // stage pipe rewinding its own cursor asks. | |
| 337 | if let reset = pipe_reset, `cast = pipe_cast, cursor_type = pipe_type then | |
| 338 | _mark(context, take_exit) | |
| 339 | ||
| 340 | _declare(context, pipe_il_name, cursor_type) | |
| 341 | gen(`cast, context) | |
| 342 | _stloc(context, pipe_il_name) | |
| 343 | ||
| 344 | _ldloc(context, pipe_il_name) | |
| 345 | _branch(context, "brfalse", System.Reflection.Metadata.ILOpCode.BRFALSE, loop_end) | |
| 346 | ||
| 347 | gen(reset, context) | |
| 348 | fi | |
| 349 | ||
| 350 | _mark(context, loop_end) | |
| 351 | ||
| 352 | if !is_void then | |
| 353 | _ldloc(context, result_il_name) | |
| 354 | fi | |
| 355 | si | |
| 356 | ||
| 357 | // A skip stage's countdown: decrement the running counter, leaving | |
| 358 | // both the new value in the local and a copy on the stack for the | |
| 359 | // caller to compare against zero. | |
| 360 | _gen_countdown(context: IR.CONTEXT, stage: FUSED_CONSUMER_STAGE) is | |
| 361 | _ldloc(context, stage.counter_il_name) | |
| 362 | _ldc_i4(context, 1) | |
| 363 | _op(context, "sub", System.Reflection.Metadata.ILOpCode.SUB) | |
| 364 | _op(context, "dup", System.Reflection.Metadata.ILOpCode.DUP) | |
| 365 | _stloc(context, stage.counter_il_name) | |
| 366 | _ldc_i4(context, 0) | |
| 367 | si | |
| 368 | ||
| 369 | // Initialise the result local before the loop. | |
| 370 | _gen_result_init(context: IR.CONTEXT) is | |
| 371 | if consumer_kind =~ "count" \/ consumer_kind =~ "any" then | |
| 372 | // count starts at 0; any starts false (0). | |
| 373 | _ldc_i4(context, 0) | |
| 374 | _stloc(context, result_il_name) | |
| 375 | elif consumer_kind =~ "all" then | |
| 376 | _ldc_i4(context, 1) | |
| 377 | _stloc(context, result_il_name) | |
| 378 | elif let seed = seed_init then | |
| 379 | // reduce (seed), find / first (empty MAYBE), collect (list). | |
| 380 | gen(seed, context) | |
| 381 | _stloc(context, result_il_name) | |
| 382 | fi | |
| 383 | si | |
| 384 | ||
| 385 | // Per-element consumer body (after the stages have produced the | |
| 386 | // surviving element). | |
| 387 | _gen_element_body(context: IR.CONTEXT, loop_start: IR.LABEL, loop_end: IR.LABEL) is | |
| 388 | if consumer_kind =~ "count" then | |
| 389 | _ldloc(context, result_il_name) | |
| 390 | _ldc_i4(context, 1) | |
| 391 | _op(context, "add", System.Reflection.Metadata.ILOpCode.ADD) | |
| 392 | _stloc(context, result_il_name) | |
| 393 | elif consumer_kind =~ "any" then | |
| 394 | let next = IR.LABEL() | |
| 395 | gen(consumer_apply!, context) | |
| 396 | _branch(context, "brfalse", System.Reflection.Metadata.ILOpCode.BRFALSE, next) | |
| 397 | _ldc_i4(context, 1) | |
| 398 | _stloc(context, result_il_name) | |
| 399 | _branch(context, "br", System.Reflection.Metadata.ILOpCode.BR, loop_end) | |
| 400 | _mark(context, next) | |
| 401 | elif consumer_kind =~ "all" then | |
| 402 | let next = IR.LABEL() | |
| 403 | gen(consumer_apply!, context) | |
| 404 | _branch(context, "brtrue", System.Reflection.Metadata.ILOpCode.BRTRUE, next) | |
| 405 | _ldc_i4(context, 0) | |
| 406 | _stloc(context, result_il_name) | |
| 407 | _branch(context, "br", System.Reflection.Metadata.ILOpCode.BR, loop_end) | |
| 408 | _mark(context, next) | |
| 409 | elif consumer_kind =~ "each" then | |
| 410 | gen(consumer_apply!, context) | |
| 411 | elif consumer_kind =~ "reduce" then | |
| 412 | gen(consumer_apply!, context) | |
| 413 | _stloc(context, result_il_name) | |
| 414 | elif consumer_kind =~ "find" then | |
| 415 | let next = IR.LABEL() | |
| 416 | gen(consumer_apply!, context) | |
| 417 | _branch(context, "brfalse", System.Reflection.Metadata.ILOpCode.BRFALSE, next) | |
| 418 | gen(wrap_element!, context) | |
| 419 | _stloc(context, result_il_name) | |
| 420 | _branch(context, "br", System.Reflection.Metadata.ILOpCode.BR, loop_end) | |
| 421 | _mark(context, next) | |
| 422 | elif consumer_kind =~ "first" then | |
| 423 | gen(wrap_element!, context) | |
| 424 | _stloc(context, result_il_name) | |
| 425 | _branch(context, "br", System.Reflection.Metadata.ILOpCode.BR, loop_end) | |
| 426 | elif consumer_kind =~ "collect" then | |
| 427 | gen(add_element!, context) | |
| 428 | fi | |
| 429 | si | |
| 430 | ||
| 431 | to_string() -> string => | |
| 432 | "fused-consumer:[{consumer_kind}]({_result_type})" | |
| 433 | si | |
| 434 | si |