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