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