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 
19 use crate::ir::BlockCall;
20 use crate::ir::entities::{ExceptionTag, SigRef};
21 use crate::ir::instructions::ValueListPool;
22 use alloc::vec::Vec;
23 use core::fmt::{self, Display, Formatter};
24 use cranelift_entity::packed_option::PackedOption;
25 #[cfg(feature = "enable-serde")]
26 use 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))]
38 pub 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 
63 impl 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     /// Deep-clone this exception table.
110     pub fn deep_clone(&self, pool: &mut ValueListPool) -> Self {
111         Self {
112             targets: self.targets.iter().map(|b| b.deep_clone(pool)).collect(),
113             tags: self.tags.clone(),
114             sig: self.sig,
115         }
116     }
117 
118     /// Get the default target for the non-exceptional return case.
119     pub fn normal_return(&self) -> &BlockCall {
120         self.targets.last().unwrap()
121     }
122 
123     /// Get the default target for the non-exceptional return case.
124     pub fn normal_return_mut(&mut self) -> &mut BlockCall {
125         self.targets.last_mut().unwrap()
126     }
127 
128     /// Get the targets for exceptional return cases, together with
129     /// their tags.
130     pub fn catches(&self) -> impl Iterator<Item = (Option<ExceptionTag>, &BlockCall)> + '_ {
131         self.tags
132             .iter()
133             .map(|tag| tag.expand())
134             // Skips the last entry of `targets` (the normal return)
135             // because `tags` is one element shorter.
136             .zip(self.targets.iter())
137     }
138 
139     /// Get the targets for exceptional return cases, together with
140     /// their tags.
141     pub fn catches_mut(
142         &mut self,
143     ) -> impl Iterator<Item = (Option<ExceptionTag>, &mut BlockCall)> + '_ {
144         self.tags
145             .iter()
146             .map(|tag| tag.expand())
147             // Skips the last entry of `targets` (the normal return)
148             // because `tags` is one element shorter.
149             .zip(self.targets.iter_mut())
150     }
151 
152     /// The number of catch edges in this exception table.
153     pub fn len_catches(&self) -> usize {
154         self.tags.len()
155     }
156 
157     /// Get the `index`th catch edge from this table.
158     pub fn get_catch(&self, index: usize) -> Option<(Option<ExceptionTag>, &BlockCall)> {
159         let tag = self.tags.get(index)?.expand();
160         let target = &self.targets[index];
161         Some((tag, target))
162     }
163 
164     /// Get all branch targets.
165     pub fn all_branches(&self) -> &[BlockCall] {
166         &self.targets[..]
167     }
168 
169     /// Get all branch targets.
170     pub fn all_branches_mut(&mut self) -> &mut [BlockCall] {
171         &mut self.targets[..]
172     }
173 
174     /// Get the signature of the function called with this exception
175     /// table.
176     pub fn signature(&self) -> SigRef {
177         self.sig
178     }
179 
180     /// Get a mutable handle to this exception table's signature.
181     pub(crate) fn signature_mut(&mut self) -> &mut SigRef {
182         &mut self.sig
183     }
184 
185     /// Clears all entries in this exception table, but leaves the function signature.
186     pub fn clear(&mut self) {
187         self.tags.clear();
188         self.targets.clear();
189     }
190 
191     /// Push a catch target onto this exception table.
192     ///
193     /// # Panics
194     ///
195     /// Panics if this exception table has been cleared.
196     pub fn push_catch(&mut self, tag: Option<ExceptionTag>, block_call: BlockCall) {
197         assert_eq!(
198             self.tags.len() + 1,
199             self.targets.len(),
200             "cannot push onto an exception table that has been cleared"
201         );
202 
203         self.tags.push(tag.into());
204 
205         let target_index = self.targets.len() - 1;
206         self.targets.insert(target_index, block_call);
207     }
208 }
209 
210 /// A wrapper for the context required to display a
211 /// [ExceptionTableData].
212 pub struct DisplayExceptionTable<'a> {
213     table: &'a ExceptionTableData,
214     pool: &'a ValueListPool,
215 }
216 
217 impl<'a> Display for DisplayExceptionTable<'a> {
218     fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
219         write!(
220             fmt,
221             "{}, {}, [",
222             self.table.sig,
223             self.table.normal_return().display(self.pool)
224         )?;
225         let mut first = true;
226         for (tag, block_call) in self.table.catches() {
227             if first {
228                 write!(fmt, " ")?;
229                 first = false;
230             } else {
231                 write!(fmt, ", ")?;
232             }
233             if let Some(tag) = tag {
234                 write!(fmt, "{}: {}", tag, block_call.display(self.pool))?;
235             } else {
236                 write!(fmt, "default: {}", block_call.display(self.pool))?;
237             }
238         }
239         let space = if first { "" } else { " " };
240         write!(fmt, "{space}]")?;
241         Ok(())
242     }
243 }
244