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