194ec88eaSChris Fallin //! Exception tables: catch handlers on `try_call` instructions.
294ec88eaSChris Fallin //!
394ec88eaSChris Fallin //! An exception table describes where execution flows after returning
494ec88eaSChris Fallin //! from `try_call`. It contains both the "normal" destination -- the
594ec88eaSChris Fallin //! block to branch to when the function returns without throwing an
694ec88eaSChris Fallin //! exception -- and any "catch" destinations associated with
794ec88eaSChris Fallin //! particular exception tags. Each target indicates the arguments to
894ec88eaSChris Fallin //! pass to the block that receives control.
994ec88eaSChris Fallin //!
1094ec88eaSChris Fallin //! Like other side-tables (e.g., jump tables), each exception table
1194ec88eaSChris Fallin //! must be used by only one instruction. Sharing is not permitted
1294ec88eaSChris Fallin //! because it can complicate transforms (how does one change the
1394ec88eaSChris Fallin //! table used by only one instruction if others also use it?).
1494ec88eaSChris Fallin //!
1594ec88eaSChris Fallin //! In order to allow the `try_call` instruction itself to remain
1694ec88eaSChris Fallin //! small, the exception table also contains the signature ID of the
1794ec88eaSChris Fallin //! called function.
1894ec88eaSChris Fallin 
1994ec88eaSChris Fallin use crate::ir::entities::{ExceptionTag, SigRef};
2094ec88eaSChris Fallin use crate::ir::instructions::ValueListPool;
214590076fSChris Fallin use crate::ir::{BlockCall, Value};
2294ec88eaSChris Fallin use alloc::vec::Vec;
2394ec88eaSChris Fallin use core::fmt::{self, Display, Formatter};
2494ec88eaSChris Fallin #[cfg(feature = "enable-serde")]
2594ec88eaSChris Fallin use serde_derive::{Deserialize, Serialize};
2694ec88eaSChris Fallin 
2794ec88eaSChris Fallin /// Contents of an exception table.
2894ec88eaSChris Fallin ///
294590076fSChris Fallin /// An exception table consists of a "no exception" ("normal")
304590076fSChris Fallin /// destination block-call, and a series of exceptional destination
314590076fSChris Fallin /// block-calls associated with tags.
324590076fSChris Fallin ///
334590076fSChris Fallin /// The exceptional tags can also be interspersed with "dynamic
344590076fSChris Fallin /// context" entries, which result in a particular value being stored
354590076fSChris Fallin /// in the stack frame and accessible at an offset given in the
364590076fSChris Fallin /// compiled exception-table metadata. This is needed for some kinds
374590076fSChris Fallin /// of tag-matching where different dynamic instances of tags may
384590076fSChris Fallin /// exist (e.g., in the WebAssembly exception-handling proposal).
394590076fSChris Fallin ///
404590076fSChris Fallin /// The sequence of targets is semantically a list of
414590076fSChris Fallin /// context-or-tagged-blockcall; e.g., `[context v0, tag1: block1(v1,
424590076fSChris Fallin /// v2), context v2, tag2: block2(), tag3: block3()]`.
434590076fSChris Fallin ///
444590076fSChris Fallin /// The "no exception" target can be accessed through the
454590076fSChris Fallin /// `normal_return` and `normal_return_mut` functions. Exceptional
464590076fSChris Fallin /// catch clauses may be iterated using the `catches` and
474590076fSChris Fallin /// `catches_mut` functions.  All targets may be iterated over using
484590076fSChris Fallin /// the `all_targets` and `all_targets_mut` functions.
4994ec88eaSChris Fallin #[derive(Debug, Clone, PartialEq, Hash)]
5094ec88eaSChris Fallin #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
5194ec88eaSChris Fallin pub struct ExceptionTableData {
5294ec88eaSChris Fallin     /// All BlockCalls packed together. This is necessary because the
5394ec88eaSChris Fallin     /// rest of the compiler expects to be able to grab a slice of
5494ec88eaSChris Fallin     /// branch targets for any branch instruction. The last BlockCall
554590076fSChris Fallin     /// is the normal-return destination, and the rest are referred to
564590076fSChris Fallin     /// by index by the `items` below.
5794ec88eaSChris Fallin     targets: Vec<BlockCall>,
5894ec88eaSChris Fallin 
594590076fSChris Fallin     /// Exception-table items.
604590076fSChris Fallin     ///
614590076fSChris Fallin     /// This internal representation for items is like
624590076fSChris Fallin     /// `ExceptionTableItem` except that it has indices that refer to
634590076fSChris Fallin     /// `targets` above.
6494ec88eaSChris Fallin     ///
6594ec88eaSChris Fallin     /// A tag value of `None` indicates a catch-all handler. The
6694ec88eaSChris Fallin     /// catch-all handler matches only if no other handler matches,
6794ec88eaSChris Fallin     /// regardless of the order in this vector.
6894ec88eaSChris Fallin     ///
6994ec88eaSChris Fallin     /// `tags[i]` corresponds to `targets[i]`. Note that there will be
7094ec88eaSChris Fallin     /// one more `targets` element than `tags` because the last
7194ec88eaSChris Fallin     /// element in `targets` is the normal-return path.
724590076fSChris Fallin     items: Vec<InternalExceptionTableItem>,
7394ec88eaSChris Fallin 
7494ec88eaSChris Fallin     /// The signature of the function whose invocation is associated
7594ec88eaSChris Fallin     /// with this handler table.
7694ec88eaSChris Fallin     sig: SigRef,
7794ec88eaSChris Fallin }
7894ec88eaSChris Fallin 
794590076fSChris Fallin /// A single item in the match-list of an exception table.
804590076fSChris Fallin #[derive(Clone, Debug)]
814590076fSChris Fallin pub enum ExceptionTableItem {
824590076fSChris Fallin     /// A tag match, taking the specified block-call destination if
834590076fSChris Fallin     /// the tag matches the one in the thrown exception. (The match
844590076fSChris Fallin     /// predicate is up to the runtime; Cranelift only emits metadata
854590076fSChris Fallin     /// containing this tag.)
864590076fSChris Fallin     Tag(ExceptionTag, BlockCall),
874590076fSChris Fallin     /// A default match, always taking the specified block-call
884590076fSChris Fallin     /// destination.
894590076fSChris Fallin     Default(BlockCall),
904590076fSChris Fallin     /// A dynamic context update, applying to all tags until the next
914590076fSChris Fallin     /// update. (Cranelift does not interpret this context, but only
924590076fSChris Fallin     /// provides information to the runtime regarding where to find
934590076fSChris Fallin     /// it.)
944590076fSChris Fallin     Context(Value),
954590076fSChris Fallin }
964590076fSChris Fallin 
974590076fSChris Fallin /// Our internal representation of exception-table items.
984590076fSChris Fallin ///
994590076fSChris Fallin /// This is a version of `ExceptionTableItem` with block-calls
1004590076fSChris Fallin /// out-of-lined so that we can provide the slice externally. Each
1014590076fSChris Fallin /// block-call is referenced via an index.
1024590076fSChris Fallin #[derive(Clone, Debug, PartialEq, Hash)]
1034590076fSChris Fallin #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
1044590076fSChris Fallin enum InternalExceptionTableItem {
1054590076fSChris Fallin     Tag(ExceptionTag, u32),
1064590076fSChris Fallin     Default(u32),
1074590076fSChris Fallin     Context(Value),
1084590076fSChris Fallin }
1094590076fSChris Fallin 
11094ec88eaSChris Fallin impl ExceptionTableData {
11194ec88eaSChris Fallin     /// Create new exception-table data.
11294ec88eaSChris Fallin     ///
11394ec88eaSChris Fallin     /// This data represents the destinations upon return from
11494ec88eaSChris Fallin     /// `try_call` or `try_call_indirect` instruction. There are two
11594ec88eaSChris Fallin     /// possibilities: "normal return" (no exception thrown), or an
11694ec88eaSChris Fallin     /// exceptional return corresponding to one of the listed
11794ec88eaSChris Fallin     /// exception tags.
11894ec88eaSChris Fallin     ///
11994ec88eaSChris Fallin     /// The given tags are passed through to the metadata provided
12094ec88eaSChris Fallin     /// alongside the provided function body, and Cranelift itself
12194ec88eaSChris Fallin     /// does not implement an unwinder; thus, the meaning of the tags
12294ec88eaSChris Fallin     /// is ultimately up to the embedder of Cranelift. The tags are
12394ec88eaSChris Fallin     /// wrapped in `Option` to allow encoding a "catch-all" handler.
12494ec88eaSChris Fallin     ///
12594ec88eaSChris Fallin     /// The BlockCalls must have signatures that match the targeted
12694ec88eaSChris Fallin     /// blocks, as usual. These calls are allowed to use
12794ec88eaSChris Fallin     /// `BlockArg::TryCallRet` in the normal-return case, with types
12894ec88eaSChris Fallin     /// corresponding to the signature's return values, and
12994ec88eaSChris Fallin     /// `BlockArg::TryCallExn` in the exceptional-return cases, with
13094ec88eaSChris Fallin     /// types corresponding to native machine words and an arity
13194ec88eaSChris Fallin     /// corresponding to the number of payload values that the calling
132*bc4582c3SAlex Crichton     /// convention and platform support. (See [`CallConv`](crate::isa::CallConv) for
13394ec88eaSChris Fallin     /// more details.)
new( sig: SigRef, normal_return: BlockCall, matches: impl IntoIterator<Item = ExceptionTableItem>, ) -> Self13494ec88eaSChris Fallin     pub fn new(
13594ec88eaSChris Fallin         sig: SigRef,
13694ec88eaSChris Fallin         normal_return: BlockCall,
1374590076fSChris Fallin         matches: impl IntoIterator<Item = ExceptionTableItem>,
13894ec88eaSChris Fallin     ) -> Self {
13994ec88eaSChris Fallin         let mut targets = vec![];
1404590076fSChris Fallin         let mut items = vec![];
1414590076fSChris Fallin         for item in matches {
1424590076fSChris Fallin             let target_idx = u32::try_from(targets.len()).unwrap();
1434590076fSChris Fallin             match item {
1444590076fSChris Fallin                 ExceptionTableItem::Tag(tag, target) => {
1454590076fSChris Fallin                     items.push(InternalExceptionTableItem::Tag(tag, target_idx));
14694ec88eaSChris Fallin                     targets.push(target);
14794ec88eaSChris Fallin                 }
1484590076fSChris Fallin                 ExceptionTableItem::Default(target) => {
1494590076fSChris Fallin                     items.push(InternalExceptionTableItem::Default(target_idx));
1504590076fSChris Fallin                     targets.push(target);
1514590076fSChris Fallin                 }
1524590076fSChris Fallin                 ExceptionTableItem::Context(ctx) => {
1534590076fSChris Fallin                     items.push(InternalExceptionTableItem::Context(ctx));
1544590076fSChris Fallin                 }
1554590076fSChris Fallin             }
1564590076fSChris Fallin         }
15794ec88eaSChris Fallin         targets.push(normal_return);
15894ec88eaSChris Fallin 
1594590076fSChris Fallin         ExceptionTableData {
1604590076fSChris Fallin             targets,
1614590076fSChris Fallin             items,
1624590076fSChris Fallin             sig,
1634590076fSChris Fallin         }
16494ec88eaSChris Fallin     }
16594ec88eaSChris Fallin 
16694ec88eaSChris Fallin     /// Return a value that can display the contents of this exception
16794ec88eaSChris Fallin     /// table.
display<'a>(&'a self, pool: &'a ValueListPool) -> DisplayExceptionTable<'a>16894ec88eaSChris Fallin     pub fn display<'a>(&'a self, pool: &'a ValueListPool) -> DisplayExceptionTable<'a> {
16994ec88eaSChris Fallin         DisplayExceptionTable { table: self, pool }
17094ec88eaSChris Fallin     }
17194ec88eaSChris Fallin 
172968952abSNick Fitzgerald     /// Deep-clone this exception table.
deep_clone(&self, pool: &mut ValueListPool) -> Self173968952abSNick Fitzgerald     pub fn deep_clone(&self, pool: &mut ValueListPool) -> Self {
174968952abSNick Fitzgerald         Self {
175968952abSNick Fitzgerald             targets: self.targets.iter().map(|b| b.deep_clone(pool)).collect(),
1764590076fSChris Fallin             items: self.items.clone(),
177968952abSNick Fitzgerald             sig: self.sig,
178968952abSNick Fitzgerald         }
179968952abSNick Fitzgerald     }
180968952abSNick Fitzgerald 
18194ec88eaSChris Fallin     /// Get the default target for the non-exceptional return case.
normal_return(&self) -> &BlockCall18294ec88eaSChris Fallin     pub fn normal_return(&self) -> &BlockCall {
18394ec88eaSChris Fallin         self.targets.last().unwrap()
18494ec88eaSChris Fallin     }
18594ec88eaSChris Fallin 
18694ec88eaSChris Fallin     /// Get the default target for the non-exceptional return case.
normal_return_mut(&mut self) -> &mut BlockCall18794ec88eaSChris Fallin     pub fn normal_return_mut(&mut self) -> &mut BlockCall {
18894ec88eaSChris Fallin         self.targets.last_mut().unwrap()
18994ec88eaSChris Fallin     }
19094ec88eaSChris Fallin 
1914590076fSChris Fallin     /// Get the exception-catch items: dynamic context updates for
1924590076fSChris Fallin     /// interpreting tags, tag-associated targets, and catch-all
1934590076fSChris Fallin     /// targets.
items(&self) -> impl Iterator<Item = ExceptionTableItem> + '_1944590076fSChris Fallin     pub fn items(&self) -> impl Iterator<Item = ExceptionTableItem> + '_ {
1954590076fSChris Fallin         self.items.iter().map(|item| match item {
1964590076fSChris Fallin             InternalExceptionTableItem::Tag(tag, target_idx) => {
1974590076fSChris Fallin                 ExceptionTableItem::Tag(*tag, self.targets[usize::try_from(*target_idx).unwrap()])
19894ec88eaSChris Fallin             }
1994590076fSChris Fallin             InternalExceptionTableItem::Default(target_idx) => {
2004590076fSChris Fallin                 ExceptionTableItem::Default(self.targets[usize::try_from(*target_idx).unwrap()])
20194ec88eaSChris Fallin             }
2024590076fSChris Fallin             InternalExceptionTableItem::Context(ctx) => ExceptionTableItem::Context(*ctx),
2034590076fSChris Fallin         })
204968952abSNick Fitzgerald     }
205968952abSNick Fitzgerald 
20694ec88eaSChris Fallin     /// Get all branch targets.
all_branches(&self) -> &[BlockCall]20794ec88eaSChris Fallin     pub fn all_branches(&self) -> &[BlockCall] {
20894ec88eaSChris Fallin         &self.targets[..]
20994ec88eaSChris Fallin     }
21094ec88eaSChris Fallin 
21194ec88eaSChris Fallin     /// Get all branch targets.
all_branches_mut(&mut self) -> &mut [BlockCall]21294ec88eaSChris Fallin     pub fn all_branches_mut(&mut self) -> &mut [BlockCall] {
21394ec88eaSChris Fallin         &mut self.targets[..]
21494ec88eaSChris Fallin     }
21594ec88eaSChris Fallin 
21694ec88eaSChris Fallin     /// Get the signature of the function called with this exception
21794ec88eaSChris Fallin     /// table.
signature(&self) -> SigRef21894ec88eaSChris Fallin     pub fn signature(&self) -> SigRef {
21994ec88eaSChris Fallin         self.sig
22094ec88eaSChris Fallin     }
2213932e8f1Sbjorn3 
222968952abSNick Fitzgerald     /// Get a mutable handle to this exception table's signature.
signature_mut(&mut self) -> &mut SigRef223968952abSNick Fitzgerald     pub(crate) fn signature_mut(&mut self) -> &mut SigRef {
224968952abSNick Fitzgerald         &mut self.sig
225968952abSNick Fitzgerald     }
226968952abSNick Fitzgerald 
2274590076fSChris Fallin     /// Get an iterator over context values.
contexts(&self) -> impl DoubleEndedIterator<Item = Value>2284590076fSChris Fallin     pub(crate) fn contexts(&self) -> impl DoubleEndedIterator<Item = Value> {
2294590076fSChris Fallin         self.items.iter().filter_map(|item| match item {
2304590076fSChris Fallin             InternalExceptionTableItem::Context(ctx) => Some(*ctx),
2314590076fSChris Fallin             _ => None,
2324590076fSChris Fallin         })
2333932e8f1Sbjorn3     }
234968952abSNick Fitzgerald 
2354590076fSChris Fallin     /// Get a mutable iterator over context values.
contexts_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut Value>2364590076fSChris Fallin     pub(crate) fn contexts_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut Value> {
2374590076fSChris Fallin         self.items.iter_mut().filter_map(|item| match item {
2384590076fSChris Fallin             InternalExceptionTableItem::Context(ctx) => Some(ctx),
2394590076fSChris Fallin             _ => None,
2404590076fSChris Fallin         })
2414590076fSChris Fallin     }
242968952abSNick Fitzgerald 
2434590076fSChris Fallin     /// Clears all entries in this exception table, but leaves the function signature.
clear(&mut self)2444590076fSChris Fallin     pub fn clear(&mut self) {
2454590076fSChris Fallin         self.items.clear();
2464590076fSChris Fallin         self.targets.clear();
247968952abSNick Fitzgerald     }
24894ec88eaSChris Fallin }
24994ec88eaSChris Fallin 
25094ec88eaSChris Fallin /// A wrapper for the context required to display a
25194ec88eaSChris Fallin /// [ExceptionTableData].
25294ec88eaSChris Fallin pub struct DisplayExceptionTable<'a> {
25394ec88eaSChris Fallin     table: &'a ExceptionTableData,
25494ec88eaSChris Fallin     pool: &'a ValueListPool,
25594ec88eaSChris Fallin }
25694ec88eaSChris Fallin 
25794ec88eaSChris Fallin impl<'a> Display for DisplayExceptionTable<'a> {
fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result25894ec88eaSChris Fallin     fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
25994ec88eaSChris Fallin         write!(
26094ec88eaSChris Fallin             fmt,
26194ec88eaSChris Fallin             "{}, {}, [",
26294ec88eaSChris Fallin             self.table.sig,
26394ec88eaSChris Fallin             self.table.normal_return().display(self.pool)
26494ec88eaSChris Fallin         )?;
26594ec88eaSChris Fallin         let mut first = true;
2664590076fSChris Fallin         for item in self.table.items() {
26794ec88eaSChris Fallin             if first {
26894ec88eaSChris Fallin                 write!(fmt, " ")?;
26994ec88eaSChris Fallin                 first = false;
27094ec88eaSChris Fallin             } else {
27194ec88eaSChris Fallin                 write!(fmt, ", ")?;
27294ec88eaSChris Fallin             }
2734590076fSChris Fallin             match item {
2744590076fSChris Fallin                 ExceptionTableItem::Tag(tag, block_call) => {
27594ec88eaSChris Fallin                     write!(fmt, "{}: {}", tag, block_call.display(self.pool))?;
2764590076fSChris Fallin                 }
2774590076fSChris Fallin                 ExceptionTableItem::Default(block_call) => {
27894ec88eaSChris Fallin                     write!(fmt, "default: {}", block_call.display(self.pool))?;
27994ec88eaSChris Fallin                 }
2804590076fSChris Fallin                 ExceptionTableItem::Context(ctx) => {
2814590076fSChris Fallin                     write!(fmt, "context {ctx}")?;
2824590076fSChris Fallin                 }
2834590076fSChris Fallin             }
28494ec88eaSChris Fallin         }
28594ec88eaSChris Fallin         let space = if first { "" } else { " " };
28694ec88eaSChris Fallin         write!(fmt, "{space}]")?;
28794ec88eaSChris Fallin         Ok(())
28894ec88eaSChris Fallin     }
28994ec88eaSChris Fallin }
290