Skip to content
← Back

src/ir/value_boxer.ghul

1
namespace IR is
2
use Semantic.Types.Type
3
4
use IR.Values.Value
5
6
class VALUE_BOXER(_logger: Logging.Logger) is
7
want_boxing: bool public
8
9
super()
10
11
box_if_value(value: Value) -> Value =>
12
if want_boxing /\ value.is_value_type then
13
Values.BOX(value)
14
else
15
value
16
fi
17
18
box_if_needed(value: Value, target_type: Type) -> Value is
19
// Try the T → T? wrap first — covers the value-type
20
// optional widening (Nullable<T>::.ctor at the slot
21
// boundary). The target stays a value type, so no
22
// subsequent BOX fires.
23
let wrapped = wrap_if_needed(value, target_type)!
24
25
if wrapped != value then
26
return wrapped
27
fi
28
29
if want_boxing /\ value.is_value_type /\ !target_type.is_value_type then
30
return Values.BOX(value)
31
else
32
return value
33
fi
34
si
35
36
// Implicit T → T? widening at slot boundaries. A non-optional
37
// value-type T flowing into a value-type optional target gets
38
// wrapped via Nullable<T>::.ctor — the value-type analogue of
39
// the box-on-assignment-to-object path. Reference T → T? needs
40
// no wrap (the bits are identical, only the type-system flag
41
// differs) so this helper only fires when the target is a
42
// value-type optional.
43
wrap_if_needed(value: Value?, target_type: Type?) -> Value? is
44
if !value? \/ !target_type? \/ !value.type? then
45
return value
46
fi
47
48
if let repacked = _repack_tuple(value, target_type) then
49
return repacked
50
fi
51
52
if !value.type? then
53
return value
54
fi
55
56
// MAYBE[T] → reference T?: `MAYBE[T].value` throws when
57
// absent (it is a checked unwrap in its own right), so the
58
// coercion has to test `has_value` first and only read
59
// `value` on the present path — mirroring the `??`
60
// value-shape lowering in COMPILE_OPERATORS. The absent
61
// arm is a bare `null`: a reference T? represents absence
62
// that way, with no exception involved.
63
if
64
value.type.is_maybe /\
65
target_type.is_optional /\
66
!target_type.is_value_type
67
then
68
let has_value_member = value.type!.find_member("has_value")
69
let value_member = value.type!.find_member("value")
70
71
if has_value_member? /\ value_member? then
72
let symbol_loader = IoC.CONTAINER.instance.symbol_loader
73
let address_stand_in = Values.STACK_TOP_ADDRESS(value.type!)
74
75
let presence_test = has_value_member.load(value.location, address_stand_in, symbol_loader)
76
let value_extract = value_member.load(value.location, address_stand_in, symbol_loader)
77
78
// The payload reaches a reference target, so a
79
// value-type payload — or a type-variable one that
80
// may instantiate as a value type — arrives boxed;
81
// the extract alone would leave a bare struct on
82
// the stack where the slot expects a reference.
83
let payload_type = value_extract.type
84
85
let present_arm: Values.Value =
86
if payload_type? /\ (payload_type.is_value_type \/ payload_type.is_type_variable) then
87
Values.BOX(Values.STACK_TOP(payload_type))
88
else
89
Values.STACK_TOP(target_type)
90
fi
91
92
return Values.NULL_COALESCE_VALUE(
93
value,
94
presence_test,
95
value_extract,
96
present_arm,
97
Values.DEFAULT(target_type),
98
target_type
99
)
100
fi
101
fi
102
103
if !target_type.is_value_type \/ !target_type.is_optional then
104
return value
105
fi
106
107
// A bare `null` flowing into a value-type optional slot is the
108
// empty optional — a zeroed value, not a null reference. Lower
109
// it like `default`, mirroring the `null`-into-NULLABLE path in
110
// compile_expressions. The constraint that would otherwise drive
111
// that lowering does not reach a call argument, so the coercion
112
// happens here at the boxing boundary instead.
113
if value.type!.is_null then
114
return Values.DEFAULT(target_type)
115
fi
116
117
// Any other `T?` lowering flowing into a MAYBE[T] slot.
118
// The three optional carriers are intercompatible in the
119
// type system but distinct CLR types, so the coercion is
120
// explicit: test the source for presence, and either wrap
121
// its payload with MAYBE<T>::.ctor or leave the empty
122
// MAYBE. A reference source needs no local — the null test
123
// is a dup-and-branch on the value itself.
124
if target_type.is_maybe /\ !target_type.is_equivalent_to(value.type!) then
125
if value.type!.is_optional /\ !value.type!.is_value_type then
126
return Values.WRAP_MAYBE(target_type, value)
127
fi
128
129
if value.type!.is_optional then
130
let has_value_member = value.type!.find_member("has_value")
131
let value_member = value.type!.find_member("value")
132
133
if has_value_member? /\ value_member? then
134
let symbol_loader = IoC.CONTAINER.instance.symbol_loader
135
let address_stand_in = Values.STACK_TOP_ADDRESS(value.type!)
136
137
let presence_test = has_value_member.load(value.location, address_stand_in, symbol_loader)
138
let value_extract = value_member.load(value.location, address_stand_in, symbol_loader)
139
140
return Values.NULL_COALESCE_VALUE(
141
value,
142
presence_test,
143
value_extract,
144
Values.WRAP_OPTIONAL(target_type, Values.STACK_TOP(value_extract.type!)),
145
Values.DEFAULT(target_type),
146
target_type
147
)
148
fi
149
fi
150
fi
151
152
if value.type!.is_optional then
153
return value
154
fi
155
156
// Value-type optional — source-side NULLABLE or a reflected
157
// Nullable<T> wrapper; both are GENERICs carrying the inner
158
// T as their single argument.
159
if isa Semantic.Types.GENERIC(target_type) then
160
let optional = target_type
161
162
if optional.arguments.count != 1 then
163
return value
164
fi
165
166
if !optional.arguments[0].is_assignable_from(value.type!) then
167
return value
168
fi
169
170
return Values.WRAP_OPTIONAL(target_type, value)
171
fi
172
173
return value
174
si
175
176
// A tuple is assignable to a tuple type whose elements each
177
// accept its own, but the CLR type is invariant: the target is
178
// rebuilt from the source's elements, each converted in turn.
179
_repack_tuple(value: Value, target_type: Type) -> Value? is
180
if
181
let source = value.type /\
182
(source.is_value_tuple \/ isa Semantic.Types.TUPLE(source)) /\
183
(target_type.is_value_tuple \/ isa Semantic.Types.TUPLE(target_type))
184
then
185
let target = target_type
186
187
if
188
source.is_optional \/
189
target.is_optional \/
190
source.arguments.count != target.arguments.count \/
191
target.is_equivalent_to(source)
192
then
193
return null
194
fi
195
196
// A tuple literal is built at the target type directly from its
197
// own element values. Spilling it first would build the source
198
// tuple, whose element types - a bare `null`, say - may have no
199
// runtime representation.
200
if let rebuilt = _rebuild_tuple_literal(value, target) then
201
return rebuilt
202
fi
203
204
let symbol_loader = IoC.CONTAINER.instance.symbol_loader
205
let temp_name = ".repack.{IR.TEMP.get_next_id()}"
206
let elements = Collections.LIST[Value]()
207
208
for i in 0..source.arguments.count do
209
let member = source.find_destructure_member(i)
210
211
if !member? then
212
return null
213
fi
214
215
let element = member.load(value.location, Values.Load.TEMP(temp_name, source), symbol_loader)
216
217
elements.add(box_if_needed(element, target.arguments[i]))
218
od
219
220
return Values.TUPLE_REPACK(value, temp_name, source, Values.TUPLE(target, elements))
221
fi
222
223
return null
224
si
225
226
// `value` rebuilt at `target` when it is a tuple literal, or a block
227
// whose result is one, as an `if` or `case` arm is; null otherwise.
228
_rebuild_tuple_literal(value: Value, target: Type) -> Value? is
229
if let literal: Values.TUPLE = value then
230
if literal.values.count != target.arguments.count then
231
return null
232
fi
233
234
let converted = Collections.LIST[Value]()
235
236
for i in 0..literal.values.count do
237
converted.add(box_if_needed(literal.values[i], target.arguments[i]))
238
od
239
240
return Values.TUPLE(target, converted)
241
fi
242
243
if let block: Values.BLOCK = value, result = block.result_value then
244
if let rebuilt_result = _rebuild_tuple_literal(result, target) then
245
let rebuilt = Values.BLOCK(target)
246
247
for i in 0..block.values.count - 1 do
248
rebuilt.add(block.values[i])
249
od
250
251
rebuilt.add(rebuilt_result)
252
253
return rebuilt
254
fi
255
fi
256
257
return null
258
si
259
260
box_arguments(arguments: Collections.List[Value], argument_types: Collections.List[Type]) -> Collections.List[Value] is
261
if arguments.count != argument_types.count then
262
_logger.poison(Source.LOCATION.internal, "boxed incomplete arguments")
263
return arguments
264
fi
265
266
let any_need_change mut = false
267
268
for i in 0..arguments.count do
269
270
// Apply the wrap first — a value-typed source that
271
// coerces to a reference-typed target (e.g. MAYBE[T] →
272
// T?) emerges from `wrap_if_needed` as the .value
273
// reference, so the boxing branch below must see the
274
// already-coerced value or it boxes the wrong thing.
275
let wrapped = wrap_if_needed(arguments[i], argument_types[i])!
276
277
if wrapped != arguments[i] then
278
any_need_change = true
279
break
280
fi
281
282
if want_boxing /\ !argument_types[i].is_value_type /\ wrapped.type!.is_value_type then
283
any_need_change = true
284
break
285
fi
286
od
287
288
if !any_need_change then
289
return arguments
290
fi
291
292
let result = Collections.LIST[Value](arguments.count)
293
294
for i in 0..arguments.count do
295
let wrapped = wrap_if_needed(arguments[i], argument_types[i])!
296
297
if want_boxing /\ !argument_types[i].is_value_type /\ wrapped.type!.is_value_type then
298
result.add(Values.BOX(wrapped))
299
else
300
result.add(wrapped)
301
fi
302
od
303
304
return result
305
si
306
si
307
si