Skip to content
← Back

src/syntax/process/generate-il/generate_il_locals.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
// Local-variable IL: assignments, variable visits, refutable bindings and destructuring.
18
partial GENERATE_IL is
19
visit(assign: Statements.ASSIGNMENT) is
20
super.visit(assign)
21
22
let v = assign.value
23
if v? then
24
add(v)
25
fi
26
si
27
28
visit(expression: Statements.EXPRESSION) is
29
super.visit(expression)
30
31
try
32
if !expression.want_value /\ expression.expression.value? then
33
let value = expression.expression.value
34
35
// `NULL.matches` answers true for every type, so a
36
// null-typed value reads as void here. It is not: emit
37
// it and pop, as for any other discarded statement
38
// value.
39
let is_void =
40
value.type? /\
41
!value.type.is_null /\
42
value.type.matches(_innate_symbol_lookup.get_void_type())
43
44
// Bare `await E;` with void result: visit(AWAIT)
45
// emitted the suspend eagerly; re-emitting the
46
// wrapper would call gen on its unreplaced DUMMY.
47
if !is_void \/ !isa Values.WRAPPER(value) then
48
add(value)
49
fi
50
51
if !is_void then
52
add(Values.INSTRUCTION(ILOpCode.POP))
53
fi
54
fi
55
catch e: System.Exception
56
_logger.exception(expression.location, e, "caught exception generating IL for expression")
57
yrt
58
si
59
60
// A local's declaration is a slot in the body's signature, so
61
// it is only a local at all inside a block. Answers whether
62
// the symbol was taken as one.
63
_declare_local(symbol: Semantic.Symbols.Symbol?) -> bool is
64
if !_block_context.is_in_block then
65
return false
66
fi
67
68
if let local: Semantic.Symbols.LOCAL_VARIABLE = symbol /\ local.type? then
69
current_block.add(Values.DECLARE_LOCAL(local.il_name, local.storage_type!))
70
fi
71
72
return true
73
si
74
75
pre(variable: Variables.VARIABLE) -> bool is
76
super.pre(variable)
77
78
if variable.want_dispose then
79
// variable will be declared outside the enclosing .try
80
// so don't declare it here
81
return false
82
fi
83
84
// Generator / async body locals: `state_machine_field`
85
// was wired up in compile-expressions (so closures
86
// freezing inside the body capture the right
87
// ldarg.0/ldfld IL). Skip emitting `.locals init` here;
88
// load/store routes through the frame field via
89
// state_machine_field.
90
let cf = current_function
91
if cf? then
92
let sm_for_function = Semantic.Symbols.state_machine_for(cf)
93
94
if sm_for_function? /\ sm_for_function.frame? then
95
return false
96
fi
97
98
let async_sm = Semantic.Symbols.async_state_machine_for(cf)
99
100
if async_sm? /\ async_sm.frame? then
101
return false
102
fi
103
fi
104
105
// A local's declaration is a value in the block: metadata
106
// has no `.locals init` directive, and the slot it
107
// allocates has to be in place before any load of the
108
// same name is encoded. A declaration at namespace or
109
// class scope is a field, whose row the structure walk
110
// writes, so nothing is emitted for one here.
111
if _block_context.is_in_block then
112
for name in variable.names do
113
_declare_local(find(name))
114
od
115
fi
116
117
return false
118
si
119
120
visit(v: Variables.VARIABLE) is
121
let initializer = v.initializer
122
if initializer? then
123
let init_value = initializer.value
124
if init_value? then
125
gen_destructuring_initialize(v.left, init_value)
126
return
127
fi
128
fi
129
130
// Destructured formal argument: no initializer expression -
131
// the "source" to destructure is the synthesised physical
132
// parameter declare-members attached to this VARIABLE.
133
if v.is_argument /\ !v.left.is_simple_name then
134
let group_symbol = symbol_for(v)
135
136
if group_symbol? then
137
gen_destructuring_initialize(v.left, group_symbol.load(v.location, null, _symbol_loader))
138
fi
139
140
return
141
fi
142
143
// No initializer, but the local is captured by a closure
144
// and reassigned, so `mark-boxed-locals` promoted its slot
145
// to `Ghul.BOX[T]`. Allocate the empty box at declaration —
146
// `Variable.store` routes through `_store_boxed_local`,
147
// which emits `newobj Ghul.BOX[T]::.ctor()` when the value
148
// is null. Without this the slot stays null, then the
149
// closure frame's ctor stashes a null box reference and
150
// every later read or write NREs.
151
if v.left.is_simple_name then
152
// is_simple_name => SIMPLE_VARIABLE_LEFT, whose name is non-null
153
let name = v.left.name!
154
let symbol = find(name)
155
156
if let variable: Semantic.Symbols.LOCAL_VARIABLE = symbol /\ variable.is_boxed then
157
add(variable.store_empty_box(v.left.location, _symbol_loader))
158
return
159
fi
160
fi
161
162
// Loop-scoped `let x: T;` (no initializer) needs an
163
// explicit default store: .NET only initialises locals
164
// on method entry, so without re-zeroing each iteration
165
// the variable carries its previous iteration's value
166
// (issue #483). Outside a loop, the method-entry
167
// initialisation is correct and we leave the existing
168
// behaviour alone. Catch handlers receive their value
169
// from the runtime — skip the default-store there too.
170
if _loops.is_in_loop /\ !_in_catch_variable then
171
gen_destructuring_default(v.left)
172
fi
173
si
174
175
// `if let` clauses' bound names are declared as CLR locals
176
// here — same as `pre(VARIABLE)` does for a plain let. The
177
// store IL itself is emitted later by
178
// `gen_destructuring_initialize` in the if-branch handler.
179
pre(rb: Statements.REFUTABLE_BINDING) -> bool is
180
let cf = current_function
181
if cf? then
182
let sm_for_function = Semantic.Symbols.state_machine_for(cf)
183
184
if sm_for_function? /\ sm_for_function.frame? then
185
return false
186
fi
187
188
let async_sm = Semantic.Symbols.async_state_machine_for(cf)
189
190
if async_sm? /\ async_sm.frame? then
191
return false
192
fi
193
fi
194
195
for c in rb.clauses do
196
for name in c.pattern.names! do
197
_declare_local(find(name))
198
od
199
od
200
201
return false
202
si
203
204
visit(rb: Statements.REFUTABLE_BINDING) is
205
si
206
207
gen_destructuring_default(left: Variables.VariableLeft) is
208
if left.is_simple_name then
209
// is_simple_name => SIMPLE_VARIABLE_LEFT, whose name is non-null
210
let name = left.name!
211
let symbol = find(name)
212
213
if !symbol? \/ symbol.is_argument then
214
return
215
fi
216
let symbol_type = symbol.type
217
if !symbol_type? then return; fi
218
219
let default_value = IR.Values.DEFAULT(symbol_type)
220
221
let store = symbol.store(left.location, null, default_value, _symbol_loader, true)
222
223
add(store)
224
else
225
for e in left.elements! do
226
gen_destructuring_default(e)
227
od
228
fi
229
si
230
231
// TODO use Pre/Visit instead
232
gen_destructuring_initialize(left: Variables.VariableLeft, value: Value) is
233
gen_destructuring_initialize(left, value, null)
234
si
235
236
// Per-element narrowing: when `narrow_branch_label` is set
237
// (the `if let` path), an element carrying a `type_expression`
238
// emits `isinst T; brfalse next` before binding. The cast
239
// result is what gets stored / recursively destructured, so
240
// the bound slot is typed T. Plain `let` and `for` pass null
241
// for the label and just store the raw value (the declared
242
// element type is a compile-time assertion only, not a runtime
243
// test).
244
gen_destructuring_initialize(
245
left: Variables.VariableLeft,
246
value: Value,
247
narrow_branch_label: LABEL?
248
) is
249
// Literal-leaf inside a destructure pattern — runtime
250
// value-equality test against the source position. Only
251
// valid in refutable contexts (`if let` / `case`-when);
252
// a plain `let` with a literal leaf is rejected during
253
// semantic analysis, so by the time we get here a literal
254
// leaf without a narrow_branch_label is a no-op (the
255
// emit-nothing path).
256
if isa Variables.LITERAL_VARIABLE_LEFT(left) then
257
if narrow_branch_label? then
258
let literal_leaf = cast Variables.LITERAL_VARIABLE_LEFT(left)
259
let leaf_state = _variable_left_state.get(literal_leaf)
260
261
// The value-equality test compile-expressions built
262
// for this leaf embeds the literal and reads the
263
// source position through a hole; fill the hole and
264
// branch on the result. The test can read the
265
// operand more than once - a null-safe `=~` tests
266
// presence before dispatching - and an instance
267
// operator on a value type needs its address, so
268
// spill anything that is not already a local.
269
if let
270
test = leaf_state?.match_test,
271
operand = leaf_state?.match_operand
272
then
273
operand.value =
274
if value.is_lightweight_pure /\ value.has_address then
275
value
276
else
277
TEMP(current_block, "literal-leaf", value).load()
278
fi
279
280
get_brancher_for_block().branch(
281
BRANCH.Z,
282
test,
283
narrow_branch_label,
284
"literal-leaf"
285
)
286
287
return
288
fi
289
290
// No test: a `null` leaf, or a type for which
291
// neither `=~` nor `<>` resolved. Compare raw, which
292
// is what a bare `==` does too.
293
literal_leaf.expression.walk(self)
294
295
let leaf_value = literal_leaf.expression.value
296
if leaf_value? then
297
get_brancher_for_block().branch(
298
BRANCH.NE,
299
value,
300
leaf_value,
301
narrow_branch_label,
302
"literal-leaf"
303
)
304
fi
305
fi
306
307
return
308
fi
309
310
let narrowed mut = value
311
312
let type_expression = left.type_expression
313
if
314
type_expression? /\
315
type_expression.type? /\
316
narrow_branch_label?
317
then
318
let cast_value = IR.Values.CAST(type_expression.type, value, false)
319
let temp = TEMP(current_block, "narrow", cast_value)
320
321
get_brancher_for_block().branch(BRANCH.Z, temp.load(), narrow_branch_label, "narrow")
322
323
narrowed = temp.load()
324
fi
325
326
if left.is_simple_name then
327
// is_simple_name => SIMPLE_VARIABLE_LEFT, whose name is non-null
328
let name = left.name!
329
let symbol = find(name)
330
331
if !symbol? then
332
return
333
fi
334
335
let store = symbol.store(left.location, null, narrowed, _symbol_loader, true)
336
337
add(store)
338
else
339
let from_type = narrowed.type
340
341
if !from_type? then return; fi
342
343
// !is_simple_name => a destructure group, whose elements are non-null
344
let elements = left.elements!
345
346
let get_from =
347
if narrowed.is_lightweight_pure then
348
() => narrowed
349
else
350
let temp = TEMP(current_block, "destructure", narrowed)
351
() => temp.load()
352
fi
353
354
let is_named_group = elements.count > 0 /\ elements[0].source_field_name?
355
356
let strategy =
357
if is_named_group then
358
let field_names = Collections.LIST[string?]()
359
for e in elements do
360
let sfn = e.source_field_name
361
field_names.add(if sfn? then sfn.name else null fi)
362
od
363
DESTRUCTURE_RESOLVER.resolve_strategy_by_name(from_type, field_names)
364
else
365
DESTRUCTURE_RESOLVER.resolve_strategy(from_type, elements.count)
366
fi
367
368
if strategy.is_deconstruct then
369
// is_deconstruct ⇔ deconstruct_function? per its definition
370
let deconstruct = strategy.deconstruct_function!
371
let arg_temps = Collections.LIST[IR.TEMP]()
372
let call_args = Collections.LIST[Value]()
373
374
for i in 0..deconstruct.arguments.count do
375
let ref_type = deconstruct.arguments[i]
376
let element_type = ref_type.get_element_type()
377
assert element_type? else "deconstruct arg type has no element type"
378
let arg_temp = IR.TEMP(current_block, "destructure_arg", i, element_type)
379
380
arg_temps.add(arg_temp)
381
call_args.add(IR.Values.ADDRESS(arg_temp.load(), ref_type))
382
od
383
384
let call_value =
385
deconstruct.call(
386
left.location,
387
get_from(),
388
call_args,
389
null,
390
_function_caller
391
)
392
393
add(call_value)
394
395
for (i, e) in elements |> index() do
396
gen_destructuring_initialize(e, arg_temps[i].load(), narrow_branch_label)
397
od
398
else
399
let members = strategy.members
400
401
for (i, e) in elements |> index() do
402
let member = members[i]
403
404
if member? then
405
let member_value = member.load(LOCATION.internal, get_from(), _symbol_loader)
406
407
gen_destructuring_initialize(e, member_value, narrow_branch_label)
408
fi
409
od
410
fi
411
fi
412
si
413
414
si
415
si