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::entities::{ExceptionTag, SigRef};
20 use crate::ir::instructions::ValueListPool;
21 use crate::ir::{BlockCall, Value};
22 use alloc::vec::Vec;
23 use core::fmt::{self, Display, Formatter};
24 #[cfg(feature = "enable-serde")]
25 use serde_derive::{Deserialize, Serialize};
26 
27 /// Contents of an exception table.
28 ///
29 /// An exception table consists of a "no exception" ("normal")
30 /// destination block-call, and a series of exceptional destination
31 /// block-calls associated with tags.
32 ///
33 /// The exceptional tags can also be interspersed with "dynamic
34 /// context" entries, which result in a particular value being stored
35 /// in the stack frame and accessible at an offset given in the
36 /// compiled exception-table metadata. This is needed for some kinds
37 /// of tag-matching where different dynamic instances of tags may
38 /// exist (e.g., in the WebAssembly exception-handling proposal).
39 ///
40 /// The sequence of targets is semantically a list of
41 /// context-or-tagged-blockcall; e.g., `[context v0, tag1: block1(v1,
42 /// v2), context v2, tag2: block2(), tag3: block3()]`.
43 ///
44 /// The "no exception" target can be accessed through the
45 /// `normal_return` and `normal_return_mut` functions. Exceptional
46 /// catch clauses may be iterated using the `catches` and
47 /// `catches_mut` functions.  All targets may be iterated over using
48 /// the `all_targets` and `all_targets_mut` functions.
49 #[derive(Debug, Clone, PartialEq, Hash)]
50 #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
51 pub struct ExceptionTableData {
52     /// All BlockCalls packed together. This is necessary because the
53     /// rest of the compiler expects to be able to grab a slice of
54     /// branch targets for any branch instruction. The last BlockCall
55     /// is the normal-return destination, and the rest are referred to
56     /// by index by the `items` below.
57     targets: Vec<BlockCall>,
58 
59     /// Exception-table items.
60     ///
61     /// This internal representation for items is like
62     /// `ExceptionTableItem` except that it has indices that refer to
63     /// `targets` above.
64     ///
65     /// A tag value of `None` indicates a catch-all handler. The
66     /// catch-all handler matches only if no other handler matches,
67     /// regardless of the order in this vector.
68     ///
69     /// `tags[i]` corresponds to `targets[i]`. Note that there will be
70     /// one more `targets` element than `tags` because the last
71     /// element in `targets` is the normal-return path.
72     items: Vec<InternalExceptionTableItem>,
73 
74     /// The signature of the function whose invocation is associated
75     /// with this handler table.
76     sig: SigRef,
77 }
78 
79 /// A single item in the match-list of an exception table.
80 #[derive(Clone, Debug)]
81 pub enum ExceptionTableItem {
82     /// A tag match, taking the specified block-call destination if
83     /// the tag matches the one in the thrown exception. (The match
84     /// predicate is up to the runtime; Cranelift only emits metadata
85     /// containing this tag.)
86     Tag(ExceptionTag, BlockCall),
87     /// A default match, always taking the specified block-call
88     /// destination.
89     Default(BlockCall),
90     /// A dynamic context update, applying to all tags until the next
91     /// update. (Cranelift does not interpret this context, but only
92     /// provides information to the runtime regarding where to find
93     /// it.)
94     Context(Value),
95 }
96 
97 /// Our internal representation of exception-table items.
98 ///
99 /// This is a version of `ExceptionTableItem` with block-calls
100 /// out-of-lined so that we can provide the slice externally. Each
101 /// block-call is referenced via an index.
102 #[derive(Clone, Debug, PartialEq, Hash)]
103 #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
104 enum InternalExceptionTableItem {
105     Tag(ExceptionTag, u32),
106     Default(u32),
107     Context(Value),
108 }
109 
110 impl ExceptionTableData {
111     /// Create new exception-table data.
112     ///
113     /// This data represents the destinations upon return from
114     /// `try_call` or `try_call_indirect` instruction. There are two
115     /// possibilities: "normal return" (no exception thrown), or an
116     /// exceptional return corresponding to one of the listed
117     /// exception tags.
118     ///
119     /// The given tags are passed through to the metadata provided
120     /// alongside the provided function body, and Cranelift itself
121     /// does not implement an unwinder; thus, the meaning of the tags
122     /// is ultimately up to the embedder of Cranelift. The tags are
123     /// wrapped in `Option` to allow encoding a "catch-all" handler.
124     ///
125     /// The BlockCalls must have signatures that match the targeted
126     /// blocks, as usual. These calls are allowed to use
127     /// `BlockArg::TryCallRet` in the normal-return case, with types
128     /// corresponding to the signature's return values, and
129     /// `BlockArg::TryCallExn` in the exceptional-return cases, with
130     /// types corresponding to native machine words and an arity
131     /// corresponding to the number of payload values that the calling
132     /// convention and platform support. (See [`CallConv`](crate::isa::CallConv) for
133     /// more details.)
new( sig: SigRef, normal_return: BlockCall, matches: impl IntoIterator<Item = ExceptionTableItem>, ) -> Self134     pub fn new(
135         sig: SigRef,
136         normal_return: BlockCall,
137         matches: impl IntoIterator<Item = ExceptionTableItem>,
138     ) -> Self {
139         let mut targets = vec![];
140         let mut items = vec![];
141         for item in matches {
142             let target_idx = u32::try_from(targets.len()).unwrap();
143             match item {
144                 ExceptionTableItem::Tag(tag, target) => {
145                     items.push(InternalExceptionTableItem::Tag(tag, target_idx));
146                     targets.push(target);
147                 }
148                 ExceptionTableItem::Default(target) => {
149                     items.push(InternalExceptionTableItem::Default(target_idx));
150                     targets.push(target);
151                 }
152                 ExceptionTableItem::Context(ctx) => {
153                     items.push(InternalExceptionTableItem::Context(ctx));
154                 }
155             }
156         }
157         targets.push(normal_return);
158 
159         ExceptionTableData {
160             targets,
161             items,
162             sig,
163         }
164     }
165 
166     /// Return a value that can display the contents of this exception
167     /// table.
display<'a>(&'a self, pool: &'a ValueListPool) -> DisplayExceptionTable<'a>168     pub fn display<'a>(&'a self, pool: &'a ValueListPool) -> DisplayExceptionTable<'a> {
169         DisplayExceptionTable { table: self, pool }
170     }
171 
172     /// Deep-clone this exception table.
deep_clone(&self, pool: &mut ValueListPool) -> Self173     pub fn deep_clone(&self, pool: &mut ValueListPool) -> Self {
174         Self {
175             targets: self.targets.iter().map(|b| b.deep_clone(pool)).collect(),
176             items: self.items.clone(),
177             sig: self.sig,
178         }
179     }
180 
181     /// Get the default target for the non-exceptional return case.
normal_return(&self) -> &BlockCall182     pub fn normal_return(&self) -> &BlockCall {
183         self.targets.last().unwrap()
184     }
185 
186     /// Get the default target for the non-exceptional return case.
normal_return_mut(&mut self) -> &mut BlockCall187     pub fn normal_return_mut(&mut self) -> &mut BlockCall {
188         self.targets.last_mut().unwrap()
189     }
190 
191     /// Get the exception-catch items: dynamic context updates for
192     /// interpreting tags, tag-associated targets, and catch-all
193     /// targets.
items(&self) -> impl Iterator<Item = ExceptionTableItem> + '_194     pub fn items(&self) -> impl Iterator<Item = ExceptionTableItem> + '_ {
195         self.items.iter().map(|item| match item {
196             InternalExceptionTableItem::Tag(tag, target_idx) => {
197                 ExceptionTableItem::Tag(*tag, self.targets[usize::try_from(*target_idx).unwrap()])
198             }
199             InternalExceptionTableItem::Default(target_idx) => {
200                 ExceptionTableItem::Default(self.targets[usize::try_from(*target_idx).unwrap()])
201             }
202             InternalExceptionTableItem::Context(ctx) => ExceptionTableItem::Context(*ctx),
203         })
204     }
205 
206     /// Get all branch targets.
all_branches(&self) -> &[BlockCall]207     pub fn all_branches(&self) -> &[BlockCall] {
208         &self.targets[..]
209     }
210 
211     /// Get all branch targets.
all_branches_mut(&mut self) -> &mut [BlockCall]212     pub fn all_branches_mut(&mut self) -> &mut [BlockCall] {
213         &mut self.targets[..]
214     }
215 
216     /// Get the signature of the function called with this exception
217     /// table.
signature(&self) -> SigRef218     pub fn signature(&self) -> SigRef {
219         self.sig
220     }
221 
222     /// Get a mutable handle to this exception table's signature.
signature_mut(&mut self) -> &mut SigRef223     pub(crate) fn signature_mut(&mut self) -> &mut SigRef {
224         &mut self.sig
225     }
226 
227     /// Get an iterator over context values.
contexts(&self) -> impl DoubleEndedIterator<Item = Value>228     pub(crate) fn contexts(&self) -> impl DoubleEndedIterator<Item = Value> {
229         self.items.iter().filter_map(|item| match item {
230             InternalExceptionTableItem::Context(ctx) => Some(*ctx),
231             _ => None,
232         })
233     }
234 
235     /// Get a mutable iterator over context values.
contexts_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut Value>236     pub(crate) fn contexts_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut Value> {
237         self.items.iter_mut().filter_map(|item| match item {
238             InternalExceptionTableItem::Context(ctx) => Some(ctx),
239             _ => None,
240         })
241     }
242 
243     /// Clears all entries in this exception table, but leaves the function signature.
clear(&mut self)244     pub fn clear(&mut self) {
245         self.items.clear();
246         self.targets.clear();
247     }
248 }
249 
250 /// A wrapper for the context required to display a
251 /// [ExceptionTableData].
252 pub struct DisplayExceptionTable<'a> {
253     table: &'a ExceptionTableData,
254     pool: &'a ValueListPool,
255 }
256 
257 impl<'a> Display for DisplayExceptionTable<'a> {
fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result258     fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
259         write!(
260             fmt,
261             "{}, {}, [",
262             self.table.sig,
263             self.table.normal_return().display(self.pool)
264         )?;
265         let mut first = true;
266         for item in self.table.items() {
267             if first {
268                 write!(fmt, " ")?;
269                 first = false;
270             } else {
271                 write!(fmt, ", ")?;
272             }
273             match item {
274                 ExceptionTableItem::Tag(tag, block_call) => {
275                     write!(fmt, "{}: {}", tag, block_call.display(self.pool))?;
276                 }
277                 ExceptionTableItem::Default(block_call) => {
278                     write!(fmt, "default: {}", block_call.display(self.pool))?;
279                 }
280                 ExceptionTableItem::Context(ctx) => {
281                     write!(fmt, "context {ctx}")?;
282                 }
283             }
284         }
285         let space = if first { "" } else { " " };
286         write!(fmt, "{space}]")?;
287         Ok(())
288     }
289 }
290