Path: blob/main/crates/cranelift/src/translate/stack.rs
1692 views
//! State of the Wasm stack for translation into CLIF.1//!2//! The `FuncTranslationStacks` struct defined in this module is used to keep3//! track of the WebAssembly value and control stacks during the translation of4//! a single function.56use cranelift_codegen::ir::{self, Block, ExceptionTag, Inst, Value};7use cranelift_frontend::FunctionBuilder;8use std::vec::Vec;910/// Information about the presence of an associated `else` for an `if`, or the11/// lack thereof.12#[derive(Debug)]13pub enum ElseData {14/// The `if` does not already have an `else` block.15///16/// This doesn't mean that it will never have an `else`, just that we17/// haven't seen it yet.18NoElse {19/// If we discover that we need an `else` block, this is the jump20/// instruction that needs to be fixed up to point to the new `else`21/// block rather than the destination block after the `if...end`.22branch_inst: Inst,2324/// The placeholder block we're replacing.25placeholder: Block,26},2728/// We have already allocated an `else` block.29///30/// Usually we don't know whether we will hit an `if .. end` or an `if31/// .. else .. end`, but sometimes we can tell based on the block's type32/// signature that the signature is not valid if there isn't an `else`. In33/// these cases, we pre-allocate the `else` block.34WithElse {35/// This is the `else` block.36else_block: Block,37},38}3940/// A control stack frame can be an `if`, a `block` or a `loop`, each one having the following41/// fields:42///43/// - `destination`: reference to the `Block` that will hold the code after the control block;44/// - `num_return_values`: number of values returned by the control block;45/// - `original_stack_size`: size of the value stack at the beginning of the control block.46///47/// The `loop` frame has a `header` field that references the `Block` that contains the beginning48/// of the body of the loop.49#[derive(Debug)]50pub enum ControlStackFrame {51If {52destination: Block,53else_data: ElseData,54num_param_values: usize,55num_return_values: usize,56original_stack_size: usize,57exit_is_branched_to: bool,58blocktype: wasmparser::BlockType,59/// Was the head of the `if` reachable?60head_is_reachable: bool,61/// What was the reachability at the end of the consequent?62///63/// This is `None` until we're finished translating the consequent, and64/// is set to `Some` either by hitting an `else` when we will begin65/// translating the alternative, or by hitting an `end` in which case66/// there is no alternative.67consequent_ends_reachable: Option<bool>,68// Note: no need for `alternative_ends_reachable` because that is just69// `state.reachable` when we hit the `end` in the `if .. else .. end`.70},71Block {72destination: Block,73num_param_values: usize,74num_return_values: usize,75original_stack_size: usize,76exit_is_branched_to: bool,77/// If this block is a try-table block, the handler state78/// checkpoint to rewind to when we leave the block, and the79/// list of catch blocks to seal when done.80try_table_info: Option<(HandlerStateCheckpoint, Vec<Block>)>,81},82Loop {83destination: Block,84header: Block,85num_param_values: usize,86num_return_values: usize,87original_stack_size: usize,88},89}9091/// Helper methods for the control stack objects.92impl ControlStackFrame {93pub fn num_return_values(&self) -> usize {94match *self {95Self::If {96num_return_values, ..97}98| Self::Block {99num_return_values, ..100}101| Self::Loop {102num_return_values, ..103} => num_return_values,104}105}106107pub fn num_param_values(&self) -> usize {108match *self {109Self::If {110num_param_values, ..111}112| Self::Block {113num_param_values, ..114}115| Self::Loop {116num_param_values, ..117} => num_param_values,118}119}120121pub fn following_code(&self) -> Block {122match *self {123Self::If { destination, .. }124| Self::Block { destination, .. }125| Self::Loop { destination, .. } => destination,126}127}128129pub fn br_destination(&self) -> Block {130match *self {131Self::If { destination, .. } | Self::Block { destination, .. } => destination,132Self::Loop { header, .. } => header,133}134}135136/// Private helper. Use `truncate_value_stack_to_else_params()` or137/// `truncate_value_stack_to_original_size()` to restore value-stack state.138fn original_stack_size(&self) -> usize {139match *self {140Self::If {141original_stack_size,142..143}144| Self::Block {145original_stack_size,146..147}148| Self::Loop {149original_stack_size,150..151} => original_stack_size,152}153}154155pub fn is_loop(&self) -> bool {156match *self {157Self::If { .. } | Self::Block { .. } => false,158Self::Loop { .. } => true,159}160}161162pub fn exit_is_branched_to(&self) -> bool {163match *self {164Self::If {165exit_is_branched_to,166..167}168| Self::Block {169exit_is_branched_to,170..171} => exit_is_branched_to,172Self::Loop { .. } => false,173}174}175176pub fn set_branched_to_exit(&mut self) {177match *self {178Self::If {179ref mut exit_is_branched_to,180..181}182| Self::Block {183ref mut exit_is_branched_to,184..185} => *exit_is_branched_to = true,186Self::Loop { .. } => {}187}188}189190/// Pop values from the value stack so that it is left at the191/// input-parameters to an else-block.192pub fn truncate_value_stack_to_else_params(&self, stack: &mut Vec<Value>) {193debug_assert!(matches!(self, &ControlStackFrame::If { .. }));194stack.truncate(self.original_stack_size());195}196197/// Pop values from the value stack so that it is left at the state it was198/// before this control-flow frame.199pub fn truncate_value_stack_to_original_size(&self, stack: &mut Vec<Value>) {200// The "If" frame pushes its parameters twice, so they're available to the else block201// (see also `FuncTranslationStacks::push_if`).202// Yet, the original_stack_size member accounts for them only once, so that the else203// block can see the same number of parameters as the consequent block. As a matter of204// fact, we need to subtract an extra number of parameter values for if blocks.205let num_duplicated_params = match self {206&ControlStackFrame::If {207num_param_values, ..208} => {209debug_assert!(num_param_values <= self.original_stack_size());210num_param_values211}212_ => 0,213};214stack.truncate(self.original_stack_size() - num_duplicated_params);215}216217/// Restore the catch-handlers as they were outside of this block.218pub fn restore_catch_handlers(219&self,220handlers: &mut HandlerState,221builder: &mut FunctionBuilder,222) {223match self {224ControlStackFrame::Block {225try_table_info: Some((ckpt, catch_blocks)),226..227} => {228handlers.restore_checkpoint(*ckpt);229for block in catch_blocks {230builder.seal_block(*block);231}232}233_ => {}234}235}236}237238/// Keeps track of Wasm's operand and control stacks, as well as reachability239/// for each control frame.240pub struct FuncTranslationStacks {241/// A stack of values corresponding to the active values in the input wasm function at this242/// point.243pub(crate) stack: Vec<Value>,244/// A stack of active control flow operations at this point in the input wasm function.245pub(crate) control_stack: Vec<ControlStackFrame>,246/// Exception handler state, updated as we enter and exit247/// `try_table` scopes and attached to each call that we make.248pub(crate) handlers: HandlerState,249/// Is the current translation state still reachable? This is false when translating operators250/// like End, Return, or Unreachable.251pub(crate) reachable: bool,252}253254// Public methods that are exposed to non- API consumers.255impl FuncTranslationStacks {256/// True if the current translation state expresses reachable code, false if it is unreachable.257#[inline]258pub fn reachable(&self) -> bool {259self.reachable260}261}262263impl FuncTranslationStacks {264/// Construct a new, empty, `FuncTranslationStacks`265pub(crate) fn new() -> Self {266Self {267stack: Vec::new(),268control_stack: Vec::new(),269handlers: HandlerState::default(),270reachable: true,271}272}273274fn clear(&mut self) {275debug_assert!(self.stack.is_empty());276debug_assert!(self.control_stack.is_empty());277debug_assert!(self.handlers.is_empty());278self.reachable = true;279}280281/// Initialize the state for compiling a function with the given signature.282///283/// This resets the state to containing only a single block representing the whole function.284/// The exit block is the last block in the function which will contain the return instruction.285pub(crate) fn initialize(&mut self, sig: &ir::Signature, exit_block: Block) {286self.clear();287self.push_block(288exit_block,2890,290sig.returns291.iter()292.filter(|arg| arg.purpose == ir::ArgumentPurpose::Normal)293.count(),294);295}296297/// Push a value.298pub(crate) fn push1(&mut self, val: Value) {299self.stack.push(val);300}301302/// Push two values.303pub(crate) fn push2(&mut self, val1: Value, val2: Value) {304self.stack.push(val1);305self.stack.push(val2);306}307308/// Push multiple values.309pub(crate) fn pushn(&mut self, vals: &[Value]) {310self.stack.extend_from_slice(vals);311}312313/// Pop one value.314pub(crate) fn pop1(&mut self) -> Value {315self.stack316.pop()317.expect("attempted to pop a value from an empty stack")318}319320/// Peek at the top of the stack without popping it.321pub(crate) fn peek1(&self) -> Value {322*self323.stack324.last()325.expect("attempted to peek at a value on an empty stack")326}327328/// Pop two values. Return them in the order they were pushed.329pub(crate) fn pop2(&mut self) -> (Value, Value) {330let v2 = self.stack.pop().unwrap();331let v1 = self.stack.pop().unwrap();332(v1, v2)333}334335/// Pop three values. Return them in the order they were pushed.336pub(crate) fn pop3(&mut self) -> (Value, Value, Value) {337let v3 = self.stack.pop().unwrap();338let v2 = self.stack.pop().unwrap();339let v1 = self.stack.pop().unwrap();340(v1, v2, v3)341}342343/// Pop four values. Return them in the order they were pushed.344pub(crate) fn pop4(&mut self) -> (Value, Value, Value, Value) {345let v4 = self.stack.pop().unwrap();346let v3 = self.stack.pop().unwrap();347let v2 = self.stack.pop().unwrap();348let v1 = self.stack.pop().unwrap();349(v1, v2, v3, v4)350}351352/// Pop five values. Return them in the order they were pushed.353pub(crate) fn pop5(&mut self) -> (Value, Value, Value, Value, Value) {354let v5 = self.stack.pop().unwrap();355let v4 = self.stack.pop().unwrap();356let v3 = self.stack.pop().unwrap();357let v2 = self.stack.pop().unwrap();358let v1 = self.stack.pop().unwrap();359(v1, v2, v3, v4, v5)360}361362/// Helper to ensure the stack size is at least as big as `n`; note that due to363/// `debug_assert` this will not execute in non-optimized builds.364#[inline]365fn ensure_length_is_at_least(&self, n: usize) {366debug_assert!(367n <= self.stack.len(),368"attempted to access {} values but stack only has {} values",369n,370self.stack.len()371)372}373374/// Pop the top `n` values on the stack.375///376/// The popped values are not returned. Use `peekn` to look at them before popping.377pub(crate) fn popn(&mut self, n: usize) {378self.ensure_length_is_at_least(n);379let new_len = self.stack.len() - n;380self.stack.truncate(new_len);381}382383/// Peek at the top `n` values on the stack in the order they were pushed.384pub(crate) fn peekn(&self, n: usize) -> &[Value] {385self.ensure_length_is_at_least(n);386&self.stack[self.stack.len() - n..]387}388389/// Peek at the top `n` values on the stack in the order they were pushed.390pub(crate) fn peekn_mut(&mut self, n: usize) -> &mut [Value] {391self.ensure_length_is_at_least(n);392let len = self.stack.len();393&mut self.stack[len - n..]394}395396fn push_block_impl(397&mut self,398following_code: Block,399num_param_types: usize,400num_result_types: usize,401try_table_info: Option<(HandlerStateCheckpoint, Vec<Block>)>,402) {403debug_assert!(num_param_types <= self.stack.len());404self.control_stack.push(ControlStackFrame::Block {405destination: following_code,406original_stack_size: self.stack.len() - num_param_types,407num_param_values: num_param_types,408num_return_values: num_result_types,409exit_is_branched_to: false,410try_table_info,411});412}413414/// Push a block on the control stack.415pub(crate) fn push_block(416&mut self,417following_code: Block,418num_param_types: usize,419num_result_types: usize,420) {421self.push_block_impl(following_code, num_param_types, num_result_types, None);422}423424/// Push a try-table block on the control stack.425pub(crate) fn push_try_table_block(426&mut self,427following_code: Block,428catch_blocks: Vec<Block>,429num_param_types: usize,430num_result_types: usize,431checkpoint: HandlerStateCheckpoint,432) {433self.push_block_impl(434following_code,435num_param_types,436num_result_types,437Some((checkpoint, catch_blocks)),438);439}440441/// Push a loop on the control stack.442pub(crate) fn push_loop(443&mut self,444header: Block,445following_code: Block,446num_param_types: usize,447num_result_types: usize,448) {449debug_assert!(num_param_types <= self.stack.len());450self.control_stack.push(ControlStackFrame::Loop {451header,452destination: following_code,453original_stack_size: self.stack.len() - num_param_types,454num_param_values: num_param_types,455num_return_values: num_result_types,456});457}458459/// Push an if on the control stack.460pub(crate) fn push_if(461&mut self,462destination: Block,463else_data: ElseData,464num_param_types: usize,465num_result_types: usize,466blocktype: wasmparser::BlockType,467) {468debug_assert!(num_param_types <= self.stack.len());469470// Push a second copy of our `if`'s parameters on the stack. This lets471// us avoid saving them on the side in the `ControlStackFrame` for our472// `else` block (if it exists), which would require a second heap473// allocation. See also the comment in `translate_operator` for474// `Operator::Else`.475self.stack.reserve(num_param_types);476for i in (self.stack.len() - num_param_types)..self.stack.len() {477let val = self.stack[i];478self.stack.push(val);479}480481self.control_stack.push(ControlStackFrame::If {482destination,483else_data,484original_stack_size: self.stack.len() - num_param_types,485num_param_values: num_param_types,486num_return_values: num_result_types,487exit_is_branched_to: false,488head_is_reachable: self.reachable,489consequent_ends_reachable: None,490blocktype,491});492}493}494495/// Exception handler state.496///497/// We update this state as we enter and exit `try_table` scopes. When498/// we visit a call, we use this state to attach handler info to a499/// `try_call` CLIF instruction.500///501/// Note that although handlers are lexically-scoped, and we could502/// optimize away shadowing, this is fairly subtle, because handler503/// order also matters (two *distinct* tag indices in our module are504/// not necessarily distinct: tag imports can create aliasing). Rather505/// than attempt to keep an ordered map and also remove shadowing, we506/// follow the Wasm spec more closely: handlers are on "the stack" and507/// inner handlers win over outer handlers. Within a single508/// `try_table`, we push handlers *in reverse*, because the semantics509/// of handler matching in `try_table` are left-to-right; this allows510/// us to *flatten* the LIFO stack of `try_table`s with left-to-right511/// scans within a table into a single stack we scan backward from the512/// end.513pub struct HandlerState {514/// List of pairs mapping from CLIF-level exception tag to515/// CLIF-level block. We will have already filled in these blocks516/// with the appropriate branch implementation when we start the517/// `try_table` scope.518pub(crate) handlers: Vec<(Option<ExceptionTag>, Block)>,519}520521impl core::default::Default for HandlerState {522fn default() -> Self {523HandlerState { handlers: vec![] }524}525}526527/// A checkpoint in the handler state. Can be restored in LIFO order528/// only: the last-taken checkpoint can be restored first, then the529/// one before it, etc.530#[derive(Clone, Copy, Debug)]531pub struct HandlerStateCheckpoint(usize);532533impl HandlerState {534/// Set a given tag's handler to a given CLIF block.535pub fn add_handler(&mut self, tag: Option<ExceptionTag>, block: Block) {536self.handlers.push((tag, block));537}538539/// Take a checkpoint.540pub fn take_checkpoint(&self) -> HandlerStateCheckpoint {541HandlerStateCheckpoint(self.handlers.len())542}543544/// Restore to a checkpoint.545pub fn restore_checkpoint(&mut self, ckpt: HandlerStateCheckpoint) {546assert!(ckpt.0 <= self.handlers.len());547self.handlers.truncate(ckpt.0);548}549550/// Get an iterator over handlers. The exception-matching551/// semantics are to take the *first* match in this sequence; that552/// is, this returns the sequence of handlers latest-first (top of553/// stack first).554pub fn handlers(&self) -> impl Iterator<Item = (Option<ExceptionTag>, Block)> + '_ {555self.handlers556.iter()557.map(|(tag, block)| (*tag, *block))558.rev()559}560561/// Are there no handlers registered?562pub fn is_empty(&self) -> bool {563self.handlers.is_empty()564}565}566567568