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 /// Get the default target for the non-exceptional return case. 110 pub fn normal_return(&self) -> &BlockCall { 111 self.targets.last().unwrap() 112 } 113 114 /// Get the default target for the non-exceptional return case. 115 pub fn normal_return_mut(&mut self) -> &mut BlockCall { 116 self.targets.last_mut().unwrap() 117 } 118 119 /// Get the targets for exceptional return cases, together with 120 /// their tags. 121 pub fn catches(&self) -> impl Iterator<Item = (Option<ExceptionTag>, &BlockCall)> + '_ { 122 self.tags 123 .iter() 124 .map(|tag| tag.expand()) 125 // Skips the last entry of `targets` (the normal return) 126 // because `tags` is one element shorter. 127 .zip(self.targets.iter()) 128 } 129 130 /// Get the targets for exceptional return cases, together with 131 /// their tags. 132 pub fn catches_mut( 133 &mut self, 134 ) -> impl Iterator<Item = (Option<ExceptionTag>, &mut BlockCall)> + '_ { 135 self.tags 136 .iter() 137 .map(|tag| tag.expand()) 138 // Skips the last entry of `targets` (the normal return) 139 // because `tags` is one element shorter. 140 .zip(self.targets.iter_mut()) 141 } 142 143 /// Get all branch targets. 144 pub fn all_branches(&self) -> &[BlockCall] { 145 &self.targets[..] 146 } 147 148 /// Get all branch targets. 149 pub fn all_branches_mut(&mut self) -> &mut [BlockCall] { 150 &mut self.targets[..] 151 } 152 153 /// Get the signature of the function called with this exception 154 /// table. 155 pub fn signature(&self) -> SigRef { 156 self.sig 157 } 158 159 /// Clears all entries in this exception table, but leaves the function signature. 160 pub fn clear(&mut self) { 161 self.tags.clear(); 162 self.targets.clear(); 163 } 164 } 165 166 /// A wrapper for the context required to display a 167 /// [ExceptionTableData]. 168 pub struct DisplayExceptionTable<'a> { 169 table: &'a ExceptionTableData, 170 pool: &'a ValueListPool, 171 } 172 173 impl<'a> Display for DisplayExceptionTable<'a> { 174 fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { 175 write!( 176 fmt, 177 "{}, {}, [", 178 self.table.sig, 179 self.table.normal_return().display(self.pool) 180 )?; 181 let mut first = true; 182 for (tag, block_call) in self.table.catches() { 183 if first { 184 write!(fmt, " ")?; 185 first = false; 186 } else { 187 write!(fmt, ", ")?; 188 } 189 if let Some(tag) = tag { 190 write!(fmt, "{}: {}", tag, block_call.display(self.pool))?; 191 } else { 192 write!(fmt, "default: {}", block_call.display(self.pool))?; 193 } 194 } 195 let space = if first { "" } else { " " }; 196 write!(fmt, "{space}]")?; 197 Ok(()) 198 } 199 } 200