cranelift-codegen 0.76.0

Low-level code generator library
Documentation
//! Represents information relating to function unwinding.

use regalloc::RealReg;

#[cfg(feature = "enable-serde")]
use serde::{Deserialize, Serialize};

#[cfg(feature = "unwind")]
pub mod systemv;

#[cfg(feature = "unwind")]
pub mod winx64;

/// Represents unwind information for a single function.
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum UnwindInfo {
    /// Windows x64 ABI unwind information.
    #[cfg(feature = "unwind")]
    WindowsX64(winx64::UnwindInfo),
    /// System V ABI unwind information.
    #[cfg(feature = "unwind")]
    SystemV(systemv::UnwindInfo),
}

/// Intermediate representation for the unwind information
/// generated by a backend.
pub mod input {
    use crate::binemit::CodeOffset;
    use alloc::vec::Vec;
    #[cfg(feature = "enable-serde")]
    use serde::{Deserialize, Serialize};

    /// Elementary operation in the unwind operations.
    #[derive(Clone, Debug, PartialEq, Eq)]
    #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
    pub enum UnwindCode<Reg> {
        /// Defines that a register is saved at the specified offset.
        SaveRegister {
            /// The saved register.
            reg: Reg,
            /// The specified offset relative to the stack pointer.
            stack_offset: u32,
        },
        /// Defines that a register is as defined before call.
        RestoreRegister {
            /// The restored register.
            reg: Reg,
        },
        /// The stack pointer was adjusted to allocate the stack.
        StackAlloc {
            /// Size to allocate.
            size: u32,
        },
        /// The stack pointer was adjusted to free the stack.
        StackDealloc {
            /// Size to deallocate.
            size: u32,
        },
        /// The alternative register was assigned as frame pointer base.
        SetFramePointer {
            /// The specified register.
            reg: Reg,
        },
        /// Restores a frame pointer base to default register.
        RestoreFramePointer,
        /// Saves the state.
        RememberState,
        /// Restores the state.
        RestoreState,
        /// On aarch64 ARMv8.3+ devices, enables or disables pointer authentication.
        Aarch64SetPointerAuth {
            /// Whether return addresses (hold in LR) contain a pointer-authentication code.
            return_addresses: bool,
        },
    }

    /// Unwind information as generated by a backend.
    #[derive(Clone, Debug, PartialEq, Eq)]
    #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
    pub struct UnwindInfo<Reg> {
        /// Size of the prologue.
        pub prologue_size: CodeOffset,
        /// Unwind codes for prologue.
        pub prologue_unwind_codes: Vec<(CodeOffset, UnwindCode<Reg>)>,
        /// Unwind codes for epilogues.
        pub epilogues_unwind_codes: Vec<Vec<(CodeOffset, UnwindCode<Reg>)>>,
        /// Entire function size.
        pub function_size: CodeOffset,
        /// Platform word size in bytes.
        pub word_size: u8,
        /// Initial stack pointer offset.
        pub initial_sp_offset: u8,
    }
}

/// Unwind pseudoinstruction used in VCode backends: represents that
/// at the present location, an action has just been taken.
///
/// VCode backends always emit unwind info that is relative to a frame
/// pointer, because we are planning to allow for dynamic frame allocation,
/// and because it makes the design quite a lot simpler in general: we don't
/// have to be precise about SP adjustments throughout the body of the function.
///
/// We include only unwind info for prologues at this time. Note that unwind
/// info for epilogues is only necessary if one expects to unwind while within
/// the last few instructions of the function (after FP has been restored) or
/// if one wishes to instruction-step through the epilogue and see a backtrace
/// at every point. This is not necessary for correct operation otherwise and so
/// we simplify the world a bit by omitting epilogue information. (Note that
/// some platforms also don't require or have a way to describe unwind
/// information for epilogues at all: for example, on Windows, the `UNWIND_INFO`
/// format only stores information for the function prologue.)
///
/// Because we are defining an abstraction over multiple unwind formats (at
/// least Windows/fastcall and System V) and multiple architectures (at least
/// x86-64 and aarch64), we have to be a little bit flexible in how we describe
/// the frame. However, it turns out that a least-common-denominator prologue
/// works for all of the cases we have to worry about today!
///
/// We assume the stack looks something like this:
///
///
/// ```plain
///                  +----------------------------------------------+
///                  | stack arg area, etc (according to ABI)       |
///                  | ...                                          |
///   SP at call --> +----------------------------------------------+
///                  | return address (pushed by HW or SW)          |
///                  +----------------------------------------------+
///                  | old frame pointer (FP)                       |
///   FP in this --> +----------------------------------------------+
///   function       | clobbered callee-save registers              |
///                  | ...                                          |
///   start of   --> +----------------------------------------------+
///   clobbers       | (rest of function's frame, irrelevant here)  |
///                  | ...                                          |
///   SP in this --> +----------------------------------------------+
///   function
/// ```
///
/// We assume that the prologue consists of:
///
/// * `PushFrameRegs`: A push operation that adds the old FP to the stack (and
///    maybe the link register, on architectures that do not push return addresses
///    in hardware)
/// * `DefineFrame`: An update that sets FP to SP to establish a new frame
/// * `SaveReg`: A number of stores or pushes to the stack to save clobbered registers
///
/// Each of these steps has a corresponding pseudo-instruction. At each step,
/// we need some information to determine where the current stack frame is
/// relative to SP or FP. When the `PushFrameRegs` occurs, we need to know how
/// much SP was decremented by, so we can allow the unwinder to continue to find
/// the caller's frame. When we define the new frame, we need to know where FP
/// is in relation to "SP at call" and also "start of clobbers", because
/// different unwind formats define one or the other of those as the anchor by
/// which we define the frame. Finally, when registers are saved, we need to
/// know which ones, and where.
///
/// Different unwind formats work differently; here is a whirlwind tour of how
/// they define frames to help understanding:
///
/// - Windows unwind information defines a frame that must start below the
///   clobber area, because all clobber-save offsets are non-negative. We set it
///   at the "start of clobbers" in the figure above. The `UNWIND_INFO` contains
///   a "frame pointer offset" field; when we define the new frame, the frame is
///   understood to be the value of FP (`RBP`) *minus* this offset. In other
///   words, the FP is *at the frame pointer offset* relative to the
///   start-of-clobber-frame. We use the "FP offset down to clobber area" offset
///   to generate this info.
///
/// - System V unwind information defines a frame in terms of the CFA
///   (call-frame address), which is equal to the "SP at call" above. SysV
///   allows negative offsets, so there is no issue defining clobber-save
///   locations in terms of CFA. The format allows us to define CFA flexibly in
///   terms of any register plus an offset; we define it in terms of FP plus
///   the clobber-to-caller-SP offset once FP is established.
///
/// Note that certain architectures impose limits on offsets: for example, on
/// Windows, the base of the clobber area must not be more than 240 bytes below
/// FP.
///
/// Unwind pseudoinstructions are emitted inline by ABI code as it generates
/// a prologue. Thus, for the usual case, a prologue might look like (using x64
/// as an example):
///
/// ```plain
/// push rbp
/// unwind UnwindInst::PushFrameRegs { offset_upward_to_caller_sp: 16 }
/// mov rbp, rsp
/// unwind UnwindInst::DefineNewFrame { offset_upward_to_caller_sp: 16,
///                                     offset_downward_to_clobbers: 16 }
/// sub rsp, 32
/// mov [rsp+16], r12
/// unwind UnwindInst::SaveReg { reg: R12, clobber_offset: 0 }
/// mov [rsp+24], r13
/// unwind UnwindInst::SaveReg { reg: R13, clobber_offset: 8 }
/// ...
/// ```
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub enum UnwindInst {
    /// The frame-pointer register for this architecture has just been pushed to
    /// the stack (and on architectures where return-addresses are not pushed by
    /// hardware, the link register as well). The FP has not been set to this
    /// frame yet. The current location of SP is such that
    /// `offset_upward_to_caller_sp` is the distance to SP-at-callsite (our
    /// caller's frame).
    PushFrameRegs {
        /// The offset from the current SP (after push) to the SP at
        /// caller's callsite.
        offset_upward_to_caller_sp: u32,
    },
    /// The frame-pointer register for this architecture has just been
    /// set to the current stack location. We wish to define a new
    /// frame that is anchored on this new FP value. Offsets are provided
    /// upward to the caller's stack frame and downward toward the clobber
    /// area. We expect this pseudo-op to come after `PushFrameRegs`.
    DefineNewFrame {
        /// The offset from the current SP and FP value upward to the value of
        /// SP at the callsite that invoked us.
        offset_upward_to_caller_sp: u32,
        /// The offset from the current SP and FP value downward to the start of
        /// the clobber area.
        offset_downward_to_clobbers: u32,
    },
    /// The stack pointer was adjusted to allocate the stack.
    StackAlloc {
        /// Size to allocate.
        size: u32,
    },
    /// The stack slot at the given offset from the clobber-area base has been
    /// used to save the given register.
    ///
    /// Given that `CreateFrame` has occurred first with some
    /// `offset_downward_to_clobbers`, `SaveReg` with `clobber_offset` indicates
    /// that the value of `reg` is saved on the stack at address `FP -
    /// offset_downward_to_clobbers + clobber_offset`.
    SaveReg {
        /// The offset from the start of the clobber area to this register's
        /// stack location.
        clobber_offset: u32,
        /// The saved register.
        reg: RealReg,
    },
    /// Defines if the aarch64-specific pointer authentication available for ARM v8.3+ devices
    /// is enabled for certain pointers or not.
    Aarch64SetPointerAuth {
        /// Whether return addresses (hold in LR) contain a pointer-authentication code.
        return_addresses: bool,
    },
}