block labels
This commit is contained in:
@@ -3,6 +3,7 @@ package lower
|
||||
import "../hir"
|
||||
import "../ir"
|
||||
import "../source"
|
||||
import "../symbol"
|
||||
import "../target"
|
||||
import "../types"
|
||||
import "core:fmt"
|
||||
@@ -22,11 +23,15 @@ State :: struct {
|
||||
allocator: mem.Allocator,
|
||||
}
|
||||
|
||||
// `break` branches to `exit_lbl`; `continue` branches to `continue_lbl` (the
|
||||
// loop's update/latch, which runs the update clause then re-tests the condition).
|
||||
// An enclosing exit target. `break` branches to `exit_lbl`; `continue` branches to
|
||||
// `continue_lbl` (the loop's update/latch, which runs the update clause then re-tests
|
||||
// the condition). A labeled `break`/`continue`/`yield` matches `label`; a value block is
|
||||
// `is_loop = false` (it has no `continue` and is skipped by plain `break`/`continue`).
|
||||
Loop_Ctx :: struct {
|
||||
label: symbol.Id,
|
||||
exit_lbl: i64,
|
||||
continue_lbl: i64,
|
||||
is_loop: bool,
|
||||
}
|
||||
|
||||
fresh_label :: proc(state: ^State) -> i64 {
|
||||
@@ -752,10 +757,25 @@ lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
|
||||
})
|
||||
}
|
||||
case .Break, .Continue:
|
||||
// The checker guarantees these only appear inside a loop, so the
|
||||
// stack is non-empty; guard defensively regardless.
|
||||
if len(state.loops) > 0 {
|
||||
target := state.loops[len(state.loops)-1]
|
||||
// Find the target: a labeled `break`/`continue` matches the innermost target
|
||||
// (loop or value block) with that label; an unlabeled one takes the innermost
|
||||
// loop (`continue` and unlabeled targets skip non-loop block targets). The
|
||||
// checker guarantees a match exists; guard defensively regardless.
|
||||
target_index := -1
|
||||
for i := len(state.loops) - 1; i >= 0; i -= 1 {
|
||||
ctx := state.loops[i]
|
||||
if symbol.is_valid(statement.label) {
|
||||
if ctx.label == statement.label && (ctx.is_loop || statement.kind == .Break) {
|
||||
target_index = i
|
||||
break
|
||||
}
|
||||
} else if ctx.is_loop {
|
||||
target_index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if target_index >= 0 {
|
||||
target := state.loops[target_index]
|
||||
label := target.exit_lbl if statement.kind == .Break else target.continue_lbl
|
||||
append_instruction(state, ir.Instruction{
|
||||
op=.Br, span=statement.span, type=types.VOID, integer=label,
|
||||
@@ -763,6 +783,27 @@ lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
case .Block:
|
||||
// A labeled value block: lower its body, then emit the exit label that its
|
||||
// `yield :blk` (a labeled break) branches to. Not a loop, so plain
|
||||
// `break`/`continue` skip it (is_loop=false).
|
||||
exit_lbl := fresh_label(state)
|
||||
append(&state.loops, Loop_Ctx{label=statement.label, exit_lbl=exit_lbl, continue_lbl=exit_lbl, is_loop=false})
|
||||
lower_statements(state, statement.then_body)
|
||||
pop(&state.loops)
|
||||
// Explicit fall-through to the exit label so the preceding block is terminated
|
||||
// (dead code after a terminator gets a fresh recovery block in the emitter),
|
||||
// mirroring the `br` a `while`/`for` emits before its labels.
|
||||
append_instruction(state, ir.Instruction{
|
||||
op=.Br, span=statement.span, type=types.VOID, integer=exit_lbl,
|
||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
append_instruction(state, ir.Instruction{
|
||||
op=.Label, span=statement.span, type=types.VOID, integer=exit_lbl,
|
||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Expression, .Sink:
|
||||
_ = lower_expr(state, statement.expr)
|
||||
case .Trap:
|
||||
@@ -940,7 +981,7 @@ lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
|
||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
append(&state.loops, Loop_Ctx{exit_lbl=exit_lbl, continue_lbl=update_lbl})
|
||||
append(&state.loops, Loop_Ctx{label=statement.label, exit_lbl=exit_lbl, continue_lbl=update_lbl, is_loop=true})
|
||||
lower_statements(state, statement.then_body)
|
||||
pop(&state.loops)
|
||||
append_instruction(state, ir.Instruction{
|
||||
@@ -1072,7 +1113,7 @@ lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
|
||||
// e.g. `for 0..=255 |b: u8| { ... continue }` exits cleanly instead of
|
||||
// overflowing the increment on the final element.
|
||||
continue_lbl := fresh_label(state)
|
||||
append(&state.loops, Loop_Ctx{exit_lbl=exit_lbl, continue_lbl=continue_lbl})
|
||||
append(&state.loops, Loop_Ctx{label=statement.label, exit_lbl=exit_lbl, continue_lbl=continue_lbl, is_loop=true})
|
||||
lower_statements(state, statement.then_body)
|
||||
pop(&state.loops)
|
||||
append_instruction(state, ir.Instruction{
|
||||
@@ -1257,7 +1298,7 @@ lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
|
||||
target=ir.INVALID_REF, a=capture_slot, b=captured,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
append(&state.loops, Loop_Ctx{exit_lbl=exit_lbl, continue_lbl=update_lbl})
|
||||
append(&state.loops, Loop_Ctx{label=statement.label, exit_lbl=exit_lbl, continue_lbl=update_lbl, is_loop=true})
|
||||
lower_statements(state, statement.then_body)
|
||||
pop(&state.loops)
|
||||
append_instruction(state, ir.Instruction{
|
||||
|
||||
Reference in New Issue
Block a user