Skip to content
← Back

src/syntax/process/generate-il/generate_il_expressions.ghul

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
// Expression IL: call/await/spill/cast walks, await-suspend construction and string interpolation.
18
partial GENERATE_IL is
19
// Override CALL.walk's default args-first order so SPILLs in
20
// the receiver position emit before AWAIT_SUSPENDs in the
21
// argument position — needed for source-order side effects
22
// across instance-call receivers.
23
pre(call: Trees.Expressions.CALL) -> bool is
24
super.pre(call)
25
26
call.function.walk(self)
27
call.arguments.walk(self)
28
29
return true
30
si
31
32
visit(`await: Trees.Expressions.AWAIT) is
33
// Emit AWAIT_SUSPEND eagerly, stash the result into a
34
// fresh frame field (NOT a CLR local — CLR locals reset
35
// on every MoveNext re-entry), and rebind the wrapper to
36
// a `ldfld <field>` load. The CLR-local TEMP is a
37
// one-MoveNext bridge: we can't `ldarg.0` before
38
// AWAIT_SUSPEND (would push across its internal labels),
39
// so the suspend's result goes to a tmp first, then
40
// `ldarg.0 / ldloc / stfld`.
41
let await_wrapper = cast Values.WRAPPER?(`await.value)
42
43
if !await_wrapper? then
44
return
45
fi
46
47
let casm = _current_async_state_machine
48
49
if !casm? \/ current_function != casm.function then
50
_logger.error(`await.location, "await outside async state-machine context")
51
return
52
fi
53
54
let operand_value = `await.operand.value
55
if !operand_value? then return; fi
56
let operand_type = operand_value.type
57
if !operand_type? then return; fi
58
59
let awaitable = `await.awaitable
60
if !awaitable? then return; fi
61
62
let suspend =
63
_build_await_suspend(`await.location, operand_value, awaitable, `await.awaiter_field)
64
65
let frame = casm.frame
66
67
assert frame? else "async state machine has no frame at await emission"
68
69
let suspend_type = suspend.type
70
71
if !suspend_type.is_void then
72
let result_field = `await.result_field
73
74
assert result_field? else "await has no result field at IL generation"
75
76
let result_temp = TEMP(current_block, "await_bridge", suspend)
77
78
add(_build_frame_field_store(frame, result_field, result_temp.load()))
79
80
await_wrapper.value =
81
IR.Values.Load.INSTANCE_FIELD(
82
IR.Values.Load.REFERENCE_SELF(frame, frame.type),
83
result_field
84
)
85
else
86
// void-async — emit the suspend IL eagerly. The wrapper
87
// already holds a void-typed DUMMY which surrounding
88
// emit paths must skip (the type check in
89
// visit(Statements.EXPRESSION) handles the only place
90
// this matters in practice — bare `await E;`).
91
add(suspend)
92
fi
93
si
94
95
visit(spill: Trees.Expressions.SPILL) is
96
// Eagerly emit the operand's IL, then `stfld` to a fresh
97
// frame field. The wrapper's value becomes a
98
// `ldarg.0; ldfld <field>` load that surrounding IR
99
// consumes lazily. Used by the SPILL_AWAITS pass to
100
// persist intermediate values evaluated to the left of an
101
// `await` so they survive the suspend.
102
let spill_wrapper = cast Values.WRAPPER?(spill.value)
103
104
if !spill_wrapper? then
105
return
106
fi
107
108
let casm = _current_async_state_machine
109
110
if !casm? \/ current_function != casm.function then
111
_logger.error(spill.location, "SPILL outside async state-machine context")
112
return
113
fi
114
115
let operand_value = spill.operand.value
116
if !operand_value? then return; fi
117
118
let frame = casm.frame
119
120
assert frame? else "async state machine has no frame at spill emission"
121
122
let spill_field = spill.spill_field
123
124
assert spill_field? else "spill has no frame field at IL generation"
125
126
add(_build_frame_field_store(frame, spill_field, operand_value))
127
128
spill_wrapper.value =
129
IR.Values.Load.INSTANCE_FIELD(
130
IR.Values.Load.REFERENCE_SELF(frame, frame.type),
131
spill_field
132
)
133
si
134
135
visit(`cast: Trees.Expressions.CAST) is
136
let cast_wrapper = cast Values.WRAPPER?(`cast.value)
137
138
if !cast_wrapper? then return; fi
139
140
let right_value = `cast.right.value
141
if !right_value? then return; fi
142
let cast_type = cast_wrapper.type
143
if !cast_type? then return; fi
144
145
// Use the WRAPPER's type, not `cast.type_expression.type`
146
// — for a bare-variant cast on a generic union (a `cast V(x)`
147
// or the `cast` produced by `if let v: V = x` lowering),
148
// compile_expressions.visit(CAST) already substituted the
149
// receiver-specialized variant type, but the user's
150
// type_expression still reads as the open generic.
151
cast_wrapper.value =
152
_type_caster
153
.cast_value(
154
`cast.location,
155
right_value,
156
cast_type,
157
true,
158
`cast.is_checked
159
)
160
si
161
162
// Build an AWAIT_SUSPEND for `awaited`: allocates a state
163
// number + cold/hot resume labels + a fresh `$awaiter_N`
164
// field, and records the (state, awaiter, label) triple
165
// for entry-dispatch population.
166
//
167
// The three member calls the suspend makes are built as
168
// ordinary call values, so a struct awaitable or awaiter is
169
// addressed and a class one dispatched exactly as a call
170
// written in source would be.
171
_build_await_suspend(
172
location: Source.LOCATION,
173
awaited: Value,
174
awaitable: Semantic.AWAITABLE,
175
declared_awaiter_field: Semantic.Symbols.Field?
176
) -> IR.Values.AWAIT_SUSPEND is
177
let async_sm = _current_async_state_machine!
178
let frame = async_sm.frame
179
180
assert frame? else "async state machine has no frame at await suspend emission"
181
182
let awaiter_type = awaitable.awaiter_type
183
let state = async_sm.allocate_state()
184
let cold_resume = IR.LABEL()
185
let hot_resume = IR.LABEL()
186
let awaiter_field =
187
declared_awaiter_field ?? frame.declare_awaiter_field(awaiter_type)
188
189
async_sm.record_label(state, awaiter_field, cold_resume)
190
191
_register_resume_with_dispatch(state, cold_resume)
192
193
let frame_type =
194
_specialized_frame_type(frame, async_sm.get_construction_type_arguments(), location) ?? frame.type!
195
196
let no_arguments = Collections.LIST[Value]()
197
198
let get_awaiter =
199
awaitable.get_awaiter.call(location, awaited, no_arguments, null, _function_caller)
200
201
let awaiter_load = () =>
202
IR.Values.Load.INSTANCE_FIELD(
203
IR.Values.Load.REFERENCE_SELF(frame, frame_type),
204
awaiter_field
205
)
206
207
let is_completed =
208
awaitable.is_completed.call(location, awaiter_load(), no_arguments, null, _function_caller)
209
210
let get_result =
211
awaitable.get_result.call(location, awaiter_load(), no_arguments, null, _function_caller)
212
213
return IR.Values.AWAIT_SUSPEND(
214
awaitable.result_type,
215
get_awaiter,
216
is_completed,
217
get_result,
218
awaiter_type,
219
awaitable.is_critical,
220
state,
221
cold_resume,
222
hot_resume,
223
_current_async_end_label!,
224
frame_type,
225
frame.state_field,
226
awaiter_field,
227
frame.builder_field!
228
)
229
si
230
231
visit(interpolation: Expressions.STRING_INTERPOLATION) is
232
ensure_runtime_symbols_are_materialized()
233
234
enter_block(cast IR.Values.BLOCK?(interpolation.value)!)
235
236
let string_type = _innate_symbol_lookup.get_string_type()
237
238
let literal_length = Literal.NUMBER(interpolation.literal_length, _innate_symbol_lookup.get_int_type())
239
let expression_count = Literal.NUMBER(interpolation.expression_count, _innate_symbol_lookup.get_int_type())
240
241
let interpolator =
242
TEMP(
243
current_block,
244
"interpolator",
245
IR.Values.NEW(
246
_interpolation_handler!,
247
_constructor, [
248
literal_length,
249
expression_count
250
]:Value
251
)
252
)
253
254
for eaf in interpolation.values do
255
let expression = eaf.expression
256
let alignment = eaf.alignment
257
let format = eaf.format
258
let expr_value = expression.value!
259
260
let args = Collections.LIST[Value](1)
261
262
args.add(expr_value)
263
264
if !eaf.is_expression then
265
let literal = cast Expressions.Literals.STRING?(expression)!
266
267
if literal.value_string.length == 0 then
268
continue
269
fi
270
271
let call = _append_literal.call(interpolation.location, interpolator.load(), args, null, _function_caller)
272
273
add(call)
274
elif !format? /\ !alignment? then
275
_append_interpolated_value(interpolation.location, interpolator, expr_value)
276
else
277
let append_formatted: Semantic.Symbols.Symbol mut
278
let args = Collections.LIST[Value](3)
279
280
args.add(expr_value)
281
282
if format? /\ alignment? then
283
args.add(alignment.value!)
284
args.add(Literal.STRING(format, string_type))
285
286
append_formatted = _append_formatted_generic_alignment_format.specialize([expr_value.type!])
287
288
elif format? then
289
args.add(Literal.STRING(format, string_type))
290
291
append_formatted = _append_formatted_generic_format.specialize([expr_value.type!])
292
else
293
args.add(alignment!.value!)
294
295
append_formatted = _append_formatted_generic_alignment.specialize([expr_value.type!])
296
fi
297
298
let call = append_formatted.call(interpolation.location, interpolator.load(), args, null, _function_caller)
299
300
add(call)
301
fi
302
od
303
304
let result = _to_string_and_clear.call(interpolation.location, interpolator.load(), System.Array.empty`[Value](), null, _function_caller)
305
add(result)
306
307
leave_block()
308
si
309
// Append an interpolated value given no format or alignment, as
310
// INTERPOLATED_VALUE_CLASSIFIER says it reads.
311
_append_interpolated_value(location: Source.LOCATION, interpolator: TEMP, value: Value) is
312
let type = value.type!
313
314
case _interpolated_value_classifier.classify(type, false)
315
when Semantic.InterpolatedValueRendering.BOOL then
316
_append_bool(location, interpolator, value)
317
318
when Semantic.InterpolatedValueRendering.OPTIONAL then
319
_append_optional(location, interpolator, value)
320
321
when Semantic.InterpolatedValueRendering.DISPLAYED then
322
if let display = _display_function then
323
_append_formatted(location, interpolator, display.call(location, null, [value]:Value, null, _function_caller))
324
else
325
_append_formatted(location, interpolator, value)
326
fi
327
328
else
329
_append_formatted(location, interpolator, value)
330
esac
331
si
332
333
_append_formatted(location: Source.LOCATION, interpolator: TEMP, value: Value) is
334
let append = _append_formatted_generic.specialize([value.type!])
335
336
add(append.call(location, interpolator.load(), [value]:Value, null, _function_caller))
337
si
338
339
_append_text(location: Source.LOCATION, interpolator: TEMP, text: string) is
340
add(_append_literal.call(location, interpolator.load(), [Literal.STRING(text, _innate_symbol_lookup.get_string_type())]:Value, null, _function_caller))
341
si
342
343
_append_bool(location: Source.LOCATION, interpolator: TEMP, value: Value) is
344
let brancher = get_brancher_for_block()
345
let is_false = LABEL()
346
let done = LABEL()
347
348
brancher.branch(IR.BRANCH.Z, value, is_false)
349
_append_text(location, interpolator, "true")
350
brancher.branch(done)
351
352
brancher.label(is_false)
353
_append_text(location, interpolator, "false")
354
355
brancher.label(done)
356
si
357
358
// Append what an optional holds, by the rules for its non-optional
359
// type, or `null` when it holds nothing.
360
_append_optional(location: Source.LOCATION, interpolator: TEMP, value: Value) is
361
let type = value.type!
362
let inner = type.optional_inner_type!
363
let brancher = get_brancher_for_block()
364
let absent = LABEL()
365
let done = LABEL()
366
367
if type.is_value_type then
368
let has_value = type.find_member("has_value")
369
let payload = type.find_member("value")
370
371
if !has_value? \/ !payload? then
372
_append_formatted(location, interpolator, value)
373
374
return
375
fi
376
377
let holder = TEMP(current_block, "optional", value)
378
let symbol_loader = IoC.CONTAINER.instance.symbol_loader
379
380
brancher.branch(IR.BRANCH.Z, has_value.load(location, IR.Values.ADDRESS(holder.load()), symbol_loader), absent)
381
_append_interpolated_value(location, interpolator, payload.load(location, IR.Values.ADDRESS(holder.load()), symbol_loader))
382
else
383
let holder = TEMP(current_block, "optional", inner)
384
385
holder.store(value)
386
387
brancher.branch(IR.BRANCH.Z, holder.load(), absent)
388
_append_interpolated_value(location, interpolator, holder.load())
389
fi
390
391
brancher.branch(done)
392
393
brancher.label(absent)
394
_append_text(location, interpolator, "null")
395
396
brancher.label(done)
397
si
398
si
399
si