Appearance
| 1 | namespace IR.Values is | |
| 2 | use Semantic.Types.Type | |
| 3 | use System.Reflection.Metadata.ILOpCode | |
| 4 | ||
| 5 | // The result of a coalescing member access `a?.b` or method call | |
| 6 | // `a?.m(...)`. The receiver is short-circuited to absent on null | |
| 7 | // (reference-type optional) or HasValue=false (value-type optional | |
| 8 | // via NULLABLE / MAYBE); otherwise the member value - a field or | |
| 9 | // property load, or a whole call including argument evaluation - | |
| 10 | // is produced against the unwrapped receiver. | |
| 11 | // | |
| 12 | // Built by COMPILE_ACCESS.build_coalesce_wrap. The construction | |
| 13 | // wires sub-Values whose gen() composes the final IL inline - no | |
| 14 | // `if let` tree-rewrite, no semantic-pass spilling. | |
| 15 | // | |
| 16 | // Reference-type receiver (T? where T is a class), ref-type | |
| 17 | // member result: | |
| 18 | // <recv IL> // [..., recv] | |
| 19 | // dup // [..., recv, recv] | |
| 20 | // brfalse <null> // [..., recv] | |
| 21 | // <member_load IL> // [..., U] (member_load reads 'this' from stack) | |
| 22 | // br <end> | |
| 23 | // <null>: | |
| 24 | // pop | |
| 25 | // <null_value IL> // ldnull (ref) or default sentinel (value) | |
| 26 | // <end>: | |
| 27 | // | |
| 28 | // Value-type receiver (NULLABLE[T] / MAYBE[T]): | |
| 29 | // <recv address IL> // [..., &recv] | |
| 30 | // dup // [..., &recv, &recv] | |
| 31 | // <presence_test IL> // [..., &recv, bool] (call get_has_value on 'this' addr) | |
| 32 | // brfalse <null> // [..., &recv] | |
| 33 | // <value_extract IL> // [..., T] (call get_value on 'this' addr) | |
| 34 | // <member_load IL> // [..., U] | |
| 35 | // br <end> | |
| 36 | // <null>: | |
| 37 | // pop | |
| 38 | // <null_value IL> | |
| 39 | // <end>: | |
| 40 | // | |
| 41 | // The null_value is `ldnull` for a reference-type result and a | |
| 42 | // fresh DEFAULT for a value-type result (DEFAULT hoists a | |
| 43 | // `.locals init` and pushes the zero value). A void member value | |
| 44 | // (a coalesced call to a void method) has no null_value - neither | |
| 45 | // arm leaves a result on the stack. | |
| 46 | // | |
| 47 | // A static member reached through `?.` never consumes the tested | |
| 48 | // receiver: `receiver_consumed` false makes the present arm pop | |
| 49 | // the receiver (or its dup'd address) before the member value, | |
| 50 | // and the value-type shape skips the value extract entirely. | |
| 51 | class COALESCE_LOAD: Value is | |
| 52 | receiver: Value | |
| 53 | presence_test: Value? | |
| 54 | value_extract: Value? | |
| 55 | member_load: Value | |
| 56 | null_value: Value? | |
| 57 | _result_type: Type | |
| 58 | _receiver_is_value_type: bool | |
| 59 | _receiver_consumed: bool | |
| 60 | ||
| 61 | type: Type => _result_type | |
| 62 | is_lightweight_pure: bool => false | |
| 63 | ||
| 64 | receiver_is_value_type: bool => _receiver_is_value_type | |
| 65 | ||
| 66 | // Reference-type receiver constructor. `member_load`'s | |
| 67 | // receiver should be a STACK_TOP - gen() leaves the receiver | |
| 68 | // on the stack ready for the access op. | |
| 69 | init( | |
| 70 | receiver: Value, | |
| 71 | member_load: Value, | |
| 72 | null_value: Value?, | |
| 73 | result_type: Type, | |
| 74 | receiver_consumed: bool | |
| 75 | ) is | |
| 76 | super.init() | |
| 77 | ||
| 78 | self.receiver = receiver | |
| 79 | self.member_load = member_load | |
| 80 | self.null_value = null_value | |
| 81 | self._result_type = result_type | |
| 82 | self._receiver_is_value_type = false | |
| 83 | self._receiver_consumed = receiver_consumed | |
| 84 | si | |
| 85 | ||
| 86 | // Value-type receiver constructor. `receiver`'s address is | |
| 87 | // pushed once, dup'd, and used by `presence_test` (HasValue) | |
| 88 | // and `value_extract` (Value). Both receivers should consume | |
| 89 | // the address from the stack - built with a STACK_TOP_ADDRESS | |
| 90 | // standing in for 'this'. `value_extract` is null when the | |
| 91 | // member value doesn't consume the receiver. | |
| 92 | init( | |
| 93 | receiver: Value, | |
| 94 | presence_test: Value, | |
| 95 | value_extract: Value?, | |
| 96 | member_load: Value, | |
| 97 | null_value: Value?, | |
| 98 | result_type: Type, | |
| 99 | receiver_consumed: bool | |
| 100 | ) is | |
| 101 | super.init() | |
| 102 | ||
| 103 | self.receiver = receiver | |
| 104 | self.presence_test = presence_test | |
| 105 | self.value_extract = value_extract | |
| 106 | self.member_load = member_load | |
| 107 | self.null_value = null_value | |
| 108 | self._result_type = result_type | |
| 109 | self._receiver_is_value_type = true | |
| 110 | self._receiver_consumed = receiver_consumed | |
| 111 | si | |
| 112 | ||
| 113 | gen(context: IR.CONTEXT) is | |
| 114 | let null_label = IR.LABEL() | |
| 115 | let end_label = IR.LABEL() | |
| 116 | ||
| 117 | let body = context.current_srm_body_emitter! | |
| 118 | if _receiver_is_value_type then | |
| 119 | // A receiver with no address of its own - a call | |
| 120 | // result, or a nested coalescing wrap - is spilled | |
| 121 | // to a local first, the same way NULL_COALESCE_VALUE | |
| 122 | // drives its value-shape `??`. | |
| 123 | let spilled mut = "" | |
| 124 | ||
| 125 | if receiver.has_address then | |
| 126 | receiver.gen_address(context) | |
| 127 | else | |
| 128 | spilled = ".coalesce.receiver.{TEMP.get_next_id()}" | |
| 129 | body.declare_local(spilled, receiver.type!) | |
| 130 | gen(receiver, context) | |
| 131 | body.stloc(spilled) | |
| 132 | body.ldloca(spilled) | |
| 133 | fi | |
| 134 | ||
| 135 | body.op(ILOpCode.DUP) | |
| 136 | gen(presence_test!, context) | |
| 137 | body.branch(ILOpCode.BRFALSE, null_label) | |
| 138 | ||
| 139 | if _receiver_consumed then | |
| 140 | gen(value_extract!, context) | |
| 141 | else | |
| 142 | body.op(ILOpCode.POP) | |
| 143 | fi | |
| 144 | else | |
| 145 | gen(receiver, context) | |
| 146 | body.op(ILOpCode.DUP) | |
| 147 | body.branch(ILOpCode.BRFALSE, null_label) | |
| 148 | ||
| 149 | if !_receiver_consumed then | |
| 150 | body.op(ILOpCode.POP) | |
| 151 | fi | |
| 152 | fi | |
| 153 | ||
| 154 | gen(member_load, context) | |
| 155 | body.branch(ILOpCode.BR, end_label) | |
| 156 | body.mark_label(null_label) | |
| 157 | body.op(ILOpCode.POP) | |
| 158 | ||
| 159 | if null_value? then | |
| 160 | gen(null_value, context) | |
| 161 | fi | |
| 162 | ||
| 163 | body.mark_label(end_label) | |
| 164 | si | |
| 165 | ||
| 166 | // Spill the whole short-circuit to a local and hand back its | |
| 167 | // address: the composite's own branching can't run in address | |
| 168 | // mode, and a consumer asking for the address (a `~>` stage | |
| 169 | // whose subject is itself a `~>` result, `??` over a | |
| 170 | // value-type optional) needs a stable slot. | |
| 171 | gen_address(context: IR.CONTEXT) is | |
| 172 | let id = TEMP.get_next_id() | |
| 173 | ||
| 174 | let name = ".coalesce.{id}" | |
| 175 | ||
| 176 | let body = context.current_srm_body_emitter! | |
| 177 | body.declare_local(name, _result_type) | |
| 178 | gen(context) | |
| 179 | body.stloc(name) | |
| 180 | body.ldloca(name) | |
| 181 | si | |
| 182 | ||
| 183 | to_string() -> string => | |
| 184 | "coalesce-load:[{type}]({receiver},{member_load})" | |
| 185 | si | |
| 186 | ||
| 187 | // A no-op Value that types as `T` and stands in for "the value | |
| 188 | // (reference or struct) already on the IL stack". Embedded as | |
| 189 | // the `from` of a `Load.INSTANCE_FIELD` / property-getter CALL | |
| 190 | // when an outer composite (COALESCE_LOAD) has already pushed | |
| 191 | // the receiver - letting the inner load emit only its trailing | |
| 192 | // access op. | |
| 193 | class STACK_TOP: Value is | |
| 194 | _type: Type? | |
| 195 | ||
| 196 | type: Type? => _type | |
| 197 | is_value_type: bool => _type? /\ _type.is_value_type | |
| 198 | is_lightweight_pure: bool => true | |
| 199 | ||
| 200 | init(type: Type) is | |
| 201 | super.init() | |
| 202 | self._type = type | |
| 203 | si | |
| 204 | ||
| 205 | gen(context: IR.CONTEXT) is | |
| 206 | si | |
| 207 | ||
| 208 | to_string() -> string => | |
| 209 | "stack-top:[{type}]" | |
| 210 | si | |
| 211 | ||
| 212 | // Like STACK_TOP but reports `has_address` - the IL stack | |
| 213 | // already holds a managed pointer (e.g. the dup'd address of a | |
| 214 | // value-type optional receiver). The struct-aware load paths | |
| 215 | // gate on `has_address`, so an `INSTANCE_FIELD` / property | |
| 216 | // built against this stands in for the implicit `ADDRESS` step | |
| 217 | // and emits only its trailing op. | |
| 218 | class STACK_TOP_ADDRESS: Value is | |
| 219 | _type: Type? | |
| 220 | ||
| 221 | type: Type? => _type | |
| 222 | is_value_type: bool => _type? /\ _type.is_value_type | |
| 223 | has_address: bool => true | |
| 224 | is_lightweight_pure: bool => true | |
| 225 | ||
| 226 | init(type: Type) is | |
| 227 | super.init() | |
| 228 | self._type = type | |
| 229 | si | |
| 230 | ||
| 231 | gen(context: IR.CONTEXT) is | |
| 232 | si | |
| 233 | ||
| 234 | gen_address(context: IR.CONTEXT) is | |
| 235 | si | |
| 236 | ||
| 237 | to_string() -> string => | |
| 238 | "stack-top-address:[{type}]" | |
| 239 | si | |
| 240 | si |