cranelift_codegen/ir/exception_table.rs
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
19use crate::ir::entities::{ExceptionTag, SigRef};
20use crate::ir::instructions::ValueListPool;
21use crate::ir::{BlockCall, Value};
22use alloc::vec::Vec;
23use core::fmt::{self, Display, Formatter};
24#[cfg(feature = "enable-serde")]
25use 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))]
51pub 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)]
81pub 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))]
104enum InternalExceptionTableItem {
105 Tag(ExceptionTag, u32),
106 Default(u32),
107 Context(Value),
108}
109
110impl 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 [`isa::CallConv`] for
133 /// more details.)
134 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.
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.
173 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.
182 pub fn normal_return(&self) -> &BlockCall {
183 self.targets.last().unwrap()
184 }
185
186 /// Get the default target for the non-exceptional return case.
187 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.
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.
207 pub fn all_branches(&self) -> &[BlockCall] {
208 &self.targets[..]
209 }
210
211 /// Get all branch targets.
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.
218 pub fn signature(&self) -> SigRef {
219 self.sig
220 }
221
222 /// Get a mutable handle to this exception table's signature.
223 pub(crate) fn signature_mut(&mut self) -> &mut SigRef {
224 &mut self.sig
225 }
226
227 /// Get an iterator over context values.
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.
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.
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].
252pub struct DisplayExceptionTable<'a> {
253 table: &'a ExceptionTableData,
254 pool: &'a ValueListPool,
255}
256
257impl<'a> Display for DisplayExceptionTable<'a> {
258 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}