Skip to content
← Back

src/syntax/process/generate-il/generate_il_conditionals.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
// Conditional IL: if-let clause tests and binds, if statements, case and match arms, and null tests.
18
partial GENERATE_IL is
19
// Emit the scrutinee evaluation, optional isinst cast, the
20
// presence test, and the branch-to-fail for one `if let`
21
// clause. Returns the temp holding the (possibly cast)
22
// scrutinee value so the caller can bind the clause's
23
// pattern from it; returns null when there is nothing to
24
// bind (a missing scrutinee value, defensive).
25
_emit_if_let_clause_test(
26
c: Statements.REFUTABLE_BINDING_CLAUSE,
27
fail: LABEL,
28
is_first: bool,
29
brancher: BLOCK_BRANCHER
30
) -> TEMP? is
31
c.scrutinee.walk(self)
32
33
if !c.scrutinee.value? then
34
return null
35
fi
36
37
let scrutinee_value = c.scrutinee.value
38
let typed_value: Value mut = scrutinee_value
39
40
if let c.narrow_type_expression?, narrow_type_expression.type? then
41
// Test against the narrow type compile-expressions
42
// stamped on the pattern: it is the written type
43
// specialised against the scrutinee, so a variant of a
44
// generic union carries its type arguments. The written
45
// form names the open generic, which is not a loadable
46
// `isinst` operand. Falls back to the written form for
47
// a clause compile-expressions left unstamped.
48
let narrow_target =
49
PATTERN_CHECKER.runtime_test_type(
50
_variable_left_state.get(c.pattern)?.narrow_type ?? type,
51
scrutinee_value.type,
52
_innate_symbol_lookup
53
)
54
55
// An optional source already of the test type needs no
56
// conversion, and the checked one would unwrap it when
57
// absent. A reference scrutinee read at its narrowed type
58
// still needs the runtime test the cast carries.
59
if !narrow_target.is_value_type \/ !(scrutinee_value.type?.matches(narrow_target) ?? false) then
60
typed_value =
61
_type_caster.cast_value(
62
c.location,
63
scrutinee_value,
64
narrow_target,
65
true,
66
false
67
)
68
fi
69
fi
70
71
let temp = TEMP(current_block, "if_let", typed_value)
72
73
let init_type = typed_value.type!
74
75
// The presence test: a reference type is absent when
76
// null; an option-shaped value type is absent when its
77
// `has_value` member is false.
78
let presence: Value? mut = null
79
80
if init_type.is_value_type then
81
let has_value_member = init_type.find_member("has_value")
82
83
if has_value_member? then
84
presence = has_value_member.load(LOCATION.internal, temp.load(), _symbol_loader)
85
fi
86
else
87
presence = temp.load()
88
fi
89
90
if presence? then
91
if is_first then
92
brancher.branch(BRANCH.Z, presence, fail, "if")
93
else
94
brancher.branch(BRANCH.Z, presence, fail, "elif")
95
fi
96
fi
97
98
return temp
99
si
100
101
// Bind one clause's pattern from the temp produced by the
102
// clause's test. Reference bindings take the value as-is;
103
// option-shaped value-type bindings unwrap `.value` first.
104
_emit_if_let_clause_bind(
105
c: Statements.REFUTABLE_BINDING_CLAUSE,
106
temp: TEMP,
107
fail: LABEL
108
) is
109
let init_type = temp.load().type
110
111
// Option-shaped means both `has_value` and `value` — a bare
112
// `value` member is not enough (see the case-arm equivalent
113
// above).
114
if init_type.is_value_type then
115
let has_value_member = init_type.find_member("has_value")
116
117
if has_value_member? then
118
let value_member = init_type.find_member("value")
119
120
if value_member? then
121
gen_destructuring_initialize(
122
c.pattern,
123
value_member.load(LOCATION.internal, temp.load(), _symbol_loader),
124
fail
125
)
126
127
return
128
fi
129
fi
130
fi
131
132
gen_destructuring_initialize(c.pattern, temp.load(), fail)
133
si
134
135
pre(i: Statements.IF) -> bool is
136
super.pre(i)
137
138
return true
139
si
140
141
visit(`if: Statements.IF) is
142
let is_first mut = true
143
let seen_else mut = false
144
145
let end = LABEL()
146
147
// Spill the IF's value through a frame field when the
148
// body contains a suspend; otherwise capture as usual.
149
// See COMPOSITE_VALUE_SPILLER. Recursive: inner
150
// composites (val-blocks, nested IFs) handle their own
151
// suspends the same way. `if.value` is non-null exactly
152
// when `if.want_value` is true (visit_if sets it only
153
// in that case), so the spiller takes the value
154
// directly.
155
let spiller = COMPOSITE_VALUE_SPILLER(
156
self,
157
`if,
158
`if.value,
159
_current_state_machine_frame()
160
)
161
162
spiller.enter()
163
164
let brancher = get_brancher_for_block()
165
166
for b in `if.branches do
167
let next = LABEL()
168
let first_clause_temp: TEMP? mut = null
169
170
if let b.condition? /\ !b.binding? then
171
// Enter the arm's own scope before walking the
172
// condition: a `val ... lav` condition can declare
173
// its own locals (`if val let v = e; v lav then`),
174
// and those live in the arm's scope, not the
175
// enclosing one.
176
self.pre(b)
177
178
condition.walk(self)
179
180
if is_first then
181
brancher.branch(BRANCH.Z, condition.value!, next, "if")
182
else
183
brancher.branch(BRANCH.Z, condition.value!, next, "elif")
184
fi
185
elif let b.binding? then
186
// `if let` branch: for each clause, evaluate its
187
// scrutinee into a temp, emit an isinst for the
188
// type test when the clause carries `: V`, branch
189
// past this arm if the result has no value, then
190
// bind the clause's pattern. Clauses chain left
191
// to right; every clause's test and optional
192
// guard must succeed for the then-arm to fire.
193
//
194
// Every clause's scrutinee evaluates inside the
195
// if-arm scope, where compile-expressions resolved
196
// it: a block in the scrutinee declares its locals
197
// there. Earlier clauses' bindings are stored
198
// before a later clause's scrutinee runs, letting
199
// `if let x = a, y = x.b` chain values through.
200
self.pre(b)
201
202
first_clause_temp = _emit_if_let_clause_test(binding.clauses[0], next, is_first, brancher)
203
else
204
seen_else = true
205
206
// enter if block scope:
207
self.pre(b)
208
fi
209
210
if let b.binding? then
211
// Declare CLR locals for every clause's pattern
212
// up front — pre(REFUTABLE_BINDING) iterates all
213
// clauses.
214
self.pre(binding)
215
216
if first_clause_temp? then
217
_emit_if_let_clause_bind(binding.clauses[0], first_clause_temp, next)
218
fi
219
220
if binding.clauses[0].guard? then
221
binding.clauses[0].guard!.walk(self)
222
brancher.branch(BRANCH.Z, binding.clauses[0].guard!.value!, next, "if-let-guard")
223
fi
224
225
let i mut = 1
226
while i < binding.clauses.count do
227
let clause = binding.clauses[i]
228
229
let temp = _emit_if_let_clause_test(clause, next, false, brancher)
230
231
if temp? then
232
_emit_if_let_clause_bind(clause, temp, next)
233
fi
234
235
if clause.guard? then
236
clause.guard.walk(self)
237
brancher.branch(BRANCH.Z, clause.guard!.value!, next, "if-let-guard")
238
fi
239
240
i = i + 1
241
od
242
fi
243
244
b.body.walk(self)
245
246
// no-op?
247
b.accept(self)
248
249
if `if.want_value then
250
if let b.body.value? then
251
// Coerce the branch value to the IF's result type
252
// so a value-type branch flowing into a wider slot
253
// is boxed (T -> object) or wrapped (T -> T?); the
254
// else branch already lowers null -> default at
255
// compile time, but a present-value branch pushes
256
// its bare value here and must match the join type.
257
let block_type = `if.value?.type
258
let coerced =
259
if block_type? /\ !block_type.is_void /\ !block_type.is_error then
260
_boxer.box_if_needed(value, block_type)
261
else
262
value
263
fi
264
265
spiller.emit_value(coerced)
266
267
// Discard the capture-mode value when the IF is
268
// in expected-void position but a branch supplied
269
// a non-void value. Spill mode stfld'd into a
270
// typed field so no extra pop is needed.
271
if let
272
`if.expected_type? /\
273
!spiller.is_spilling /\
274
expected_type.is_void /\
275
!value.type!.is_void
276
then
277
add(Values.INSTRUCTION(ILOpCode.POP))
278
fi
279
fi
280
fi
281
282
brancher.branch(end)
283
brancher.label(next)
284
285
is_first = false
286
od
287
288
// A missing-else if yields absence on its fall-through edge -
289
// the same default-of-optional a natural loop exit pushes.
290
if `if.yields_absence_on_fall_through /\ `if.value? then
291
spiller.emit_value(IR.Values.DEFAULT(`if.value!.type!))
292
fi
293
294
brancher.label(end)
295
296
super.visit(`if)
297
298
spiller.leave()
299
si
300
301
pre(`case: Statements.CASE) -> bool is
302
super.pre(`case)
303
304
return true
305
si
306
307
visit(`case: Statements.CASE) is
308
// Spill the CASE's value through a frame field when the
309
// body contains a suspend; otherwise capture as usual.
310
// See COMPOSITE_VALUE_SPILLER. `case.value` is non-null
311
// exactly when `case.want_value` is true.
312
let spiller = COMPOSITE_VALUE_SPILLER(
313
self,
314
`case,
315
`case.value,
316
_current_state_machine_frame()
317
)
318
319
spiller.enter()
320
321
// The scrutinee was spilled once into a temp in
322
// compile-expressions (see CASE_STATE); emit the spill and
323
// read the temp. Reusing the single load Value across every
324
// label and pattern arm is safe - Load.TEMP.gen is an
325
// idempotent ldloc - and preserves the scrutinee's
326
// single-evaluation semantics.
327
// pre(CASE) takes over the walk, so the scrutinee is walked
328
// here: an `if` or a block in it builds its arms' IL as it
329
// is walked, and the spill only reads the value that built.
330
`case.expression.walk(self)
331
332
if `case.scrutinee_spill? then
333
current_block.add(`case.scrutinee_spill!)
334
fi
335
336
let scrutinee_load = `case.scrutinee_load!
337
338
let brancher = get_brancher_for_block()
339
340
// The case's own exit, as a plain label rather than an entry
341
// on the loop stack: an arm branches here to skip the arms
342
// below it. A case is not a loop, and with no fall-through
343
// between arms there is nothing in one to break out of, so
344
// putting it on that stack only hid the enclosing loop: a
345
// `break` in an arm left the case rather than the loop, and a
346
// `continue` targeted a `start` the case never marks. The `if`
347
// statement above uses a plain label for the same job.
348
let case_end = LABEL()
349
350
let has_else mut = false
351
for m in `case.matches do
352
if !m.expressions? /\ !m.pattern? then
353
has_else = true
354
fi
355
od
356
357
for m in `case.matches do
358
let next = LABEL()
359
360
if m.expressions? then
361
// Per-label value-equality test Values, built in
362
// compile-expressions (see CASE_MATCH_STATE). A
363
// non-null test embeds its label, so it is emitted
364
// and branched on directly; a null test means no
365
// operator resolved (or the label is `null`), so
366
// generate-il falls back to a raw compare.
367
let tests = m.tests
368
369
if m.expressions.expressions.count == 1 then
370
let test: IR.Values.Value? = if tests? then tests[0] else null fi
371
372
if test? then
373
brancher.branch(BRANCH.Z, test, next)
374
else
375
m.expressions.expressions[0].walk(self)
376
377
let label_value = m.expressions!.expressions[0].value!
378
379
if (label_value.type?.is_null ?? false) then
380
// `when null` is a presence test, not a
381
// value compare: match absence. Branch
382
// to `next` when the scrutinee is present.
383
gen_case_null_test(scrutinee_load, `case.expression.value!.type!, next, false)
384
else
385
brancher.branch(BRANCH.NE, scrutinee_load, label_value, next)
386
fi
387
fi
388
elif m.expressions.expressions.count > 1 then
389
let match = LABEL()
390
391
for i in 0..m.expressions!.expressions.count do
392
let test: IR.Values.Value? = if tests? then tests[i] else null fi
393
394
if test? then
395
brancher.branch(BRANCH.NZ, test, match)
396
else
397
let e = m.expressions!.expressions[i]
398
e.walk(self)
399
400
let label_value = e.value!
401
402
if (label_value.type?.is_null ?? false) then
403
// `when null` in a multi-label arm:
404
// branch to `match` when absent.
405
gen_case_null_test(scrutinee_load, `case.expression.value!.type!, match, true)
406
else
407
brancher.branch(BRANCH.EQ, scrutinee_load, label_value, match)
408
fi
409
fi
410
od
411
412
brancher.branch(next)
413
414
brancher.label(match)
415
fi
416
417
m.walk(self)
418
elif m.pattern? then
419
// Pattern arm: enter the arm's scope, narrow the
420
// case temp against the pattern's type (ascription
421
// → isinst; option-shape value type → has_value),
422
// branch to `next` on failure, then bind. Mirrors
423
// `if let`'s presence-test + bind shape, applied
424
// against the case's scrutinee TEMP instead of a
425
// per-branch initializer TEMP.
426
self.pre(m)
427
428
let pattern = m.pattern!
429
let init_type: Semantic.Types.Type mut = `case.expression.value!.type!
430
let arm_source: IR.Values.Value mut = scrutinee_load
431
432
if pattern.is_explicit_type /\ pattern.type_expression.type? then
433
let source_type = init_type
434
435
init_type = PATTERN_CHECKER.runtime_test_type(pattern.type_expression.type, init_type, _innate_symbol_lookup)
436
437
if PATTERN_CHECKER.is_lifted_test(pattern.type_expression.type, source_type) then
438
// A test against the optional of a plain value
439
// type unboxes as well as testing.
440
if !source_type.matches(init_type) then
441
arm_source = _type_caster.cast_value(m.location, arm_source, init_type, true, false)
442
fi
443
else
444
arm_source = IR.Values.CAST(init_type, arm_source, false)
445
fi
446
fi
447
448
let arm_temp = TEMP(current_block, "case_let", arm_source)
449
450
// An option-shape value type is one with both
451
// `has_value` and `value` members. Requiring both
452
// matters: a plain value type can legitimately have
453
// a member named `value` — a tuple with an element
454
// so named, for instance — and unwrapping through
455
// it would bind the pattern against that element
456
// instead of the whole value.
457
let value_member: Semantic.Symbols.Symbol? mut = null
458
let presence: IR.Values.Value? mut = null
459
if init_type.is_value_type then
460
let has_value_member = init_type.find_member("has_value")
461
if has_value_member? then
462
presence = has_value_member.load(LOCATION.internal, arm_temp.load(), _symbol_loader)
463
value_member = init_type.find_member("value")
464
fi
465
else
466
presence = arm_temp.load()
467
fi
468
469
if presence? then
470
brancher.branch(BRANCH.Z, presence, next, "case-let")
471
fi
472
473
self.pre(pattern)
474
475
if value_member? then
476
// Option-shape value type: bind from the
477
// unwrapped `.value` member.
478
gen_destructuring_initialize(
479
pattern.left,
480
value_member.load(LOCATION.internal, arm_temp.load(), _symbol_loader),
481
next
482
)
483
else
484
gen_destructuring_initialize(pattern.left, arm_temp.load(), next)
485
fi
486
487
if m.guard? then
488
m.guard.walk(self)
489
brancher.branch(BRANCH.Z, m.guard!.value!, next, "case-when-guard")
490
fi
491
492
self.visit(m)
493
else
494
m.walk(self)
495
fi
496
497
if `case.want_value then
498
if let m.statements.value? then
499
// Coerce the arm value to the CASE's result type
500
// so a value-type arm flowing into a wider slot is
501
// boxed (T -> object) or wrapped (T -> T?), matching
502
// the join type the other arms deliver.
503
let block_type = `case.value?.type
504
let coerced =
505
if block_type? /\ !block_type.is_void /\ !block_type.is_error then
506
_boxer.box_if_needed(value, block_type)
507
else
508
value
509
fi
510
511
spiller.emit_value(coerced)
512
513
if let
514
`case.expected_type? /\
515
!spiller.is_spilling /\
516
expected_type.is_void /\
517
!value.type!.is_void
518
then
519
add(Values.INSTRUCTION(ILOpCode.POP))
520
fi
521
fi
522
fi
523
524
brancher.branch(case_end)
525
526
brancher.label(next)
527
od
528
529
// The no-match path. For a case-expression it is
530
// unreachable but the verifier cannot see that, so the
531
// throw is there to keep the stack invariant. In a session
532
// it is reachable for either form: a later cell can add an
533
// alternative this case never saw, and a statement-form
534
// case would otherwise run no arm and carry on as though it
535
// had. An ordinary build's statement form needs neither.
536
if
537
`case.is_exhaustive /\ !has_else /\
538
(`case.want_value \/ _context.is_submission)
539
then
540
add(
541
Literal.STRING(
542
"case is not exhaustive at runtime",
543
_innate_symbol_lookup.get_string_type()
544
)
545
)
546
add(Values.NEW_ASSERT_FAILED_EXCEPTION())
547
add(Values.INSTRUCTION(ILOpCode.THROW))
548
elif `case.want_value /\ `case.requires_default_fallthrough /\ !has_else then
549
// Open-domain case-expression with a defaultable
550
// expected type and no `else` arm — push default(T)
551
// for the no-match path. CASE_EXHAUSTIVENESS_CHECKER
552
// has emitted the `case-needs-else` warning.
553
add(IR.Values.DEFAULT(`case.expected_type!))
554
fi
555
556
brancher.label(case_end)
557
558
super.visit(`case)
559
560
spiller.leave()
561
si
562
563
// Emit a `when null` test against the case scrutinee: a presence
564
// test, not a value compare. For a value-type optional
565
// (`Nullable[T]`, `MAYBE[T]`) read `has_value`; for a reference
566
// optional the receiver itself is the presence. `branch_on_absent`
567
// selects which edge branches to `target`: a single-label `when
568
// null` arm skips to `next` when the scrutinee is present; a null
569
// label in a multi-label arm jumps to `match` when it is absent.
570
gen_case_null_test(
571
scrutinee_load: Values.Value,
572
scrutinee_type: Semantic.Types.Type,
573
target: IR.LABEL,
574
branch_on_absent: bool
575
) is
576
let presence: Values.Value? mut =
577
if scrutinee_type.is_value_type then
578
let has_value_member = scrutinee_type.find_member("has_value")
579
580
if has_value_member? then
581
has_value_member.load(LOCATION.internal, scrutinee_load, _symbol_loader)
582
else
583
null
584
fi
585
else
586
scrutinee_load
587
fi
588
589
if presence? then
590
if branch_on_absent then
591
get_brancher_for_block().branch(BRANCH.Z, presence, target)
592
else
593
get_brancher_for_block().branch(BRANCH.NZ, presence, target)
594
fi
595
else
596
let null_value = IR.Values.NULL(scrutinee_type)
597
598
if branch_on_absent then
599
get_brancher_for_block().branch(BRANCH.EQ, scrutinee_load, null_value, target)
600
else
601
get_brancher_for_block().branch(BRANCH.NE, scrutinee_load, null_value, target)
602
fi
603
fi
604
si
605
606
pre(match: Statements.CASE_MATCH) -> bool is
607
super.pre(match)
608
609
return true
610
si
611
612
visit(match: Statements.CASE_MATCH) is
613
match.statements.accept(self)
614
615
super.visit(match)
616
si
617
618
si
619
si