compiler: Initialize catch lowering metadata

71575880a1e68799ec099bb7e79e1b1d2738591ccd477eb5dba3eef4fd269ddf
Alexis Sellier committed ago 1 parent ce06be75
lib/std/lang/lower.rad +29 -13
6583 6583
    } else { // Void return.
6584 6584
        return il::Val::Undef;
6585 6585
    }
6586 6586
}
6587 6587
6588 +
/// Destination and optional payload type for one catch clause.
6589 +
record CatchTarget: Copy {
6590 +
    /// Block that receives the selected error.
6591 +
    block: BlockId,
6592 +
    /// Error type for a typed clause, or nil for a catch-all.
6593 +
    errorType: ?resolver::Type,
6594 +
}
6595 +
6596 +
/// Read a catch destination that the first lowering pass initialized.
6597 +
fn catchTarget(targets: &[?CatchTarget], index: u32) -> CatchTarget {
6598 +
    let target = targets[index] else panic "catchTarget: missing catch destination";
6599 +
    return target;
6600 +
}
6601 +
6588 6602
/// Lower typed multi-catch clauses.
6589 6603
///
6590 6604
/// Emits a switch on the global error tag to dispatch to the correct catch
6591 6605
/// clause. Each typed clause extracts the error payload for its specific type
6592 6606
/// and binds it to the clause's identifier.
6599 6613
    mergeBlock: &mut ?BlockId
6600 6614
) throws (LowerError) where 'arena: 'phase, 'phase: 'function {
6601 6615
    let entry = currentBlock(self);
6602 6616
6603 6617
    // First pass: create blocks, resolve error types, and build switch cases.
6604 -
    let mut blocks: [BlockId; MAX_CATCH_CLAUSES] = undefined;
6605 -
    let mut errTypes: [?resolver::Type; MAX_CATCH_CLAUSES] = undefined;
6618 +
    let mut targets: [?CatchTarget; MAX_CATCH_CLAUSES] = [nil; MAX_CATCH_CLAUSES];
6606 6619
    let mut cases: *unsafe mut [il::SwitchCase] = &mut [];
6607 6620
    let mut defaultIdx: ?u32 = nil;
6608 6621
6609 6622
    for clauseNode, i in catches {
6610 6623
        let case ast::NodeValue::CatchClause(clause) = clauseNode.value
6611 6624
            else panic "lowerMultiCatch: expected CatchClause";
6612 6625
6613 -
        set blocks[i] = try createBlock(self, "catch");
6614 -
        addPredecessor(self, blocks[i], entry);
6626 +
        let block = try createBlock(self, "catch");
6627 +
        addPredecessor(self, block, entry);
6615 6628
6616 6629
        if let typeNode = clause.typeNode {
6617 6630
            let errTy = try typeOf(self, typeNode);
6618 -
            set errTypes[i] = errTy;
6631 +
            set targets[i] = CatchTarget { block, errorType: errTy };
6619 6632
6620 6633
            cases.append(il::SwitchCase {
6621 6634
                value: getOrAssignErrorTag(self.low, errTy) as i64,
6622 -
                target: *blocks[i],
6635 +
                target: *block,
6623 6636
                args: &mut []
6624 6637
            }, alloc::arenaAllocator(self.arena));
6625 6638
        } else {
6626 -
            set errTypes[i] = nil;
6639 +
            set targets[i] = CatchTarget { block, errorType: nil };
6627 6640
            set defaultIdx = i;
6628 6641
        }
6629 6642
    }
6630 6643
6631 6644
    // Emit switch. Default target is the catch-all block, or an unreachable block.
6632 -
    let mut defaultTarget: BlockId = undefined;
6645 +
    let mut selectedDefault: ?BlockId = nil;
6633 6646
    if let idx = defaultIdx {
6634 -
        set defaultTarget = blocks[idx];
6647 +
        set selectedDefault = catchTarget(&targets[..], idx).block;
6635 6648
    } else {
6636 -
        set defaultTarget = try createBlock(self, "unreachable");
6637 -
        addPredecessor(self, defaultTarget, entry);
6649 +
        let block = try createBlock(self, "unreachable");
6650 +
        addPredecessor(self, block, entry);
6651 +
        set selectedDefault = block;
6638 6652
    }
6653 +
    let defaultTarget = selectedDefault else panic "lowerMultiCatch: missing default destination";
6639 6654
    emit(self, il::Instr::Switch {
6640 6655
        val: il::Val::Reg(tagReg),
6641 6656
        defaultTarget: *defaultTarget,
6642 6657
        defaultArgs: &mut [],
6643 6658
        cases
6646 6661
    // Second pass: emit each catch clause body.
6647 6662
    for clauseNode, i in catches {
6648 6663
        let case ast::NodeValue::CatchClause(clause) = clauseNode.value
6649 6664
            else panic "lowerMultiCatch: expected CatchClause";
6650 6665
6651 -
        try switchToAndSeal(self, blocks[i]);
6666 +
        let target = catchTarget(&targets[..], i);
6667 +
        try switchToAndSeal(self, target.block);
6652 6668
        let savedVarsLen = enterVarScope(&self.vars);
6653 6669
6654 6670
        if let binding = clause.binding {
6655 6671
            let case ast::NodeValue::Ident(name) = binding.value else {
6656 6672
                throw LowerError::ExpectedIdentifier;
6657 6673
            };
6658 -
            let errTy = errTypes[i] else panic "lowerMultiCatch: catch-all with binding";
6674 +
            let errTy = target.errorType else panic "lowerMultiCatch: catch-all with binding";
6659 6675
            let errVal = tvalPayloadVal(self, base, errTy, RESULT_VAL_OFFSET);
6660 6676
6661 6677
            newVar(self, name, ilType(self.low, errTy), false, errVal);
6662 6678
        }
6663 6679
        try lowerBlock(self, clause.body);
test/tests/error.multi.catch.capacity.rad added +92 -0
1 +
//! returns: 0
2 +
//! Typed catch metadata retains every error payload across repeated dispatch.
3 +
4 +
/// Error payload 0.
5 +
union Error0: Copy { Value(u32) }
6 +
7 +
/// Error payload 1.
8 +
union Error1: Copy { Value(u32) }
9 +
10 +
/// Error payload 2.
11 +
union Error2: Copy { Value(u32) }
12 +
13 +
/// Error payload 3.
14 +
union Error3: Copy { Value(u32) }
15 +
16 +
/// Error payload 4.
17 +
union Error4: Copy { Value(u32) }
18 +
19 +
/// Error payload 5.
20 +
union Error5: Copy { Value(u32) }
21 +
22 +
/// Error payload 6.
23 +
union Error6: Copy { Value(u32) }
24 +
25 +
/// Error payload 7.
26 +
union Error7: Copy { Value(u32) }
27 +
28 +
/// Throw the selected payload or return a successful result.
29 +
fn dispatch(which: u32) -> u32 throws (Error0, Error1, Error2, Error3, Error4, Error5, Error6, Error7) {
30 +
    if which == 0 { throw Error0::Value(which + 10); }
31 +
    if which == 1 { throw Error1::Value(which + 10); }
32 +
    if which == 2 { throw Error2::Value(which + 10); }
33 +
    if which == 3 { throw Error3::Value(which + 10); }
34 +
    if which == 4 { throw Error4::Value(which + 10); }
35 +
    if which == 5 { throw Error5::Value(which + 10); }
36 +
    if which == 6 { throw Error6::Value(which + 10); }
37 +
    if which == 7 { throw Error7::Value(which + 10); }
38 +
    return 99;
39 +
}
40 +
41 +
/// Match every declared error type and retain its payload.
42 +
fn exhaustive(which: u32) -> u32 {
43 +
    let mut result: u32 = 0;
44 +
    set result = try dispatch(which) catch e as Error0 {
45 +
        let case Error0::Value(value) = e else panic "exhaustive: expected payload";
46 +
        return value;
47 +
    } catch e as Error1 {
48 +
        let case Error1::Value(value) = e else panic "exhaustive: expected payload";
49 +
        return value;
50 +
    } catch e as Error2 {
51 +
        let case Error2::Value(value) = e else panic "exhaustive: expected payload";
52 +
        return value;
53 +
    } catch e as Error3 {
54 +
        let case Error3::Value(value) = e else panic "exhaustive: expected payload";
55 +
        return value;
56 +
    } catch e as Error4 {
57 +
        let case Error4::Value(value) = e else panic "exhaustive: expected payload";
58 +
        return value;
59 +
    } catch e as Error5 {
60 +
        let case Error5::Value(value) = e else panic "exhaustive: expected payload";
61 +
        return value;
62 +
    } catch e as Error6 {
63 +
        let case Error6::Value(value) = e else panic "exhaustive: expected payload";
64 +
        return value;
65 +
    } catch e as Error7 {
66 +
        let case Error7::Value(value) = e else panic "exhaustive: expected payload";
67 +
        return value;
68 +
    };
69 +
    return result;
70 +
}
71 +
72 +
/// Retain the typed payload or select the untyped fallback.
73 +
fn fallback(which: u32) -> u32 {
74 +
    return try dispatch(which) catch e as Error0 {
75 +
        let case Error0::Value(value) = e else panic "fallback: expected payload";
76 +
        return value;
77 +
    } catch {
78 +
        return 50;
79 +
    };
80 +
}
81 +
82 +
/// Exercise every catch destination, success, fallback, and repeated calls.
83 +
@default fn main() -> u32 {
84 +
    for repeat in 0..2 {
85 +
        for which in 0..8 { assert exhaustive(which) == which + 10; }
86 +
        assert exhaustive(8) == 99;
87 +
        assert fallback(0) == 10;
88 +
        assert fallback(7) == 50;
89 +
        assert fallback(8) == 99;
90 +
    }
91 +
    return 0;
92 +
}