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 | class FUSED_CONSUMER_STAGE is | |
| 13 | is_filter: bool public; | |
| 14 | delegate_il_name: string public; | |
| 15 | delegate_type: Type public; | |
| 16 | delegate_init: Value public; | |
| 17 | apply: Value public; | |
| 18 | output_il_name: string public; | |
| 19 | output_type: Type? public; | |
| 20 | ||
| 21 | // A `take`/`skip` stage carries no delegate: the count is evaluated | |
| 22 | // once into `counter_il_name` and each element decrements it (take | |
| 23 | // leaves the loop when it runs out, skip drops the element while it | |
| 24 | // remains). `is_countdown` marks either. | |
| 25 | is_take: bool public; | |
| 26 | is_skip: bool public; | |
| 27 | counter_il_name: string public; | |
| 28 | count_init: Value? public; | |
| 29 | ||
| 30 | is_countdown: bool => is_take \/ is_skip; | |
| 31 | ||
| 32 | init(is_filter: bool, delegate_il_name: string, delegate_type: Type, delegate_init: Value, apply: Value, output_il_name: string, output_type: Type?) is | |
| 33 | self.is_filter = is_filter; | |
| 34 | self.delegate_il_name = delegate_il_name; | |
| 35 | self.delegate_type = delegate_type; | |
| 36 | self.delegate_init = delegate_init; | |
| 37 | self.apply = apply; | |
| 38 | self.output_il_name = output_il_name; | |
| 39 | self.output_type = output_type; | |
| 40 | si | |
| 41 | ||
| 42 | init(is_take: bool, is_skip: bool, counter_il_name: string, count_init: Value) is | |
| 43 | self.is_take = is_take; | |
| 44 | self.is_skip = is_skip; | |
| 45 | self.counter_il_name = counter_il_name; | |
| 46 | self.count_init = count_init; | |
| 47 | si | |
| 48 | si | |
| 49 | ||
| 50 | // A terminal Pipe consumer (`count`, `any`, `all`, `for_each`, `reduce`, | |
| 51 | // `find`, `first`, `collect_list`) fused with its chain: drives the | |
| 52 | // chain's source iterator directly, applies the map/filter stages per | |
| 53 | // element by calling each stage's hoisted delegate, runs the consumer's | |
| 54 | // per-element body, and leaves the consumer's result on the stack - | |
| 55 | // emitted in place of the consumer method call, so it flows through any | |
| 56 | // expression context (like NULL_COALESCE_VALUE). A non-sealed source is | |
| 57 | // guarded by `isa Pipe[element]`, falling back to the original call. | |
| 58 | class FUSED_CONSUMER: Value is | |
| 59 | _result_type: Type; | |
| 60 | ||
| 61 | // Guard: when guard_isa_type is non-null the source could | |
| 62 | // dynamically be a user Pipe, so emit `if source isa Pipe then | |
| 63 | // fallback else fused`. `source` is the value the `isa` tests. | |
| 64 | guard_isa_type: Type? public; | |
| 65 | source: Value public; | |
| 66 | fallback: Value public; | |
| 67 | ||
| 68 | // Loop skeleton, pre-built against the '.fc_iter.N' iterator local. | |
| 69 | // `iterator_init` produces the source's own iterator (or is the | |
| 70 | // source itself when it is already an iterator); move_next / | |
| 71 | // read_current drive it. | |
| 72 | has_iterator: bool public; | |
| 73 | iterator_init: Value public; | |
| 74 | iterator_il_name: string public; | |
| 75 | iterator_type: Type public; | |
| 76 | move_next: Value public; | |
| 77 | read_current: Value public; | |
| 78 | ||
| 79 | // The element local the innermost stage reads. | |
| 80 | element_il_name: string public; | |
| 81 | element_type: Type public; | |
| 82 | ||
| 83 | stages: Collections.List[FUSED_CONSUMER_STAGE] public; | |
| 84 | ||
| 85 | // The surviving element after every stage - what the consumer body | |
| 86 | // operates on (`collect` adds it, `find` returns it, ...). | |
| 87 | final_element_il_name: string public; | |
| 88 | final_element_type: Type public; | |
| 89 | ||
| 90 | // Consumer specifics. | |
| 91 | consumer_kind: string public; | |
| 92 | ||
| 93 | // The result local, named in compile-expressions so pre-built | |
| 94 | // values (a `reduce` accumulator) can reference it. | |
| 95 | result_il_name: string public; | |
| 96 | ||
| 97 | // A consumer with a function argument (`any`/`all`/`find`/`for_each` | |
| 98 | // predicate/action, `reduce` accumulator) hoists it into a local so | |
| 99 | // the delegate is built once. `consumer_apply` is that argument | |
| 100 | // applied per element (to the final element, and for `reduce` the | |
| 101 | // running result too), pre-built against the hoisted local. | |
| 102 | consumer_arg_il_name: string? public; | |
| 103 | consumer_arg_type: Type? public; | |
| 104 | consumer_arg_init: Value? public; | |
| 105 | consumer_apply: Value? public; | |
| 106 | ||
| 107 | // `reduce` seeds the result with its first argument; `find`/`first` | |
| 108 | // seed the result with an empty MAYBE; `collect` seeds it with a | |
| 109 | // fresh list. | |
| 110 | seed_init: Value? public; | |
| 111 | ||
| 112 | // `find`/`first` wrap the surviving element in a MAYBE; `collect` | |
| 113 | // adds it to the result list. Pre-built against the element / result | |
| 114 | // locals. | |
| 115 | wrap_element: Value? public; | |
| 116 | add_element: Value? public; | |
| 117 | ||
| 118 | type: Type => _result_type; | |
| 119 | is_lightweight_pure: bool => false; | |
| 120 | ||
| 121 | init( | |
| 122 | result_type: Type, | |
| 123 | source: Value, | |
| 124 | fallback: Value, | |
| 125 | iterator_init: Value, | |
| 126 | has_iterator: bool, | |
| 127 | iterator_il_name: string, | |
| 128 | iterator_type: Type, | |
| 129 | move_next: Value, | |
| 130 | read_current: Value, | |
| 131 | element_il_name: string, | |
| 132 | element_type: Type, | |
| 133 | stages: Collections.List[FUSED_CONSUMER_STAGE], | |
| 134 | final_element_il_name: string, | |
| 135 | final_element_type: Type, | |
| 136 | consumer_kind: string, | |
| 137 | result_il_name: string | |
| 138 | ) is | |
| 139 | super.init(); | |
| 140 | ||
| 141 | self._result_type = result_type; | |
| 142 | self.source = source; | |
| 143 | self.fallback = fallback; | |
| 144 | self.iterator_init = iterator_init; | |
| 145 | self.has_iterator = has_iterator; | |
| 146 | self.iterator_il_name = iterator_il_name; | |
| 147 | self.iterator_type = iterator_type; | |
| 148 | self.move_next = move_next; | |
| 149 | self.read_current = read_current; | |
| 150 | self.element_il_name = element_il_name; | |
| 151 | self.element_type = element_type; | |
| 152 | self.stages = stages; | |
| 153 | self.final_element_il_name = final_element_il_name; | |
| 154 | self.final_element_type = final_element_type; | |
| 155 | self.consumer_kind = consumer_kind; | |
| 156 | self.result_il_name = result_il_name; | |
| 157 | si | |
| 158 | ||
| 159 | gen(context: IR.CONTEXT) is | |
| 160 | if guard_isa_type? then | |
| 161 | let fused_label = IR.LABEL(); | |
| 162 | let end_label = IR.LABEL(); | |
| 163 | ||
| 164 | // if source isa Pipe[element] then fall back else fuse | |
| 165 | gen(source, context); | |
| 166 | context.write_line("isinst {guard_isa_type!.get_il_type()}"); | |
| 167 | context.write_line("brfalse {fused_label}"); | |
| 168 | ||
| 169 | gen(fallback, context); | |
| 170 | context.write_line("br {end_label}"); | |
| 171 | ||
| 172 | context.write_line("{fused_label}:"); | |
| 173 | _gen_fused_loop(context); | |
| 174 | ||
| 175 | context.write_line("{end_label}:"); | |
| 176 | else | |
| 177 | _gen_fused_loop(context); | |
| 178 | fi | |
| 179 | si | |
| 180 | ||
| 181 | is_void: bool => consumer_kind =~ "for_each"; | |
| 182 | ||
| 183 | // Emit the fused loop, leaving the consumer's result on the stack | |
| 184 | // (nothing for a void `for_each`). | |
| 185 | _gen_fused_loop(context: IR.CONTEXT) is | |
| 186 | let loop_start = IR.LABEL(); | |
| 187 | let loop_end = IR.LABEL(); | |
| 188 | ||
| 189 | if !is_void then | |
| 190 | context.write_line(".locals init ({_result_type.get_il_type()} {result_il_name})"); | |
| 191 | _gen_result_init(context); | |
| 192 | fi | |
| 193 | ||
| 194 | // A consumer's function argument is built once into a local. | |
| 195 | if let arg_name = consumer_arg_il_name, arg_type = consumer_arg_type, arg_init = consumer_arg_init then | |
| 196 | context.write_line(".locals init ({arg_type.get_il_type()} {arg_name})"); | |
| 197 | gen(arg_init, context); | |
| 198 | context.write_line("stloc {arg_name}"); | |
| 199 | fi | |
| 200 | ||
| 201 | // Each stage's lambda delegate is built once before the loop; a | |
| 202 | // take/skip stage instead seeds its running counter with the | |
| 203 | // count evaluated once. | |
| 204 | for stage in stages do | |
| 205 | if stage.is_countdown then | |
| 206 | context.write_line(".locals init (int32 {stage.counter_il_name})"); | |
| 207 | gen(stage.count_init!, context); | |
| 208 | context.write_line("stloc {stage.counter_il_name}"); | |
| 209 | else | |
| 210 | context.write_line(".locals init ({stage.delegate_type.get_il_type()} {stage.delegate_il_name})"); | |
| 211 | gen(stage.delegate_init, context); | |
| 212 | context.write_line("stloc {stage.delegate_il_name}"); | |
| 213 | fi | |
| 214 | od | |
| 215 | ||
| 216 | // The iterator always lives in a local - whether it came from | |
| 217 | // the source's iterator member or the source is itself an | |
| 218 | // iterator (a value-type range), move_next / current drive it | |
| 219 | // through the local (by address for a value-type iterator). | |
| 220 | context.write_line(".locals init ({iterator_type.get_il_type()} {iterator_il_name})"); | |
| 221 | gen(iterator_init, context); | |
| 222 | context.write_line("stloc {iterator_il_name}"); | |
| 223 | ||
| 224 | context.write_line("{loop_start}:"); | |
| 225 | ||
| 226 | gen(move_next, context); | |
| 227 | context.write_line("brfalse {loop_end}"); | |
| 228 | ||
| 229 | context.write_line(".locals init ({element_type.get_il_type()} {element_il_name})"); | |
| 230 | gen(read_current, context); | |
| 231 | context.write_line("stloc {element_il_name}"); | |
| 232 | ||
| 233 | for stage in stages do | |
| 234 | if stage.is_take then | |
| 235 | // Decrement; once the counter goes negative the take | |
| 236 | // limit is spent, so leave the loop. | |
| 237 | context.write_line("ldloc {stage.counter_il_name}"); | |
| 238 | context.write_line("ldc.i4.1"); | |
| 239 | context.write_line("sub"); | |
| 240 | context.write_line("dup"); | |
| 241 | context.write_line("stloc {stage.counter_il_name}"); | |
| 242 | context.write_line("ldc.i4.0"); | |
| 243 | context.write_line("blt {loop_end}"); | |
| 244 | elif stage.is_skip then | |
| 245 | // Decrement; while the counter stays non-negative this is | |
| 246 | // a leading element to drop, so pull the next one. | |
| 247 | context.write_line("ldloc {stage.counter_il_name}"); | |
| 248 | context.write_line("ldc.i4.1"); | |
| 249 | context.write_line("sub"); | |
| 250 | context.write_line("dup"); | |
| 251 | context.write_line("stloc {stage.counter_il_name}"); | |
| 252 | context.write_line("ldc.i4.0"); | |
| 253 | context.write_line("bge {loop_start}"); | |
| 254 | elif stage.is_filter then | |
| 255 | gen(stage.apply, context); | |
| 256 | context.write_line("brfalse {loop_start}"); | |
| 257 | else | |
| 258 | context.write_line(".locals init ({stage.output_type!.get_il_type()} {stage.output_il_name})"); | |
| 259 | gen(stage.apply, context); | |
| 260 | context.write_line("stloc {stage.output_il_name}"); | |
| 261 | fi | |
| 262 | od | |
| 263 | ||
| 264 | _gen_element_body(context, loop_start, loop_end); | |
| 265 | ||
| 266 | context.write_line("br {loop_start}"); | |
| 267 | context.write_line("{loop_end}:"); | |
| 268 | ||
| 269 | if !is_void then | |
| 270 | context.write_line("ldloc {result_il_name}"); | |
| 271 | fi | |
| 272 | si | |
| 273 | ||
| 274 | // Initialise the result local before the loop. | |
| 275 | _gen_result_init(context: IR.CONTEXT) is | |
| 276 | if consumer_kind =~ "count" \/ consumer_kind =~ "any" then | |
| 277 | // count starts at 0; any starts false (0). | |
| 278 | context.write_line("ldc.i4.0"); | |
| 279 | context.write_line("stloc {result_il_name}"); | |
| 280 | elif consumer_kind =~ "all" then | |
| 281 | context.write_line("ldc.i4.1"); | |
| 282 | context.write_line("stloc {result_il_name}"); | |
| 283 | elif let seed = seed_init then | |
| 284 | // reduce (seed), find / first (empty MAYBE), collect (list). | |
| 285 | gen(seed, context); | |
| 286 | context.write_line("stloc {result_il_name}"); | |
| 287 | fi | |
| 288 | si | |
| 289 | ||
| 290 | // Per-element consumer body (after the stages have produced the | |
| 291 | // surviving element). | |
| 292 | _gen_element_body(context: IR.CONTEXT, loop_start: IR.LABEL, loop_end: IR.LABEL) is | |
| 293 | if consumer_kind =~ "count" then | |
| 294 | context.write_line("ldloc {result_il_name}"); | |
| 295 | context.write_line("ldc.i4.1"); | |
| 296 | context.write_line("add"); | |
| 297 | context.write_line("stloc {result_il_name}"); | |
| 298 | elif consumer_kind =~ "any" then | |
| 299 | let next = IR.LABEL(); | |
| 300 | gen(consumer_apply!, context); | |
| 301 | context.write_line("brfalse {next}"); | |
| 302 | context.write_line("ldc.i4.1"); | |
| 303 | context.write_line("stloc {result_il_name}"); | |
| 304 | context.write_line("br {loop_end}"); | |
| 305 | context.write_line("{next}:"); | |
| 306 | elif consumer_kind =~ "all" then | |
| 307 | let next = IR.LABEL(); | |
| 308 | gen(consumer_apply!, context); | |
| 309 | context.write_line("brtrue {next}"); | |
| 310 | context.write_line("ldc.i4.0"); | |
| 311 | context.write_line("stloc {result_il_name}"); | |
| 312 | context.write_line("br {loop_end}"); | |
| 313 | context.write_line("{next}:"); | |
| 314 | elif consumer_kind =~ "for_each" then | |
| 315 | gen(consumer_apply!, context); | |
| 316 | elif consumer_kind =~ "reduce" then | |
| 317 | gen(consumer_apply!, context); | |
| 318 | context.write_line("stloc {result_il_name}"); | |
| 319 | elif consumer_kind =~ "find" then | |
| 320 | let next = IR.LABEL(); | |
| 321 | gen(consumer_apply!, context); | |
| 322 | context.write_line("brfalse {next}"); | |
| 323 | gen(wrap_element!, context); | |
| 324 | context.write_line("stloc {result_il_name}"); | |
| 325 | context.write_line("br {loop_end}"); | |
| 326 | context.write_line("{next}:"); | |
| 327 | elif consumer_kind =~ "first" then | |
| 328 | gen(wrap_element!, context); | |
| 329 | context.write_line("stloc {result_il_name}"); | |
| 330 | context.write_line("br {loop_end}"); | |
| 331 | elif consumer_kind =~ "collect" then | |
| 332 | gen(add_element!, context); | |
| 333 | fi | |
| 334 | si | |
| 335 | ||
| 336 | to_string() -> string => | |
| 337 | "fused-consumer:[{consumer_kind}]({_result_type})"; | |
| 338 | si | |
| 339 | si |