//! Low-level details of stack accesses.
//!
//! The `ir::StackSlots` type deals with stack slots and stack frame layout. The `StackRef` type
//! defined in this module expresses the low-level details of accessing a stack slot from an
//! encoded instruction.

use crate::ir::stackslot::{StackOffset, StackSlotKind, StackSlots};
use crate::ir::StackSlot;

/// A method for referencing a stack slot in the current stack frame.
///
/// Stack slots are addressed with a constant offset from a base register. The base can be the
/// stack pointer, the frame pointer, or (in the future) a zone register pointing to an inner zone
/// of a large stack frame.
#[derive(Clone, Copy, Debug)]
pub struct StackRef {
    /// The base register to use for addressing.
    pub base: StackBase,

    /// Immediate offset from the base register to the first byte of the stack slot.
    pub offset: StackOffset,
}

impl StackRef {
    /// Get a reference to the stack slot `ss` using one of the base pointers in `mask`.
    pub fn masked(ss: StackSlot, mask: StackBaseMask, frame: &StackSlots) -> Option<Self> {
        // Try an SP-relative reference.
        if mask.contains(StackBase::SP) {
            return Some(Self::sp(ss, frame));
        }

        // No reference possible with this mask.
        None
    }

    /// Get a reference to `ss` using the stack pointer as a base.
    pub fn sp(ss: StackSlot, frame: &StackSlots) -> Self {
        let size = frame
            .layout_info
            .expect("Stack layout must be computed before referencing stack slots")
            .frame_size;
        let slot = &frame[ss];
        let offset = if slot.kind == StackSlotKind::OutgoingArg {
            // Outgoing argument slots have offsets relative to our stack pointer.
            slot.offset.unwrap()
        } else {
            // All other slots have offsets relative to our caller's stack frame.
            // Offset where SP is pointing. (All ISAs have stacks growing downwards.)
            let sp_offset = -(size as StackOffset);
            slot.offset.unwrap() - sp_offset
        };
        Self {
            base: StackBase::SP,
            offset,
        }
    }
}

/// Generic base register for referencing stack slots.
///
/// Most ISAs have a stack pointer and an optional frame pointer, so provide generic names for
/// those two base pointers.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum StackBase {
    /// Use the stack pointer.
    SP = 0,

    /// Use the frame pointer (if one is present).
    FP = 1,

    /// Use an explicit zone pointer in a general-purpose register.
    ///
    /// This feature is not yet implemented.
    Zone = 2,
}

/// Bit mask of supported stack bases.
///
/// Many instruction encodings can use different base registers while others only work with the
/// stack pointer, say. A `StackBaseMask` is a bit mask of supported stack bases for a given
/// instruction encoding.
///
/// This behaves like a set of `StackBase` variants.
///
/// The internal representation as a `u8` is public because stack base masks are used in constant
/// tables generated from the meta-language encoding definitions.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct StackBaseMask(pub u8);

impl StackBaseMask {
    /// Check if this mask contains the `base` variant.
    pub fn contains(self, base: StackBase) -> bool {
        self.0 & (1 << base as usize) != 0
    }
}
