compiler: Initialize catch lowering metadata
71575880a1e68799ec099bb7e79e1b1d2738591ccd477eb5dba3eef4fd269ddf
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 | + | } |