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 | // Local-variable IL: assignments, variable visits, refutable bindings and destructuring. | |
| 18 | partial GENERATE_IL is | |
| 19 | visit(assign: Statements.ASSIGNMENT) is | |
| 20 | super.visit(assign) | |
| 21 | ||
| 22 | let v = assign.value | |
| 23 | if v? then | |
| 24 | add(v) | |
| 25 | fi | |
| 26 | si | |
| 27 | ||
| 28 | visit(expression: Statements.EXPRESSION) is | |
| 29 | super.visit(expression) | |
| 30 | ||
| 31 | try | |
| 32 | if !expression.want_value /\ expression.expression.value? then | |
| 33 | let value = expression.expression.value | |
| 34 | ||
| 35 | // `NULL.matches` answers true for every type, so a | |
| 36 | // null-typed value reads as void here. It is not: emit | |
| 37 | // it and pop, as for any other discarded statement | |
| 38 | // value. | |
| 39 | let is_void = | |
| 40 | value.type? /\ | |
| 41 | !value.type.is_null /\ | |
| 42 | value.type.matches(_innate_symbol_lookup.get_void_type()) | |
| 43 | ||
| 44 | // Bare `await E;` with void result: visit(AWAIT) | |
| 45 | // emitted the suspend eagerly; re-emitting the | |
| 46 | // wrapper would call gen on its unreplaced DUMMY. | |
| 47 | if !is_void \/ !isa Values.WRAPPER(value) then | |
| 48 | add(value) | |
| 49 | fi | |
| 50 | ||
| 51 | if !is_void then | |
| 52 | add(Values.INSTRUCTION(ILOpCode.POP)) | |
| 53 | fi | |
| 54 | fi | |
| 55 | catch e: System.Exception | |
| 56 | _logger.exception(expression.location, e, "caught exception generating IL for expression") | |
| 57 | yrt | |
| 58 | si | |
| 59 | ||
| 60 | // A local's declaration is a slot in the body's signature, so | |
| 61 | // it is only a local at all inside a block. Answers whether | |
| 62 | // the symbol was taken as one. | |
| 63 | _declare_local(symbol: Semantic.Symbols.Symbol?) -> bool is | |
| 64 | if !_block_context.is_in_block then | |
| 65 | return false | |
| 66 | fi | |
| 67 | ||
| 68 | if let local: Semantic.Symbols.LOCAL_VARIABLE = symbol /\ local.type? then | |
| 69 | current_block.add(Values.DECLARE_LOCAL(local.il_name, local.storage_type!)) | |
| 70 | fi | |
| 71 | ||
| 72 | return true | |
| 73 | si | |
| 74 | ||
| 75 | pre(variable: Variables.VARIABLE) -> bool is | |
| 76 | super.pre(variable) | |
| 77 | ||
| 78 | if variable.want_dispose then | |
| 79 | // variable will be declared outside the enclosing .try | |
| 80 | // so don't declare it here | |
| 81 | return false | |
| 82 | fi | |
| 83 | ||
| 84 | // Generator / async body locals: `state_machine_field` | |
| 85 | // was wired up in compile-expressions (so closures | |
| 86 | // freezing inside the body capture the right | |
| 87 | // ldarg.0/ldfld IL). Skip emitting `.locals init` here; | |
| 88 | // load/store routes through the frame field via | |
| 89 | // state_machine_field. | |
| 90 | let cf = current_function | |
| 91 | if cf? then | |
| 92 | let sm_for_function = Semantic.Symbols.state_machine_for(cf) | |
| 93 | ||
| 94 | if sm_for_function? /\ sm_for_function.frame? then | |
| 95 | return false | |
| 96 | fi | |
| 97 | ||
| 98 | let async_sm = Semantic.Symbols.async_state_machine_for(cf) | |
| 99 | ||
| 100 | if async_sm? /\ async_sm.frame? then | |
| 101 | return false | |
| 102 | fi | |
| 103 | fi | |
| 104 | ||
| 105 | // A local's declaration is a value in the block: metadata | |
| 106 | // has no `.locals init` directive, and the slot it | |
| 107 | // allocates has to be in place before any load of the | |
| 108 | // same name is encoded. A declaration at namespace or | |
| 109 | // class scope is a field, whose row the structure walk | |
| 110 | // writes, so nothing is emitted for one here. | |
| 111 | if _block_context.is_in_block then | |
| 112 | for name in variable.names do | |
| 113 | _declare_local(find(name)) | |
| 114 | od | |
| 115 | fi | |
| 116 | ||
| 117 | return false | |
| 118 | si | |
| 119 | ||
| 120 | visit(v: Variables.VARIABLE) is | |
| 121 | let initializer = v.initializer | |
| 122 | if initializer? then | |
| 123 | let init_value = initializer.value | |
| 124 | if init_value? then | |
| 125 | gen_destructuring_initialize(v.left, init_value) | |
| 126 | return | |
| 127 | fi | |
| 128 | fi | |
| 129 | ||
| 130 | // Destructured formal argument: no initializer expression - | |
| 131 | // the "source" to destructure is the synthesised physical | |
| 132 | // parameter declare-members attached to this VARIABLE. | |
| 133 | if v.is_argument /\ !v.left.is_simple_name then | |
| 134 | let group_symbol = symbol_for(v) | |
| 135 | ||
| 136 | if group_symbol? then | |
| 137 | gen_destructuring_initialize(v.left, group_symbol.load(v.location, null, _symbol_loader)) | |
| 138 | fi | |
| 139 | ||
| 140 | return | |
| 141 | fi | |
| 142 | ||
| 143 | // No initializer, but the local is captured by a closure | |
| 144 | // and reassigned, so `mark-boxed-locals` promoted its slot | |
| 145 | // to `Ghul.BOX[T]`. Allocate the empty box at declaration — | |
| 146 | // `Variable.store` routes through `_store_boxed_local`, | |
| 147 | // which emits `newobj Ghul.BOX[T]::.ctor()` when the value | |
| 148 | // is null. Without this the slot stays null, then the | |
| 149 | // closure frame's ctor stashes a null box reference and | |
| 150 | // every later read or write NREs. | |
| 151 | if v.left.is_simple_name then | |
| 152 | // is_simple_name => SIMPLE_VARIABLE_LEFT, whose name is non-null | |
| 153 | let name = v.left.name! | |
| 154 | let symbol = find(name) | |
| 155 | ||
| 156 | if let variable: Semantic.Symbols.LOCAL_VARIABLE = symbol /\ variable.is_boxed then | |
| 157 | add(variable.store_empty_box(v.left.location, _symbol_loader)) | |
| 158 | return | |
| 159 | fi | |
| 160 | fi | |
| 161 | ||
| 162 | // Loop-scoped `let x: T;` (no initializer) needs an | |
| 163 | // explicit default store: .NET only initialises locals | |
| 164 | // on method entry, so without re-zeroing each iteration | |
| 165 | // the variable carries its previous iteration's value | |
| 166 | // (issue #483). Outside a loop, the method-entry | |
| 167 | // initialisation is correct and we leave the existing | |
| 168 | // behaviour alone. Catch handlers receive their value | |
| 169 | // from the runtime — skip the default-store there too. | |
| 170 | if _loops.is_in_loop /\ !_in_catch_variable then | |
| 171 | gen_destructuring_default(v.left) | |
| 172 | fi | |
| 173 | si | |
| 174 | ||
| 175 | // `if let` clauses' bound names are declared as CLR locals | |
| 176 | // here — same as `pre(VARIABLE)` does for a plain let. The | |
| 177 | // store IL itself is emitted later by | |
| 178 | // `gen_destructuring_initialize` in the if-branch handler. | |
| 179 | pre(rb: Statements.REFUTABLE_BINDING) -> bool is | |
| 180 | let cf = current_function | |
| 181 | if cf? then | |
| 182 | let sm_for_function = Semantic.Symbols.state_machine_for(cf) | |
| 183 | ||
| 184 | if sm_for_function? /\ sm_for_function.frame? then | |
| 185 | return false | |
| 186 | fi | |
| 187 | ||
| 188 | let async_sm = Semantic.Symbols.async_state_machine_for(cf) | |
| 189 | ||
| 190 | if async_sm? /\ async_sm.frame? then | |
| 191 | return false | |
| 192 | fi | |
| 193 | fi | |
| 194 | ||
| 195 | for c in rb.clauses do | |
| 196 | for name in c.pattern.names! do | |
| 197 | _declare_local(find(name)) | |
| 198 | od | |
| 199 | od | |
| 200 | ||
| 201 | return false | |
| 202 | si | |
| 203 | ||
| 204 | visit(rb: Statements.REFUTABLE_BINDING) is | |
| 205 | si | |
| 206 | ||
| 207 | gen_destructuring_default(left: Variables.VariableLeft) is | |
| 208 | if left.is_simple_name then | |
| 209 | // is_simple_name => SIMPLE_VARIABLE_LEFT, whose name is non-null | |
| 210 | let name = left.name! | |
| 211 | let symbol = find(name) | |
| 212 | ||
| 213 | if !symbol? \/ symbol.is_argument then | |
| 214 | return | |
| 215 | fi | |
| 216 | let symbol_type = symbol.type | |
| 217 | if !symbol_type? then return; fi | |
| 218 | ||
| 219 | let default_value = IR.Values.DEFAULT(symbol_type) | |
| 220 | ||
| 221 | let store = symbol.store(left.location, null, default_value, _symbol_loader, true) | |
| 222 | ||
| 223 | add(store) | |
| 224 | else | |
| 225 | for e in left.elements! do | |
| 226 | gen_destructuring_default(e) | |
| 227 | od | |
| 228 | fi | |
| 229 | si | |
| 230 | ||
| 231 | // TODO use Pre/Visit instead | |
| 232 | gen_destructuring_initialize(left: Variables.VariableLeft, value: Value) is | |
| 233 | gen_destructuring_initialize(left, value, null) | |
| 234 | si | |
| 235 | ||
| 236 | // Per-element narrowing: when `narrow_branch_label` is set | |
| 237 | // (the `if let` path), an element carrying a `type_expression` | |
| 238 | // emits `isinst T; brfalse next` before binding. The cast | |
| 239 | // result is what gets stored / recursively destructured, so | |
| 240 | // the bound slot is typed T. Plain `let` and `for` pass null | |
| 241 | // for the label and just store the raw value (the declared | |
| 242 | // element type is a compile-time assertion only, not a runtime | |
| 243 | // test). | |
| 244 | gen_destructuring_initialize( | |
| 245 | left: Variables.VariableLeft, | |
| 246 | value: Value, | |
| 247 | narrow_branch_label: LABEL? | |
| 248 | ) is | |
| 249 | // Literal-leaf inside a destructure pattern — runtime | |
| 250 | // value-equality test against the source position. Only | |
| 251 | // valid in refutable contexts (`if let` / `case`-when); | |
| 252 | // a plain `let` with a literal leaf is rejected during | |
| 253 | // semantic analysis, so by the time we get here a literal | |
| 254 | // leaf without a narrow_branch_label is a no-op (the | |
| 255 | // emit-nothing path). | |
| 256 | if isa Variables.LITERAL_VARIABLE_LEFT(left) then | |
| 257 | if narrow_branch_label? then | |
| 258 | let literal_leaf = cast Variables.LITERAL_VARIABLE_LEFT(left) | |
| 259 | let leaf_state = _variable_left_state.get(literal_leaf) | |
| 260 | ||
| 261 | // The value-equality test compile-expressions built | |
| 262 | // for this leaf embeds the literal and reads the | |
| 263 | // source position through a hole; fill the hole and | |
| 264 | // branch on the result. The test can read the | |
| 265 | // operand more than once - a null-safe `=~` tests | |
| 266 | // presence before dispatching - and an instance | |
| 267 | // operator on a value type needs its address, so | |
| 268 | // spill anything that is not already a local. | |
| 269 | if let | |
| 270 | test = leaf_state?.match_test, | |
| 271 | operand = leaf_state?.match_operand | |
| 272 | then | |
| 273 | operand.value = | |
| 274 | if value.is_lightweight_pure /\ value.has_address then | |
| 275 | value | |
| 276 | else | |
| 277 | TEMP(current_block, "literal-leaf", value).load() | |
| 278 | fi | |
| 279 | ||
| 280 | get_brancher_for_block().branch( | |
| 281 | BRANCH.Z, | |
| 282 | test, | |
| 283 | narrow_branch_label, | |
| 284 | "literal-leaf" | |
| 285 | ) | |
| 286 | ||
| 287 | return | |
| 288 | fi | |
| 289 | ||
| 290 | // No test: a `null` leaf, or a type for which | |
| 291 | // neither `=~` nor `<>` resolved. Compare raw, which | |
| 292 | // is what a bare `==` does too. | |
| 293 | literal_leaf.expression.walk(self) | |
| 294 | ||
| 295 | let leaf_value = literal_leaf.expression.value | |
| 296 | if leaf_value? then | |
| 297 | get_brancher_for_block().branch( | |
| 298 | BRANCH.NE, | |
| 299 | value, | |
| 300 | leaf_value, | |
| 301 | narrow_branch_label, | |
| 302 | "literal-leaf" | |
| 303 | ) | |
| 304 | fi | |
| 305 | fi | |
| 306 | ||
| 307 | return | |
| 308 | fi | |
| 309 | ||
| 310 | let narrowed mut = value | |
| 311 | ||
| 312 | let type_expression = left.type_expression | |
| 313 | if | |
| 314 | type_expression? /\ | |
| 315 | type_expression.type? /\ | |
| 316 | narrow_branch_label? | |
| 317 | then | |
| 318 | let cast_value = IR.Values.CAST(type_expression.type, value, false) | |
| 319 | let temp = TEMP(current_block, "narrow", cast_value) | |
| 320 | ||
| 321 | get_brancher_for_block().branch(BRANCH.Z, temp.load(), narrow_branch_label, "narrow") | |
| 322 | ||
| 323 | narrowed = temp.load() | |
| 324 | fi | |
| 325 | ||
| 326 | if left.is_simple_name then | |
| 327 | // is_simple_name => SIMPLE_VARIABLE_LEFT, whose name is non-null | |
| 328 | let name = left.name! | |
| 329 | let symbol = find(name) | |
| 330 | ||
| 331 | if !symbol? then | |
| 332 | return | |
| 333 | fi | |
| 334 | ||
| 335 | let store = symbol.store(left.location, null, narrowed, _symbol_loader, true) | |
| 336 | ||
| 337 | add(store) | |
| 338 | else | |
| 339 | let from_type = narrowed.type | |
| 340 | ||
| 341 | if !from_type? then return; fi | |
| 342 | ||
| 343 | // !is_simple_name => a destructure group, whose elements are non-null | |
| 344 | let elements = left.elements! | |
| 345 | ||
| 346 | let get_from = | |
| 347 | if narrowed.is_lightweight_pure then | |
| 348 | () => narrowed | |
| 349 | else | |
| 350 | let temp = TEMP(current_block, "destructure", narrowed) | |
| 351 | () => temp.load() | |
| 352 | fi | |
| 353 | ||
| 354 | let is_named_group = elements.count > 0 /\ elements[0].source_field_name? | |
| 355 | ||
| 356 | let strategy = | |
| 357 | if is_named_group then | |
| 358 | let field_names = Collections.LIST[string?]() | |
| 359 | for e in elements do | |
| 360 | let sfn = e.source_field_name | |
| 361 | field_names.add(if sfn? then sfn.name else null fi) | |
| 362 | od | |
| 363 | DESTRUCTURE_RESOLVER.resolve_strategy_by_name(from_type, field_names) | |
| 364 | else | |
| 365 | DESTRUCTURE_RESOLVER.resolve_strategy(from_type, elements.count) | |
| 366 | fi | |
| 367 | ||
| 368 | if strategy.is_deconstruct then | |
| 369 | // is_deconstruct ⇔ deconstruct_function? per its definition | |
| 370 | let deconstruct = strategy.deconstruct_function! | |
| 371 | let arg_temps = Collections.LIST[IR.TEMP]() | |
| 372 | let call_args = Collections.LIST[Value]() | |
| 373 | ||
| 374 | for i in 0..deconstruct.arguments.count do | |
| 375 | let ref_type = deconstruct.arguments[i] | |
| 376 | let element_type = ref_type.get_element_type() | |
| 377 | assert element_type? else "deconstruct arg type has no element type" | |
| 378 | let arg_temp = IR.TEMP(current_block, "destructure_arg", i, element_type) | |
| 379 | ||
| 380 | arg_temps.add(arg_temp) | |
| 381 | call_args.add(IR.Values.ADDRESS(arg_temp.load(), ref_type)) | |
| 382 | od | |
| 383 | ||
| 384 | let call_value = | |
| 385 | deconstruct.call( | |
| 386 | left.location, | |
| 387 | get_from(), | |
| 388 | call_args, | |
| 389 | null, | |
| 390 | _function_caller | |
| 391 | ) | |
| 392 | ||
| 393 | add(call_value) | |
| 394 | ||
| 395 | for (i, e) in elements |> index() do | |
| 396 | gen_destructuring_initialize(e, arg_temps[i].load(), narrow_branch_label) | |
| 397 | od | |
| 398 | else | |
| 399 | let members = strategy.members | |
| 400 | ||
| 401 | for (i, e) in elements |> index() do | |
| 402 | let member = members[i] | |
| 403 | ||
| 404 | if member? then | |
| 405 | let member_value = member.load(LOCATION.internal, get_from(), _symbol_loader) | |
| 406 | ||
| 407 | gen_destructuring_initialize(e, member_value, narrow_branch_label) | |
| 408 | fi | |
| 409 | od | |
| 410 | fi | |
| 411 | fi | |
| 412 | si | |
| 413 | ||
| 414 | si | |
| 415 | si |