Path: blob/main/crates/cranelift/src/translate/stack.rs
3068 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;9use wasmtime_environ::FrameStackShape;1011/// Information about the presence of an associated `else` for an `if`, or the12/// lack thereof.13#[derive(Debug)]14pub enum ElseData {15/// The `if` does not already have an `else` block.16///17/// This doesn't mean that it will never have an `else`, just that we18/// haven't seen it yet.19NoElse {20/// If we discover that we need an `else` block, this is the jump21/// instruction that needs to be fixed up to point to the new `else`22/// block rather than the destination block after the `if...end`.23branch_inst: Inst,2425/// The placeholder block we're replacing.26placeholder: Block,27},2829/// We have already allocated an `else` block.30///31/// Usually we don't know whether we will hit an `if .. end` or an `if32/// .. else .. end`, but sometimes we can tell based on the block's type33/// signature that the signature is not valid if there isn't an `else`. In34/// these cases, we pre-allocate the `else` block.35WithElse {36/// This is the `else` block.37else_block: Block,38},39}4041/// A control stack frame can be an `if`, a `block` or a `loop`, each one having the following42/// fields:43///44/// - `destination`: reference to the `Block` that will hold the code after the control block;45/// - `num_return_values`: number of values returned by the control block;46/// - `original_stack_size`: size of the value stack at the beginning of the control block.47///48/// The `loop` frame has a `header` field that references the `Block` that contains the beginning49/// of the body of the loop.50#[derive(Debug)]51pub enum ControlStackFrame {52If {53destination: Block,54else_data: ElseData,55num_param_values: usize,56num_return_values: usize,57original_stack_size: usize,58exit_is_branched_to: bool,59blocktype: wasmparser::BlockType,60/// Was the head of the `if` reachable?61head_is_reachable: bool,62/// What was the reachability at the end of the consequent?63///64/// This is `None` until we're finished translating the consequent, and65/// is set to `Some` either by hitting an `else` when we will begin66/// translating the alternative, or by hitting an `end` in which case67/// there is no alternative.68consequent_ends_reachable: Option<bool>,69// Note: no need for `alternative_ends_reachable` because that is just70// `state.reachable` when we hit the `end` in the `if .. else .. end`.71},72Block {73destination: Block,74num_param_values: usize,75num_return_values: usize,76original_stack_size: usize,77exit_is_branched_to: bool,78/// If this block is a try-table block, the handler state79/// checkpoint to rewind to when we leave the block, and the80/// list of catch blocks to seal when done.81try_table_info: Option<(HandlerStateCheckpoint, Vec<Block>)>,82},83Loop {84destination: Block,85header: Block,86num_param_values: usize,87num_return_values: usize,88original_stack_size: usize,89},90}9192/// Helper methods for the control stack objects.93impl ControlStackFrame {94pub fn num_return_values(&self) -> usize {95match *self {96Self::If {97num_return_values, ..98}99| Self::Block {100num_return_values, ..101}102| Self::Loop {103num_return_values, ..104} => num_return_values,105}106}107108pub fn num_param_values(&self) -> usize {109match *self {110Self::If {111num_param_values, ..112}113| Self::Block {114num_param_values, ..115}116| Self::Loop {117num_param_values, ..118} => num_param_values,119}120}121122pub fn following_code(&self) -> Block {123match *self {124Self::If { destination, .. }125| Self::Block { destination, .. }126| Self::Loop { destination, .. } => destination,127}128}129130pub fn br_destination(&self) -> Block {131match *self {132Self::If { destination, .. } | Self::Block { destination, .. } => destination,133Self::Loop { header, .. } => header,134}135}136137/// Private helper. Use `truncate_value_stack_to_else_params()` or138/// `truncate_value_stack_to_original_size()` to restore value-stack state.139fn original_stack_size(&self) -> usize {140match *self {141Self::If {142original_stack_size,143..144}145| Self::Block {146original_stack_size,147..148}149| Self::Loop {150original_stack_size,151..152} => original_stack_size,153}154}155156pub fn is_loop(&self) -> bool {157match *self {158Self::If { .. } | Self::Block { .. } => false,159Self::Loop { .. } => true,160}161}162163pub fn exit_is_branched_to(&self) -> bool {164match *self {165Self::If {166exit_is_branched_to,167..168}169| Self::Block {170exit_is_branched_to,171..172} => exit_is_branched_to,173Self::Loop { .. } => false,174}175}176177pub fn set_branched_to_exit(&mut self) {178match *self {179Self::If {180ref mut exit_is_branched_to,181..182}183| Self::Block {184ref mut exit_is_branched_to,185..186} => *exit_is_branched_to = true,187Self::Loop { .. } => {}188}189}190191/// Pop values from the value stack so that it is left at the192/// input-parameters to an else-block.193pub fn truncate_value_stack_to_else_params(194&self,195stack: &mut Vec<Value>,196stack_shape: &mut Vec<FrameStackShape>,197) {198debug_assert!(matches!(self, &ControlStackFrame::If { .. }));199stack.truncate(self.original_stack_size());200stack_shape.truncate(self.original_stack_size());201}202203/// Pop values from the value stack so that it is left at the state it was204/// before this control-flow frame.205pub fn truncate_value_stack_to_original_size(206&self,207stack: &mut Vec<Value>,208stack_shape: &mut Vec<FrameStackShape>,209) {210// The "If" frame pushes its parameters twice, so they're available to the else block211// (see also `FuncTranslationStacks::push_if`).212// Yet, the original_stack_size member accounts for them only once, so that the else213// block can see the same number of parameters as the consequent block. As a matter of214// fact, we need to subtract an extra number of parameter values for if blocks.215let num_duplicated_params = match self {216&ControlStackFrame::If {217num_param_values, ..218} => {219debug_assert!(num_param_values <= self.original_stack_size());220num_param_values221}222_ => 0,223};224225let new_len = self.original_stack_size() - num_duplicated_params;226stack.truncate(new_len);227stack_shape.truncate(new_len);228}229230/// Restore the catch-handlers as they were outside of this block.231pub fn restore_catch_handlers(232&self,233handlers: &mut HandlerState,234builder: &mut FunctionBuilder,235) {236match self {237ControlStackFrame::Block {238try_table_info: Some((ckpt, catch_blocks)),239..240} => {241handlers.restore_checkpoint(*ckpt);242for block in catch_blocks {243builder.seal_block(*block);244}245}246_ => {}247}248}249}250251/// Keeps track of Wasm's operand and control stacks, as well as reachability252/// for each control frame.253pub struct FuncTranslationStacks {254/// A stack of values corresponding to the active values in the input wasm function at this255/// point.256pub(crate) stack: Vec<Value>,257/// "Shape" of stack at each index, if emitting debug instrumentation.258///259/// When we pop `stack`, we automatically pop `stack_shape` as260/// well, but we never push automatically; this enables us to261/// determine which values are new and need to be flushed to262/// memory after translating an operator.263pub(crate) stack_shape: Vec<FrameStackShape>,264/// A stack of active control flow operations at this point in the input wasm function.265pub(crate) control_stack: Vec<ControlStackFrame>,266/// Exception handler state, updated as we enter and exit267/// `try_table` scopes and attached to each call that we make.268pub(crate) handlers: HandlerState,269/// Is the current translation state still reachable? This is false when translating operators270/// like End, Return, or Unreachable.271pub(crate) reachable: bool,272}273274// Public methods that are exposed to non- API consumers.275impl FuncTranslationStacks {276/// True if the current translation state expresses reachable code, false if it is unreachable.277#[inline]278pub fn reachable(&self) -> bool {279self.reachable280}281}282283impl FuncTranslationStacks {284/// Construct a new, empty, `FuncTranslationStacks`285pub(crate) fn new() -> Self {286Self {287stack: Vec::new(),288stack_shape: Vec::new(),289control_stack: Vec::new(),290handlers: HandlerState::default(),291reachable: true,292}293}294295fn clear(&mut self) {296debug_assert!(self.stack.is_empty());297debug_assert!(self.stack_shape.is_empty());298debug_assert!(self.control_stack.is_empty());299debug_assert!(self.handlers.is_empty());300self.reachable = true;301}302303/// Initialize the state for compiling a function with the given signature.304///305/// This resets the state to containing only a single block representing the whole function.306/// The exit block is the last block in the function which will contain the return instruction.307pub(crate) fn initialize(&mut self, sig: &ir::Signature, exit_block: Block) {308self.clear();309self.push_block(310exit_block,3110,312sig.returns313.iter()314.filter(|arg| arg.purpose == ir::ArgumentPurpose::Normal)315.count(),316);317}318319/// Push a value.320pub(crate) fn push1(&mut self, val: Value) {321self.stack.push(val);322}323324/// Push two values.325pub(crate) fn push2(&mut self, val1: Value, val2: Value) {326self.stack.push(val1);327self.stack.push(val2);328}329330/// Push multiple values.331pub(crate) fn pushn(&mut self, vals: &[Value]) {332self.stack.extend_from_slice(vals);333}334335/// Pop one value.336pub(crate) fn pop1(&mut self) -> Value {337self.pop_stack_shape(1);338self.stack339.pop()340.expect("attempted to pop a value from an empty stack")341}342343/// Peek at the top of the stack without popping it.344pub(crate) fn peek1(&self) -> Value {345*self346.stack347.last()348.expect("attempted to peek at a value on an empty stack")349}350351/// Pop two values. Return them in the order they were pushed.352pub(crate) fn pop2(&mut self) -> (Value, Value) {353self.pop_stack_shape(2);354let v2 = self.stack.pop().unwrap();355let v1 = self.stack.pop().unwrap();356(v1, v2)357}358359/// Pop three values. Return them in the order they were pushed.360pub(crate) fn pop3(&mut self) -> (Value, Value, Value) {361self.pop_stack_shape(3);362let v3 = self.stack.pop().unwrap();363let v2 = self.stack.pop().unwrap();364let v1 = self.stack.pop().unwrap();365(v1, v2, v3)366}367368/// Pop four values. Return them in the order they were pushed.369pub(crate) fn pop4(&mut self) -> (Value, Value, Value, Value) {370self.pop_stack_shape(4);371let v4 = self.stack.pop().unwrap();372let v3 = self.stack.pop().unwrap();373let v2 = self.stack.pop().unwrap();374let v1 = self.stack.pop().unwrap();375(v1, v2, v3, v4)376}377378/// Pop five values. Return them in the order they were pushed.379pub(crate) fn pop5(&mut self) -> (Value, Value, Value, Value, Value) {380self.pop_stack_shape(5);381let v5 = self.stack.pop().unwrap();382let v4 = self.stack.pop().unwrap();383let v3 = self.stack.pop().unwrap();384let v2 = self.stack.pop().unwrap();385let v1 = self.stack.pop().unwrap();386(v1, v2, v3, v4, v5)387}388389/// Helper to ensure the stack size is at least as big as `n`; note that due to390/// `debug_assert` this will not execute in non-optimized builds.391#[inline]392fn ensure_length_is_at_least(&self, n: usize) {393debug_assert!(394n <= self.stack.len(),395"attempted to access {} values but stack only has {} values",396n,397self.stack.len()398)399}400401/// Pop the top `n` values on the stack.402///403/// The popped values are not returned. Use `peekn` to look at them before popping.404pub(crate) fn popn(&mut self, n: usize) {405self.ensure_length_is_at_least(n);406let new_len = self.stack.len() - n;407self.stack.truncate(new_len);408self.stack_shape.truncate(new_len);409}410411fn pop_stack_shape(&mut self, n: usize) {412// The `stack_shape` vec represents the *clean* slots (already413// flushed to memory); its length is always less than or equal414// to `stack`, but indices always correspond between the415// two. Thus a pop on `stack` may or may not pop something on416// `stack_shape`; but if `stack` is truncated down to a length417// L by some number of pops, truncating `stack_shape` to that418// same length L will pop exactly the right shapes and will419// ensure that any new pushes that are "dirty" will be420// correctly represented as such.421let new_len = self.stack.len() - n;422self.stack_shape.truncate(new_len);423}424425/// Peek at the top `n` values on the stack in the order they were pushed.426pub(crate) fn peekn(&self, n: usize) -> &[Value] {427self.ensure_length_is_at_least(n);428&self.stack[self.stack.len() - n..]429}430431/// Peek at the top `n` values on the stack in the order they were pushed.432pub(crate) fn peekn_mut(&mut self, n: usize) -> &mut [Value] {433self.ensure_length_is_at_least(n);434let len = self.stack.len();435&mut self.stack[len - n..]436}437438fn push_block_impl(439&mut self,440following_code: Block,441num_param_types: usize,442num_result_types: usize,443try_table_info: Option<(HandlerStateCheckpoint, Vec<Block>)>,444) {445debug_assert!(num_param_types <= self.stack.len());446self.control_stack.push(ControlStackFrame::Block {447destination: following_code,448original_stack_size: self.stack.len() - num_param_types,449num_param_values: num_param_types,450num_return_values: num_result_types,451exit_is_branched_to: false,452try_table_info,453});454}455456/// Push a block on the control stack.457pub(crate) fn push_block(458&mut self,459following_code: Block,460num_param_types: usize,461num_result_types: usize,462) {463self.push_block_impl(following_code, num_param_types, num_result_types, None);464}465466/// Push a try-table block on the control stack.467pub(crate) fn push_try_table_block(468&mut self,469following_code: Block,470catch_blocks: Vec<Block>,471num_param_types: usize,472num_result_types: usize,473checkpoint: HandlerStateCheckpoint,474) {475self.push_block_impl(476following_code,477num_param_types,478num_result_types,479Some((checkpoint, catch_blocks)),480);481}482483/// Push a loop on the control stack.484pub(crate) fn push_loop(485&mut self,486header: Block,487following_code: Block,488num_param_types: usize,489num_result_types: usize,490) {491debug_assert!(num_param_types <= self.stack.len());492self.control_stack.push(ControlStackFrame::Loop {493header,494destination: following_code,495original_stack_size: self.stack.len() - num_param_types,496num_param_values: num_param_types,497num_return_values: num_result_types,498});499}500501/// Push an if on the control stack.502pub(crate) fn push_if(503&mut self,504destination: Block,505else_data: ElseData,506num_param_types: usize,507num_result_types: usize,508blocktype: wasmparser::BlockType,509) {510debug_assert!(num_param_types <= self.stack.len());511self.assert_debug_stack_is_synced();512513// Push a second copy of our `if`'s parameters on the stack. This lets514// us avoid saving them on the side in the `ControlStackFrame` for our515// `else` block (if it exists), which would require a second heap516// allocation. See also the comment in `translate_operator` for517// `Operator::Else`.518self.stack.reserve(num_param_types);519for i in (self.stack.len() - num_param_types)..self.stack.len() {520let val = self.stack[i];521self.stack.push(val);522// Duplicate the stack-shape as well, if we're doing debug523// instrumentation. Note that we must have flushed524// everything before processing an `if`, so (as per the525// assert above) we can rely on either no shapes (if no526// instrumentation) or all shapes being present.527if !self.stack_shape.is_empty() {528let shape = self.stack_shape[i];529self.stack_shape.push(shape);530}531}532533self.control_stack.push(ControlStackFrame::If {534destination,535else_data,536original_stack_size: self.stack.len() - num_param_types,537num_param_values: num_param_types,538num_return_values: num_result_types,539exit_is_branched_to: false,540head_is_reachable: self.reachable,541consequent_ends_reachable: None,542blocktype,543});544}545546pub(crate) fn assert_debug_stack_is_synced(&self) {547debug_assert!(self.stack_shape.is_empty() || self.stack_shape.len() == self.stack.len());548}549}550551/// Exception handler state.552///553/// We update this state as we enter and exit `try_table` scopes. When554/// we visit a call, we use this state to attach handler info to a555/// `try_call` CLIF instruction.556///557/// Note that although handlers are lexically-scoped, and we could558/// optimize away shadowing, this is fairly subtle, because handler559/// order also matters (two *distinct* tag indices in our module are560/// not necessarily distinct: tag imports can create aliasing). Rather561/// than attempt to keep an ordered map and also remove shadowing, we562/// follow the Wasm spec more closely: handlers are on "the stack" and563/// inner handlers win over outer handlers. Within a single564/// `try_table`, we push handlers *in reverse*, because the semantics565/// of handler matching in `try_table` are left-to-right; this allows566/// us to *flatten* the LIFO stack of `try_table`s with left-to-right567/// scans within a table into a single stack we scan backward from the568/// end.569pub struct HandlerState {570/// List of pairs mapping from CLIF-level exception tag to571/// CLIF-level block. We will have already filled in these blocks572/// with the appropriate branch implementation when we start the573/// `try_table` scope.574pub(crate) handlers: Vec<(Option<ExceptionTag>, Block)>,575}576577impl core::default::Default for HandlerState {578fn default() -> Self {579HandlerState { handlers: vec![] }580}581}582583/// A checkpoint in the handler state. Can be restored in LIFO order584/// only: the last-taken checkpoint can be restored first, then the585/// one before it, etc.586#[derive(Clone, Copy, Debug)]587pub struct HandlerStateCheckpoint(usize);588589impl HandlerState {590/// Set a given tag's handler to a given CLIF block.591pub fn add_handler(&mut self, tag: Option<ExceptionTag>, block: Block) {592self.handlers.push((tag, block));593}594595/// Take a checkpoint.596pub fn take_checkpoint(&self) -> HandlerStateCheckpoint {597HandlerStateCheckpoint(self.handlers.len())598}599600/// Restore to a checkpoint.601pub fn restore_checkpoint(&mut self, ckpt: HandlerStateCheckpoint) {602assert!(ckpt.0 <= self.handlers.len());603self.handlers.truncate(ckpt.0);604}605606/// Get an iterator over handlers. The exception-matching607/// semantics are to take the *first* match in this sequence; that608/// is, this returns the sequence of handlers latest-first (top of609/// stack first).610pub fn handlers(&self) -> impl Iterator<Item = (Option<ExceptionTag>, Block)> + '_ {611self.handlers612.iter()613.map(|(tag, block)| (*tag, *block))614.rev()615}616617/// Are there no handlers registered?618pub fn is_empty(&self) -> bool {619self.handlers.is_empty()620}621}622623624