1 //! State of the Wasm stack for translation into CLIF. 2 //! 3 //! The `FuncTranslationStacks` struct defined in this module is used to keep 4 //! track of the WebAssembly value and control stacks during the translation of 5 //! a single function. 6 7 use cranelift_codegen::ir::{self, Block, ExceptionTag, Inst, Value}; 8 use cranelift_frontend::FunctionBuilder; 9 use std::vec::Vec; 10 use wasmtime_environ::FrameStackShape; 11 12 /// Information about the presence of an associated `else` for an `if`, or the 13 /// lack thereof. 14 #[derive(Debug)] 15 pub enum ElseData { 16 /// The `if` does not already have an `else` block. 17 /// 18 /// This doesn't mean that it will never have an `else`, just that we 19 /// haven't seen it yet. 20 NoElse { 21 /// If we discover that we need an `else` block, this is the jump 22 /// instruction that needs to be fixed up to point to the new `else` 23 /// block rather than the destination block after the `if...end`. 24 branch_inst: Inst, 25 26 /// The placeholder block we're replacing. 27 placeholder: Block, 28 }, 29 30 /// We have already allocated an `else` block. 31 /// 32 /// Usually we don't know whether we will hit an `if .. end` or an `if 33 /// .. else .. end`, but sometimes we can tell based on the block's type 34 /// signature that the signature is not valid if there isn't an `else`. In 35 /// these cases, we pre-allocate the `else` block. 36 WithElse { 37 /// This is the `else` block. 38 else_block: Block, 39 }, 40 } 41 42 /// A control stack frame can be an `if`, a `block` or a `loop`, each one having the following 43 /// fields: 44 /// 45 /// - `destination`: reference to the `Block` that will hold the code after the control block; 46 /// - `num_return_values`: number of values returned by the control block; 47 /// - `original_stack_size`: size of the value stack at the beginning of the control block. 48 /// 49 /// The `loop` frame has a `header` field that references the `Block` that contains the beginning 50 /// of the body of the loop. 51 #[derive(Debug)] 52 pub enum ControlStackFrame { 53 If { 54 destination: Block, 55 else_data: ElseData, 56 num_param_values: usize, 57 num_return_values: usize, 58 original_stack_size: usize, 59 exit_is_branched_to: bool, 60 blocktype: wasmparser::BlockType, 61 /// Was the head of the `if` reachable? 62 head_is_reachable: bool, 63 /// What was the reachability at the end of the consequent? 64 /// 65 /// This is `None` until we're finished translating the consequent, and 66 /// is set to `Some` either by hitting an `else` when we will begin 67 /// translating the alternative, or by hitting an `end` in which case 68 /// there is no alternative. 69 consequent_ends_reachable: Option<bool>, 70 // Note: no need for `alternative_ends_reachable` because that is just 71 // `state.reachable` when we hit the `end` in the `if .. else .. end`. 72 }, 73 Block { 74 destination: Block, 75 num_param_values: usize, 76 num_return_values: usize, 77 original_stack_size: usize, 78 exit_is_branched_to: bool, 79 /// If this block is a try-table block, the handler state 80 /// checkpoint to rewind to when we leave the block, and the 81 /// list of catch blocks to seal when done. 82 try_table_info: Option<(HandlerStateCheckpoint, Vec<Block>)>, 83 }, 84 Loop { 85 destination: Block, 86 header: Block, 87 num_param_values: usize, 88 num_return_values: usize, 89 original_stack_size: usize, 90 }, 91 } 92 93 /// Helper methods for the control stack objects. 94 impl ControlStackFrame { num_return_values(&self) -> usize95 pub fn num_return_values(&self) -> usize { 96 match *self { 97 Self::If { 98 num_return_values, .. 99 } 100 | Self::Block { 101 num_return_values, .. 102 } 103 | Self::Loop { 104 num_return_values, .. 105 } => num_return_values, 106 } 107 } 108 num_param_values(&self) -> usize109 pub fn num_param_values(&self) -> usize { 110 match *self { 111 Self::If { 112 num_param_values, .. 113 } 114 | Self::Block { 115 num_param_values, .. 116 } 117 | Self::Loop { 118 num_param_values, .. 119 } => num_param_values, 120 } 121 } 122 following_code(&self) -> Block123 pub fn following_code(&self) -> Block { 124 match *self { 125 Self::If { destination, .. } 126 | Self::Block { destination, .. } 127 | Self::Loop { destination, .. } => destination, 128 } 129 } 130 br_destination(&self) -> Block131 pub fn br_destination(&self) -> Block { 132 match *self { 133 Self::If { destination, .. } | Self::Block { destination, .. } => destination, 134 Self::Loop { header, .. } => header, 135 } 136 } 137 138 /// Private helper. Use `truncate_value_stack_to_else_params()` or 139 /// `truncate_value_stack_to_original_size()` to restore value-stack state. original_stack_size(&self) -> usize140 fn original_stack_size(&self) -> usize { 141 match *self { 142 Self::If { 143 original_stack_size, 144 .. 145 } 146 | Self::Block { 147 original_stack_size, 148 .. 149 } 150 | Self::Loop { 151 original_stack_size, 152 .. 153 } => original_stack_size, 154 } 155 } 156 is_loop(&self) -> bool157 pub fn is_loop(&self) -> bool { 158 match *self { 159 Self::If { .. } | Self::Block { .. } => false, 160 Self::Loop { .. } => true, 161 } 162 } 163 exit_is_branched_to(&self) -> bool164 pub fn exit_is_branched_to(&self) -> bool { 165 match *self { 166 Self::If { 167 exit_is_branched_to, 168 .. 169 } 170 | Self::Block { 171 exit_is_branched_to, 172 .. 173 } => exit_is_branched_to, 174 Self::Loop { .. } => false, 175 } 176 } 177 set_branched_to_exit(&mut self)178 pub fn set_branched_to_exit(&mut self) { 179 match *self { 180 Self::If { 181 ref mut exit_is_branched_to, 182 .. 183 } 184 | Self::Block { 185 ref mut exit_is_branched_to, 186 .. 187 } => *exit_is_branched_to = true, 188 Self::Loop { .. } => {} 189 } 190 } 191 192 /// Pop values from the value stack so that it is left at the 193 /// input-parameters to an else-block. truncate_value_stack_to_else_params( &self, stack: &mut Vec<Value>, stack_shape: &mut Vec<FrameStackShape>, )194 pub fn truncate_value_stack_to_else_params( 195 &self, 196 stack: &mut Vec<Value>, 197 stack_shape: &mut Vec<FrameStackShape>, 198 ) { 199 debug_assert!(matches!(self, &ControlStackFrame::If { .. })); 200 stack.truncate(self.original_stack_size()); 201 stack_shape.truncate(self.original_stack_size()); 202 } 203 204 /// Pop values from the value stack so that it is left at the state it was 205 /// before this control-flow frame. truncate_value_stack_to_original_size( &self, stack: &mut Vec<Value>, stack_shape: &mut Vec<FrameStackShape>, )206 pub fn truncate_value_stack_to_original_size( 207 &self, 208 stack: &mut Vec<Value>, 209 stack_shape: &mut Vec<FrameStackShape>, 210 ) { 211 // The "If" frame pushes its parameters twice, so they're available to the else block 212 // (see also `FuncTranslationStacks::push_if`). 213 // Yet, the original_stack_size member accounts for them only once, so that the else 214 // block can see the same number of parameters as the consequent block. As a matter of 215 // fact, we need to subtract an extra number of parameter values for if blocks. 216 let num_duplicated_params = match self { 217 &ControlStackFrame::If { 218 num_param_values, .. 219 } => { 220 debug_assert!(num_param_values <= self.original_stack_size()); 221 num_param_values 222 } 223 _ => 0, 224 }; 225 226 let new_len = self.original_stack_size() - num_duplicated_params; 227 stack.truncate(new_len); 228 stack_shape.truncate(new_len); 229 } 230 231 /// Restore the catch-handlers as they were outside of this block. restore_catch_handlers( &self, handlers: &mut HandlerState, builder: &mut FunctionBuilder, )232 pub fn restore_catch_handlers( 233 &self, 234 handlers: &mut HandlerState, 235 builder: &mut FunctionBuilder, 236 ) { 237 match self { 238 ControlStackFrame::Block { 239 try_table_info: Some((ckpt, catch_blocks)), 240 .. 241 } => { 242 handlers.restore_checkpoint(*ckpt); 243 for block in catch_blocks { 244 builder.seal_block(*block); 245 } 246 } 247 _ => {} 248 } 249 } 250 } 251 252 /// Keeps track of Wasm's operand and control stacks, as well as reachability 253 /// for each control frame. 254 pub struct FuncTranslationStacks { 255 /// A stack of values corresponding to the active values in the input wasm function at this 256 /// point. 257 pub(crate) stack: Vec<Value>, 258 /// "Shape" of stack at each index, if emitting debug instrumentation. 259 /// 260 /// When we pop `stack`, we automatically pop `stack_shape` as 261 /// well, but we never push automatically; this enables us to 262 /// determine which values are new and need to be flushed to 263 /// memory after translating an operator. 264 pub(crate) stack_shape: Vec<FrameStackShape>, 265 /// A stack of active control flow operations at this point in the input wasm function. 266 pub(crate) control_stack: Vec<ControlStackFrame>, 267 /// Exception handler state, updated as we enter and exit 268 /// `try_table` scopes and attached to each call that we make. 269 pub(crate) handlers: HandlerState, 270 /// Is the current translation state still reachable? This is false when translating operators 271 /// like End, Return, or Unreachable. 272 pub(crate) reachable: bool, 273 } 274 275 // Public methods that are exposed to non- API consumers. 276 impl FuncTranslationStacks { 277 /// True if the current translation state expresses reachable code, false if it is unreachable. 278 #[inline] reachable(&self) -> bool279 pub fn reachable(&self) -> bool { 280 self.reachable 281 } 282 } 283 284 impl FuncTranslationStacks { 285 /// Construct a new, empty, `FuncTranslationStacks` new() -> Self286 pub(crate) fn new() -> Self { 287 Self { 288 stack: Vec::new(), 289 stack_shape: Vec::new(), 290 control_stack: Vec::new(), 291 handlers: HandlerState::default(), 292 reachable: true, 293 } 294 } 295 clear(&mut self)296 fn clear(&mut self) { 297 debug_assert!(self.stack.is_empty()); 298 debug_assert!(self.stack_shape.is_empty()); 299 debug_assert!(self.control_stack.is_empty()); 300 debug_assert!(self.handlers.is_empty()); 301 self.reachable = true; 302 } 303 304 /// Initialize the state for compiling a function with the given signature. 305 /// 306 /// This resets the state to containing only a single block representing the whole function. 307 /// The exit block is the last block in the function which will contain the return instruction. initialize(&mut self, sig: &ir::Signature, exit_block: Block)308 pub(crate) fn initialize(&mut self, sig: &ir::Signature, exit_block: Block) { 309 self.clear(); 310 self.push_block( 311 exit_block, 312 0, 313 sig.returns 314 .iter() 315 .filter(|arg| arg.purpose == ir::ArgumentPurpose::Normal) 316 .count(), 317 ); 318 } 319 320 /// Push a value. push1(&mut self, val: Value)321 pub(crate) fn push1(&mut self, val: Value) { 322 self.stack.push(val); 323 } 324 325 /// Push two values. push2(&mut self, val1: Value, val2: Value)326 pub(crate) fn push2(&mut self, val1: Value, val2: Value) { 327 self.stack.push(val1); 328 self.stack.push(val2); 329 } 330 331 /// Push multiple values. pushn(&mut self, vals: &[Value])332 pub(crate) fn pushn(&mut self, vals: &[Value]) { 333 self.stack.extend_from_slice(vals); 334 } 335 336 /// Pop one value. pop1(&mut self) -> Value337 pub(crate) fn pop1(&mut self) -> Value { 338 self.pop_stack_shape(1); 339 self.stack 340 .pop() 341 .expect("attempted to pop a value from an empty stack") 342 } 343 344 /// Peek at the top of the stack without popping it. peek1(&self) -> Value345 pub(crate) fn peek1(&self) -> Value { 346 *self 347 .stack 348 .last() 349 .expect("attempted to peek at a value on an empty stack") 350 } 351 352 /// Pop two values. Return them in the order they were pushed. pop2(&mut self) -> (Value, Value)353 pub(crate) fn pop2(&mut self) -> (Value, Value) { 354 self.pop_stack_shape(2); 355 let v2 = self.stack.pop().unwrap(); 356 let v1 = self.stack.pop().unwrap(); 357 (v1, v2) 358 } 359 360 /// Pop three values. Return them in the order they were pushed. pop3(&mut self) -> (Value, Value, Value)361 pub(crate) fn pop3(&mut self) -> (Value, Value, Value) { 362 self.pop_stack_shape(3); 363 let v3 = self.stack.pop().unwrap(); 364 let v2 = self.stack.pop().unwrap(); 365 let v1 = self.stack.pop().unwrap(); 366 (v1, v2, v3) 367 } 368 369 /// Pop four values. Return them in the order they were pushed. pop4(&mut self) -> (Value, Value, Value, Value)370 pub(crate) fn pop4(&mut self) -> (Value, Value, Value, Value) { 371 self.pop_stack_shape(4); 372 let v4 = self.stack.pop().unwrap(); 373 let v3 = self.stack.pop().unwrap(); 374 let v2 = self.stack.pop().unwrap(); 375 let v1 = self.stack.pop().unwrap(); 376 (v1, v2, v3, v4) 377 } 378 379 /// Pop five values. Return them in the order they were pushed. pop5(&mut self) -> (Value, Value, Value, Value, Value)380 pub(crate) fn pop5(&mut self) -> (Value, Value, Value, Value, Value) { 381 self.pop_stack_shape(5); 382 let v5 = self.stack.pop().unwrap(); 383 let v4 = self.stack.pop().unwrap(); 384 let v3 = self.stack.pop().unwrap(); 385 let v2 = self.stack.pop().unwrap(); 386 let v1 = self.stack.pop().unwrap(); 387 (v1, v2, v3, v4, v5) 388 } 389 390 /// Helper to ensure the stack size is at least as big as `n`; note that due to 391 /// `debug_assert` this will not execute in non-optimized builds. 392 #[inline] ensure_length_is_at_least(&self, n: usize)393 fn ensure_length_is_at_least(&self, n: usize) { 394 debug_assert!( 395 n <= self.stack.len(), 396 "attempted to access {} values but stack only has {} values", 397 n, 398 self.stack.len() 399 ) 400 } 401 402 /// Pop the top `n` values on the stack. 403 /// 404 /// The popped values are not returned. Use `peekn` to look at them before popping. popn(&mut self, n: usize)405 pub(crate) fn popn(&mut self, n: usize) { 406 self.ensure_length_is_at_least(n); 407 let new_len = self.stack.len() - n; 408 self.stack.truncate(new_len); 409 self.stack_shape.truncate(new_len); 410 } 411 pop_stack_shape(&mut self, n: usize)412 fn pop_stack_shape(&mut self, n: usize) { 413 // The `stack_shape` vec represents the *clean* slots (already 414 // flushed to memory); its length is always less than or equal 415 // to `stack`, but indices always correspond between the 416 // two. Thus a pop on `stack` may or may not pop something on 417 // `stack_shape`; but if `stack` is truncated down to a length 418 // L by some number of pops, truncating `stack_shape` to that 419 // same length L will pop exactly the right shapes and will 420 // ensure that any new pushes that are "dirty" will be 421 // correctly represented as such. 422 let new_len = self.stack.len() - n; 423 self.stack_shape.truncate(new_len); 424 } 425 426 /// Peek at the top `n` values on the stack in the order they were pushed. peekn(&self, n: usize) -> &[Value]427 pub(crate) fn peekn(&self, n: usize) -> &[Value] { 428 self.ensure_length_is_at_least(n); 429 &self.stack[self.stack.len() - n..] 430 } 431 432 /// Peek at the top `n` values on the stack in the order they were pushed. peekn_mut(&mut self, n: usize) -> &mut [Value]433 pub(crate) fn peekn_mut(&mut self, n: usize) -> &mut [Value] { 434 self.ensure_length_is_at_least(n); 435 let len = self.stack.len(); 436 &mut self.stack[len - n..] 437 } 438 push_block_impl( &mut self, following_code: Block, num_param_types: usize, num_result_types: usize, try_table_info: Option<(HandlerStateCheckpoint, Vec<Block>)>, )439 fn push_block_impl( 440 &mut self, 441 following_code: Block, 442 num_param_types: usize, 443 num_result_types: usize, 444 try_table_info: Option<(HandlerStateCheckpoint, Vec<Block>)>, 445 ) { 446 debug_assert!(num_param_types <= self.stack.len()); 447 self.control_stack.push(ControlStackFrame::Block { 448 destination: following_code, 449 original_stack_size: self.stack.len() - num_param_types, 450 num_param_values: num_param_types, 451 num_return_values: num_result_types, 452 exit_is_branched_to: false, 453 try_table_info, 454 }); 455 } 456 457 /// Push a block on the control stack. push_block( &mut self, following_code: Block, num_param_types: usize, num_result_types: usize, )458 pub(crate) fn push_block( 459 &mut self, 460 following_code: Block, 461 num_param_types: usize, 462 num_result_types: usize, 463 ) { 464 self.push_block_impl(following_code, num_param_types, num_result_types, None); 465 } 466 467 /// Push a try-table block on the control stack. push_try_table_block( &mut self, following_code: Block, catch_blocks: Vec<Block>, num_param_types: usize, num_result_types: usize, checkpoint: HandlerStateCheckpoint, )468 pub(crate) fn push_try_table_block( 469 &mut self, 470 following_code: Block, 471 catch_blocks: Vec<Block>, 472 num_param_types: usize, 473 num_result_types: usize, 474 checkpoint: HandlerStateCheckpoint, 475 ) { 476 self.push_block_impl( 477 following_code, 478 num_param_types, 479 num_result_types, 480 Some((checkpoint, catch_blocks)), 481 ); 482 } 483 484 /// Push a loop on the control stack. push_loop( &mut self, header: Block, following_code: Block, num_param_types: usize, num_result_types: usize, )485 pub(crate) fn push_loop( 486 &mut self, 487 header: Block, 488 following_code: Block, 489 num_param_types: usize, 490 num_result_types: usize, 491 ) { 492 debug_assert!(num_param_types <= self.stack.len()); 493 self.control_stack.push(ControlStackFrame::Loop { 494 header, 495 destination: following_code, 496 original_stack_size: self.stack.len() - num_param_types, 497 num_param_values: num_param_types, 498 num_return_values: num_result_types, 499 }); 500 } 501 502 /// Push an if on the control stack. push_if( &mut self, destination: Block, else_data: ElseData, num_param_types: usize, num_result_types: usize, blocktype: wasmparser::BlockType, )503 pub(crate) fn push_if( 504 &mut self, 505 destination: Block, 506 else_data: ElseData, 507 num_param_types: usize, 508 num_result_types: usize, 509 blocktype: wasmparser::BlockType, 510 ) { 511 debug_assert!(num_param_types <= self.stack.len()); 512 self.assert_debug_stack_is_synced(); 513 514 // Push a second copy of our `if`'s parameters on the stack. This lets 515 // us avoid saving them on the side in the `ControlStackFrame` for our 516 // `else` block (if it exists), which would require a second heap 517 // allocation. See also the comment in `translate_operator` for 518 // `Operator::Else`. 519 self.stack.reserve(num_param_types); 520 for i in (self.stack.len() - num_param_types)..self.stack.len() { 521 let val = self.stack[i]; 522 self.stack.push(val); 523 // Duplicate the stack-shape as well, if we're doing debug 524 // instrumentation. Note that we must have flushed 525 // everything before processing an `if`, so (as per the 526 // assert above) we can rely on either no shapes (if no 527 // instrumentation) or all shapes being present. 528 if !self.stack_shape.is_empty() { 529 let shape = self.stack_shape[i]; 530 self.stack_shape.push(shape); 531 } 532 } 533 534 self.control_stack.push(ControlStackFrame::If { 535 destination, 536 else_data, 537 original_stack_size: self.stack.len() - num_param_types, 538 num_param_values: num_param_types, 539 num_return_values: num_result_types, 540 exit_is_branched_to: false, 541 head_is_reachable: self.reachable, 542 consequent_ends_reachable: None, 543 blocktype, 544 }); 545 } 546 assert_debug_stack_is_synced(&self)547 pub(crate) fn assert_debug_stack_is_synced(&self) { 548 debug_assert!(self.stack_shape.is_empty() || self.stack_shape.len() == self.stack.len()); 549 } 550 } 551 552 /// Exception handler state. 553 /// 554 /// We update this state as we enter and exit `try_table` scopes. When 555 /// we visit a call, we use this state to attach handler info to a 556 /// `try_call` CLIF instruction. 557 /// 558 /// Note that although handlers are lexically-scoped, and we could 559 /// optimize away shadowing, this is fairly subtle, because handler 560 /// order also matters (two *distinct* tag indices in our module are 561 /// not necessarily distinct: tag imports can create aliasing). Rather 562 /// than attempt to keep an ordered map and also remove shadowing, we 563 /// follow the Wasm spec more closely: handlers are on "the stack" and 564 /// inner handlers win over outer handlers. Within a single 565 /// `try_table`, we push handlers *in reverse*, because the semantics 566 /// of handler matching in `try_table` are left-to-right; this allows 567 /// us to *flatten* the LIFO stack of `try_table`s with left-to-right 568 /// scans within a table into a single stack we scan backward from the 569 /// end. 570 pub struct HandlerState { 571 /// List of pairs mapping from CLIF-level exception tag to 572 /// CLIF-level block. We will have already filled in these blocks 573 /// with the appropriate branch implementation when we start the 574 /// `try_table` scope. 575 pub(crate) handlers: Vec<(Option<ExceptionTag>, Block)>, 576 } 577 578 impl core::default::Default for HandlerState { default() -> Self579 fn default() -> Self { 580 HandlerState { handlers: vec![] } 581 } 582 } 583 584 /// A checkpoint in the handler state. Can be restored in LIFO order 585 /// only: the last-taken checkpoint can be restored first, then the 586 /// one before it, etc. 587 #[derive(Clone, Copy, Debug)] 588 pub struct HandlerStateCheckpoint(usize); 589 590 impl HandlerState { 591 /// Set a given tag's handler to a given CLIF block. add_handler(&mut self, tag: Option<ExceptionTag>, block: Block)592 pub fn add_handler(&mut self, tag: Option<ExceptionTag>, block: Block) { 593 self.handlers.push((tag, block)); 594 } 595 596 /// Take a checkpoint. take_checkpoint(&self) -> HandlerStateCheckpoint597 pub fn take_checkpoint(&self) -> HandlerStateCheckpoint { 598 HandlerStateCheckpoint(self.handlers.len()) 599 } 600 601 /// Restore to a checkpoint. restore_checkpoint(&mut self, ckpt: HandlerStateCheckpoint)602 pub fn restore_checkpoint(&mut self, ckpt: HandlerStateCheckpoint) { 603 assert!(ckpt.0 <= self.handlers.len()); 604 self.handlers.truncate(ckpt.0); 605 } 606 607 /// Get an iterator over handlers. The exception-matching 608 /// semantics are to take the *first* match in this sequence; that 609 /// is, this returns the sequence of handlers latest-first (top of 610 /// stack first). handlers(&self) -> impl Iterator<Item = (Option<ExceptionTag>, Block)> + '_611 pub fn handlers(&self) -> impl Iterator<Item = (Option<ExceptionTag>, Block)> + '_ { 612 self.handlers 613 .iter() 614 .map(|(tag, block)| (*tag, *block)) 615 .rev() 616 } 617 618 /// Are there no handlers registered? is_empty(&self) -> bool619 pub fn is_empty(&self) -> bool { 620 self.handlers.is_empty() 621 } 622 } 623