cranelift_codegen/ir/exception_table.rs
1//! Exception tables: catch handlers on `try_call` instructions.
2//!
3//! An exception table describes where execution flows after returning
4//! from `try_call`. It contains both the "normal" destination -- the
5//! block to branch to when the function returns without throwing an
6//! exception -- and any "catch" destinations associated with
7//! particular exception tags. Each target indicates the arguments to
8//! pass to the block that receives control.
9//!
10//! Like other side-tables (e.g., jump tables), each exception table
11//! must be used by only one instruction. Sharing is not permitted
12//! because it can complicate transforms (how does one change the
13//! table used by only one instruction if others also use it?).
14//!
15//! In order to allow the `try_call` instruction itself to remain
16//! small, the exception table also contains the signature ID of the
17//! called function.
18
19use crate::ir::entities::{ExceptionTag, SigRef};
20use crate::ir::instructions::ValueListPool;
21use crate::ir::BlockCall;
22use alloc::vec::Vec;
23use core::fmt::{self, Display, Formatter};
24use cranelift_entity::packed_option::PackedOption;
25#[cfg(feature = "enable-serde")]
26use serde_derive::{Deserialize, Serialize};
27
28/// Contents of an exception table.
29///
30/// The "no exception" target for is stored as the last element of the
31/// underlying vector. It can be accessed through the `normal_return`
32/// and `normal_return_mut` functions. Exceptional catch clauses may
33/// be iterated using the `catches` and `catches_mut` functions. All
34/// targets may be iterated over using the `all_targets` and
35/// `all_targets_mut` functions.
36#[derive(Debug, Clone, PartialEq, Hash)]
37#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
38pub struct ExceptionTableData {
39 /// All BlockCalls packed together. This is necessary because the
40 /// rest of the compiler expects to be able to grab a slice of
41 /// branch targets for any branch instruction. The last BlockCall
42 /// is the normal-return destination, and the rest correspond to
43 /// the tags in `tags` below. Thus, we have the invariant that
44 /// `targets.len() == tags.len() + 1`.
45 targets: Vec<BlockCall>,
46
47 /// Tags corresponding to targets other than the first one.
48 ///
49 /// A tag value of `None` indicates a catch-all handler. The
50 /// catch-all handler matches only if no other handler matches,
51 /// regardless of the order in this vector.
52 ///
53 /// `tags[i]` corresponds to `targets[i]`. Note that there will be
54 /// one more `targets` element than `tags` because the last
55 /// element in `targets` is the normal-return path.
56 tags: Vec<PackedOption<ExceptionTag>>,
57
58 /// The signature of the function whose invocation is associated
59 /// with this handler table.
60 sig: SigRef,
61}
62
63impl ExceptionTableData {
64 /// Create new exception-table data.
65 ///
66 /// This data represents the destinations upon return from
67 /// `try_call` or `try_call_indirect` instruction. There are two
68 /// possibilities: "normal return" (no exception thrown), or an
69 /// exceptional return corresponding to one of the listed
70 /// exception tags.
71 ///
72 /// The given tags are passed through to the metadata provided
73 /// alongside the provided function body, and Cranelift itself
74 /// does not implement an unwinder; thus, the meaning of the tags
75 /// is ultimately up to the embedder of Cranelift. The tags are
76 /// wrapped in `Option` to allow encoding a "catch-all" handler.
77 ///
78 /// The BlockCalls must have signatures that match the targeted
79 /// blocks, as usual. These calls are allowed to use
80 /// `BlockArg::TryCallRet` in the normal-return case, with types
81 /// corresponding to the signature's return values, and
82 /// `BlockArg::TryCallExn` in the exceptional-return cases, with
83 /// types corresponding to native machine words and an arity
84 /// corresponding to the number of payload values that the calling
85 /// convention and platform support. (See [`isa::CallConv`] for
86 /// more details.)
87 pub fn new(
88 sig: SigRef,
89 normal_return: BlockCall,
90 tags_and_targets: impl IntoIterator<Item = (Option<ExceptionTag>, BlockCall)>,
91 ) -> Self {
92 let mut targets = vec![];
93 let mut tags = vec![];
94 for (tag, target) in tags_and_targets {
95 tags.push(tag.into());
96 targets.push(target);
97 }
98 targets.push(normal_return);
99
100 ExceptionTableData { targets, tags, sig }
101 }
102
103 /// Return a value that can display the contents of this exception
104 /// table.
105 pub fn display<'a>(&'a self, pool: &'a ValueListPool) -> DisplayExceptionTable<'a> {
106 DisplayExceptionTable { table: self, pool }
107 }
108
109 /// Get the default target for the non-exceptional return case.
110 pub fn normal_return(&self) -> &BlockCall {
111 self.targets.last().unwrap()
112 }
113
114 /// Get the default target for the non-exceptional return case.
115 pub fn normal_return_mut(&mut self) -> &mut BlockCall {
116 self.targets.last_mut().unwrap()
117 }
118
119 /// Get the targets for exceptional return cases, together with
120 /// their tags.
121 pub fn catches(&self) -> impl Iterator<Item = (Option<ExceptionTag>, &BlockCall)> + '_ {
122 self.tags
123 .iter()
124 .map(|tag| tag.expand())
125 // Skips the last entry of `targets` (the normal return)
126 // because `tags` is one element shorter.
127 .zip(self.targets.iter())
128 }
129
130 /// Get the targets for exceptional return cases, together with
131 /// their tags.
132 pub fn catches_mut(
133 &mut self,
134 ) -> impl Iterator<Item = (Option<ExceptionTag>, &mut BlockCall)> + '_ {
135 self.tags
136 .iter()
137 .map(|tag| tag.expand())
138 // Skips the last entry of `targets` (the normal return)
139 // because `tags` is one element shorter.
140 .zip(self.targets.iter_mut())
141 }
142
143 /// Get all branch targets.
144 pub fn all_branches(&self) -> &[BlockCall] {
145 &self.targets[..]
146 }
147
148 /// Get all branch targets.
149 pub fn all_branches_mut(&mut self) -> &mut [BlockCall] {
150 &mut self.targets[..]
151 }
152
153 /// Get the signature of the function called with this exception
154 /// table.
155 pub fn signature(&self) -> SigRef {
156 self.sig
157 }
158
159 /// Clears all entries in this exception table, but leaves the function signature.
160 pub fn clear(&mut self) {
161 self.tags.clear();
162 self.targets.clear();
163 }
164}
165
166/// A wrapper for the context required to display a
167/// [ExceptionTableData].
168pub struct DisplayExceptionTable<'a> {
169 table: &'a ExceptionTableData,
170 pool: &'a ValueListPool,
171}
172
173impl<'a> Display for DisplayExceptionTable<'a> {
174 fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
175 write!(
176 fmt,
177 "{}, {}, [",
178 self.table.sig,
179 self.table.normal_return().display(self.pool)
180 )?;
181 let mut first = true;
182 for (tag, block_call) in self.table.catches() {
183 if first {
184 write!(fmt, " ")?;
185 first = false;
186 } else {
187 write!(fmt, ", ")?;
188 }
189 if let Some(tag) = tag {
190 write!(fmt, "{}: {}", tag, block_call.display(self.pool))?;
191 } else {
192 write!(fmt, "default: {}", block_call.display(self.pool))?;
193 }
194 }
195 let space = if first { "" } else { " " };
196 write!(fmt, "{space}]")?;
197 Ok(())
198 }
199}