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