Skip to content
← Back

src/ir/values/coalesce_load.ghul

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