/*!
Definitions for index bounds checking.
*/

use core::iter::{self, zip};

use crate::arena::{Handle, HandleSet, UniqueArena};
use crate::{valid, FastHashSet};

/// How should code generated by Naga do bounds checks?
///
/// When a vector, matrix, or array index is out of bounds—either negative, or
/// greater than or equal to the number of elements in the type—WGSL requires
/// that some other index of the implementation's choice that is in bounds is
/// used instead. (There are no types with zero elements.)
///
/// Similarly, when out-of-bounds coordinates, array indices, or sample indices
/// are presented to the WGSL `textureLoad` and `textureStore` operations, the
/// operation is redirected to do something safe.
///
/// Different users of Naga will prefer different defaults:
///
/// -   When used as part of a WebGPU implementation, the WGSL specification
///     requires the `Restrict` behavior for array, vector, and matrix accesses,
///     and either the `Restrict` or `ReadZeroSkipWrite` behaviors for texture
///     accesses.
///
/// -   When used by the `wgpu` crate for native development, `wgpu` selects
///     `ReadZeroSkipWrite` as its default.
///
/// -   Naga's own default is `Unchecked`, so that shader translations
///     are as faithful to the original as possible.
///
/// Sometimes the underlying hardware and drivers can perform bounds checks
/// themselves, in a way that performs better than the checks Naga would inject.
/// If you're using native checks like this, then having Naga inject its own
/// checks as well would be redundant, and the `Unchecked` policy is
/// appropriate.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
pub enum BoundsCheckPolicy {
    /// Replace out-of-bounds indexes with some arbitrary in-bounds index.
    ///
    /// (This does not necessarily mean clamping. For example, interpreting the
    /// index as unsigned and taking the minimum with the largest valid index
    /// would also be a valid implementation. That would map negative indices to
    /// the last element, not the first.)
    Restrict,

    /// Out-of-bounds reads return zero, and writes have no effect.
    ///
    /// When applied to a chain of accesses, like `a[i][j].b[k]`, all index
    /// expressions are evaluated, regardless of whether prior or later index
    /// expressions were in bounds. But all the accesses per se are skipped
    /// if any index is out of bounds.
    ReadZeroSkipWrite,

    /// Naga adds no checks to indexing operations. Generate the fastest code
    /// possible. This is the default for Naga, as a translator, but consumers
    /// should consider defaulting to a safer behavior.
    Unchecked,
}

/// Policies for injecting bounds checks during code generation.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
#[cfg_attr(feature = "deserialize", serde(default))]
pub struct BoundsCheckPolicies {
    /// How should the generated code handle array, vector, or matrix indices
    /// that are out of range?
    pub index: BoundsCheckPolicy,

    /// How should the generated code handle array, vector, or matrix indices
    /// that are out of range, when those values live in a [`GlobalVariable`] in
    /// the [`Storage`] or [`Uniform`] address spaces?
    ///
    /// Some graphics hardware provides "robust buffer access", a feature that
    /// ensures that using a pointer cannot access memory outside the 'buffer'
    /// that it was derived from. In Naga terms, this means that the hardware
    /// ensures that pointers computed by applying [`Access`] and
    /// [`AccessIndex`] expressions to a [`GlobalVariable`] whose [`space`] is
    /// [`Storage`] or [`Uniform`] will never read or write memory outside that
    /// global variable.
    ///
    /// When hardware offers such a feature, it is probably undesirable to have
    /// Naga inject bounds checking code for such accesses, since the hardware
    /// can probably provide the same protection more efficiently. However,
    /// bounds checks are still needed on accesses to indexable values that do
    /// not live in buffers, like local variables.
    ///
    /// So, this option provides a separate policy that applies only to accesses
    /// to storage and uniform globals. When depending on hardware bounds
    /// checking, this policy can be `Unchecked` to avoid unnecessary overhead.
    ///
    /// When special hardware support is not available, this should probably be
    /// the same as `index_bounds_check_policy`.
    ///
    /// [`GlobalVariable`]: crate::GlobalVariable
    /// [`space`]: crate::GlobalVariable::space
    /// [`Restrict`]: crate::back::BoundsCheckPolicy::Restrict
    /// [`ReadZeroSkipWrite`]: crate::back::BoundsCheckPolicy::ReadZeroSkipWrite
    /// [`Access`]: crate::Expression::Access
    /// [`AccessIndex`]: crate::Expression::AccessIndex
    /// [`Storage`]: crate::AddressSpace::Storage
    /// [`Uniform`]: crate::AddressSpace::Uniform
    pub buffer: BoundsCheckPolicy,

    /// How should the generated code handle image texel loads that are out
    /// of range?
    ///
    /// This controls the behavior of [`ImageLoad`] expressions when a coordinate,
    /// texture array index, level of detail, or multisampled sample number is out of range.
    ///
    /// There is no corresponding policy for [`ImageStore`] statements. All the
    /// platforms we support already discard out-of-bounds image stores,
    /// effectively implementing the "skip write" part of [`ReadZeroSkipWrite`].
    ///
    /// [`ImageLoad`]: crate::Expression::ImageLoad
    /// [`ImageStore`]: crate::Statement::ImageStore
    /// [`ReadZeroSkipWrite`]: BoundsCheckPolicy::ReadZeroSkipWrite
    pub image_load: BoundsCheckPolicy,

    /// How should the generated code handle binding array indexes that are out of bounds.
    pub binding_array: BoundsCheckPolicy,
}

/// The default `BoundsCheckPolicy` is `Unchecked`.
impl Default for BoundsCheckPolicy {
    fn default() -> Self {
        BoundsCheckPolicy::Unchecked
    }
}

impl BoundsCheckPolicies {
    /// Determine which policy applies to `base`.
    ///
    /// `base` is the "base" expression (the expression being indexed) of a `Access`
    /// and `AccessIndex` expression. This is either a pointer, a value, being directly
    /// indexed, or a binding array.
    ///
    /// See the documentation for [`BoundsCheckPolicy`] for details about
    /// when each policy applies.
    pub fn choose_policy(
        &self,
        base: Handle<crate::Expression>,
        types: &UniqueArena<crate::Type>,
        info: &valid::FunctionInfo,
    ) -> BoundsCheckPolicy {
        let ty = info[base].ty.inner_with(types);

        if let crate::TypeInner::BindingArray { .. } = *ty {
            return self.binding_array;
        }

        match ty.pointer_space() {
            Some(crate::AddressSpace::Storage { access: _ } | crate::AddressSpace::Uniform) => {
                self.buffer
            }
            // This covers other address spaces, but also accessing vectors and
            // matrices by value, where no pointer is involved.
            _ => self.index,
        }
    }

    /// Return `true` if any of `self`'s policies are `policy`.
    pub fn contains(&self, policy: BoundsCheckPolicy) -> bool {
        self.index == policy || self.buffer == policy || self.image_load == policy
    }
}

/// An index that may be statically known, or may need to be computed at runtime.
///
/// This enum lets us handle both [`Access`] and [`AccessIndex`] expressions
/// with the same code.
///
/// [`Access`]: crate::Expression::Access
/// [`AccessIndex`]: crate::Expression::AccessIndex
#[derive(Clone, Copy, Debug)]
pub enum GuardedIndex {
    Known(u32),
    Expression(Handle<crate::Expression>),
}

/// Build a set of expressions used as indices, to cache in temporary variables when
/// emitted.
///
/// Given the bounds-check policies `policies`, construct a `HandleSet` containing the handle
/// indices of all the expressions in `function` that are ever used as guarded indices
/// under the [`ReadZeroSkipWrite`] policy. The `module` argument must be the module to
/// which `function` belongs, and `info` should be that function's analysis results.
///
/// Such index expressions will be used twice in the generated code: first for the
/// comparison to see if the index is in bounds, and then for the access itself, should
/// the comparison succeed. To avoid computing the expressions twice, the generated code
/// should cache them in temporary variables.
///
/// Why do we need to build such a set in advance, instead of just processing access
/// expressions as we encounter them? Whether an expression needs to be cached depends on
/// whether it appears as something like the [`index`] operand of an [`Access`] expression
/// or the [`level`] operand of an [`ImageLoad`] expression, and on the index bounds check
/// policies that apply to those accesses. But [`Emit`] statements just identify a range
/// of expressions by index; there's no good way to tell what an expression is used
/// for. The only way to do it is to just iterate over all the expressions looking for
/// relevant `Access` expressions --- which is what this function does.
///
/// Simple expressions like variable loads and constants don't make sense to cache: it's
/// no better than just re-evaluating them. But constants are not covered by `Emit`
/// statements, and `Load`s are always cached to ensure they occur at the right time, so
/// we don't bother filtering them out from this set.
///
/// Fortunately, we don't need to deal with [`ImageStore`] statements here. When we emit
/// code for a statement, the writer isn't in the middle of an expression, so we can just
/// emit declarations for temporaries, initialized appropriately.
///
/// None of these concerns apply for SPIR-V output, since it's easy to just reuse an
/// instruction ID in two places; that has the same semantics as a temporary variable, and
/// it's inherent in the design of SPIR-V. This function is more useful for text-based
/// back ends.
///
/// [`ReadZeroSkipWrite`]: BoundsCheckPolicy::ReadZeroSkipWrite
/// [`index`]: crate::Expression::Access::index
/// [`Access`]: crate::Expression::Access
/// [`level`]: crate::Expression::ImageLoad::level
/// [`ImageLoad`]: crate::Expression::ImageLoad
/// [`Emit`]: crate::Statement::Emit
/// [`ImageStore`]: crate::Statement::ImageStore
pub fn find_checked_indexes(
    module: &crate::Module,
    function: &crate::Function,
    info: &valid::FunctionInfo,
    policies: BoundsCheckPolicies,
) -> HandleSet<crate::Expression> {
    use crate::Expression as Ex;

    let mut guarded_indices = HandleSet::for_arena(&function.expressions);

    // Don't bother scanning if we never need `ReadZeroSkipWrite`.
    if policies.contains(BoundsCheckPolicy::ReadZeroSkipWrite) {
        for (_handle, expr) in function.expressions.iter() {
            // There's no need to handle `AccessIndex` expressions, as their
            // indices never need to be cached.
            match *expr {
                Ex::Access { base, index } => {
                    if policies.choose_policy(base, &module.types, info)
                        == BoundsCheckPolicy::ReadZeroSkipWrite
                        && access_needs_check(
                            base,
                            GuardedIndex::Expression(index),
                            module,
                            &function.expressions,
                            info,
                        )
                        .is_some()
                    {
                        guarded_indices.insert(index);
                    }
                }
                Ex::ImageLoad {
                    coordinate,
                    array_index,
                    sample,
                    level,
                    ..
                } => {
                    if policies.image_load == BoundsCheckPolicy::ReadZeroSkipWrite {
                        guarded_indices.insert(coordinate);
                        if let Some(array_index) = array_index {
                            guarded_indices.insert(array_index);
                        }
                        if let Some(sample) = sample {
                            guarded_indices.insert(sample);
                        }
                        if let Some(level) = level {
                            guarded_indices.insert(level);
                        }
                    }
                }
                _ => {}
            }
        }
    }

    guarded_indices
}

/// Determine whether `index` is statically known to be in bounds for `base`.
///
/// If we can't be sure that the index is in bounds, return the limit within
/// which valid indices must fall.
///
/// The return value is one of the following:
///
/// - `Some(Known(n))` indicates that `n` is the largest valid index.
///
/// - `Some(Computed(global))` indicates that the largest valid index is one
///   less than the length of the array that is the last member of the
///   struct held in `global`.
///
/// - `None` indicates that the index need not be checked, either because it
///   is statically known to be in bounds, or because the applicable policy
///   is `Unchecked`.
///
/// This function only handles subscriptable types: arrays, vectors, and
/// matrices. It does not handle struct member indices; those never require
/// run-time checks, so it's best to deal with them further up the call
/// chain.
///
/// This function assumes that any relevant overrides have fully-evaluated
/// constants as their values (as arranged by [`process_overrides`], for
/// example).
///
/// [`process_overrides`]: crate::back::pipeline_constants::process_overrides
///
/// # Panics
///
/// - If `base` is not an indexable type, panic.
///
/// - If `base` is an override-sized array, but the override's value is not a
///   fully-evaluated constant expression, panic.
pub fn access_needs_check(
    base: Handle<crate::Expression>,
    mut index: GuardedIndex,
    module: &crate::Module,
    expressions: &crate::Arena<crate::Expression>,
    info: &valid::FunctionInfo,
) -> Option<IndexableLength> {
    let base_inner = info[base].ty.inner_with(&module.types);
    // Unwrap safety: `Err` here indicates unindexable base types and invalid
    // length constants, but `access_needs_check` is only used by back ends, so
    // validation should have caught those problems.
    let length = base_inner.indexable_length_resolved(module).unwrap();
    index.try_resolve_to_constant(expressions, module);
    if let (&GuardedIndex::Known(index), &IndexableLength::Known(length)) = (&index, &length) {
        if index < length {
            // Index is statically known to be in bounds, no check needed.
            return None;
        }
    };

    Some(length)
}

/// Items returned by the [`bounds_check_iter`] iterator.
#[cfg_attr(not(feature = "msl-out"), allow(dead_code))]
pub(crate) struct BoundsCheck {
    /// The base of the [`Access`] or [`AccessIndex`] expression.
    ///
    /// [`Access`]: crate::Expression::Access
    /// [`AccessIndex`]: crate::Expression::AccessIndex
    pub base: Handle<crate::Expression>,

    /// The index being accessed.
    pub index: GuardedIndex,

    /// The length of `base`.
    pub length: IndexableLength,
}

/// Returns an iterator of accesses within the chain of `Access` and
/// `AccessIndex` expressions starting from `chain` that may need to be
/// bounds-checked at runtime.
///
/// Items are yielded as [`BoundsCheck`] instances.
///
/// Accesses through a struct are omitted, since you never need a bounds check
/// for accessing a struct field.
///
/// If `chain` isn't an `Access` or `AccessIndex` expression at all, the
/// iterator is empty.
pub(crate) fn bounds_check_iter<'a>(
    mut chain: Handle<crate::Expression>,
    module: &'a crate::Module,
    function: &'a crate::Function,
    info: &'a valid::FunctionInfo,
) -> impl Iterator<Item = BoundsCheck> + 'a {
    iter::from_fn(move || {
        let (next_expr, result) = match function.expressions[chain] {
            crate::Expression::Access { base, index } => {
                (base, Some((base, GuardedIndex::Expression(index))))
            }
            crate::Expression::AccessIndex { base, index } => {
                // Don't try to check indices into structs. Validation already took
                // care of them, and access_needs_check doesn't handle that case.
                let mut base_inner = info[base].ty.inner_with(&module.types);
                if let crate::TypeInner::Pointer { base, .. } = *base_inner {
                    base_inner = &module.types[base].inner;
                }
                match *base_inner {
                    crate::TypeInner::Struct { .. } => (base, None),
                    _ => (base, Some((base, GuardedIndex::Known(index)))),
                }
            }
            _ => return None,
        };
        chain = next_expr;
        Some(result)
    })
    .flatten()
    .filter_map(|(base, index)| {
        access_needs_check(base, index, module, &function.expressions, info).map(|length| {
            BoundsCheck {
                base,
                index,
                length,
            }
        })
    })
}

/// Returns all the types which we need out-of-bounds locals for; that is,
/// all of the types which the code might attempt to get an out-of-bounds
/// pointer to, in which case we yield a pointer to the out-of-bounds local
/// of the correct type.
pub fn oob_local_types(
    module: &crate::Module,
    function: &crate::Function,
    info: &valid::FunctionInfo,
    policies: BoundsCheckPolicies,
) -> FastHashSet<Handle<crate::Type>> {
    let mut result = FastHashSet::default();

    if policies.index != BoundsCheckPolicy::ReadZeroSkipWrite {
        return result;
    }

    for statement in &function.body {
        // The only situation in which we end up actually needing to create an
        // out-of-bounds pointer is when passing one to a function.
        //
        // This is because pointers are never baked; they're just inlined everywhere
        // they're used. That means that loads can just return 0, and stores can just do
        // nothing; functions are the only case where you actually *have* to produce a
        // pointer.
        if let crate::Statement::Call {
            function: callee,
            ref arguments,
            ..
        } = *statement
        {
            // Now go through the arguments of the function looking for pointers which need bounds checks.
            for (arg_info, &arg) in zip(&module.functions[callee].arguments, arguments) {
                match module.types[arg_info.ty].inner {
                    crate::TypeInner::ValuePointer { .. } => {
                        // `ValuePointer`s should only ever be used when resolving the types of
                        // expressions, since the arena can no longer be modified at that point; things
                        // in the arena should always use proper `Pointer`s.
                        unreachable!("`ValuePointer` found in arena")
                    }
                    crate::TypeInner::Pointer { base, .. } => {
                        if bounds_check_iter(arg, module, function, info)
                            .next()
                            .is_some()
                        {
                            result.insert(base);
                        }
                    }
                    _ => continue,
                };
            }
        }
    }
    result
}

impl GuardedIndex {
    /// Make a `GuardedIndex::Known` from a `GuardedIndex::Expression` if possible.
    ///
    /// Return values that are already `Known` unchanged.
    pub(crate) fn try_resolve_to_constant(
        &mut self,
        expressions: &crate::Arena<crate::Expression>,
        module: &crate::Module,
    ) {
        if let GuardedIndex::Expression(expr) = *self {
            *self = GuardedIndex::from_expression(expr, expressions, module);
        }
    }

    pub(crate) fn from_expression(
        expr: Handle<crate::Expression>,
        expressions: &crate::Arena<crate::Expression>,
        module: &crate::Module,
    ) -> Self {
        match module.to_ctx().eval_expr_to_u32_from(expr, expressions) {
            Ok(value) => Self::Known(value),
            Err(_) => Self::Expression(expr),
        }
    }
}

#[derive(Clone, Copy, Debug, thiserror::Error, PartialEq)]
pub enum IndexableLengthError {
    #[error("Type is not indexable, and has no length (validation error)")]
    TypeNotIndexable,
    #[error(transparent)]
    ResolveArraySizeError(#[from] super::ResolveArraySizeError),
    #[error("Array size is still pending")]
    Pending(crate::ArraySize),
}

impl crate::TypeInner {
    /// Return the length of a subscriptable type.
    ///
    /// The `self` parameter should be a handle to a vector, matrix, or array
    /// type, a pointer to one of those, or a value pointer. Arrays may be
    /// fixed-size, dynamically sized, or sized by a specializable constant.
    /// This function does not handle struct member references, as with
    /// `AccessIndex`.
    ///
    /// The value returned is appropriate for bounds checks on subscripting.
    ///
    /// Return an error if `self` does not describe a subscriptable type at all.
    pub fn indexable_length(
        &self,
        module: &crate::Module,
    ) -> Result<IndexableLength, IndexableLengthError> {
        use crate::TypeInner as Ti;
        let known_length = match *self {
            Ti::Vector { size, .. } => size as _,
            Ti::Matrix { columns, .. } => columns as _,
            Ti::Array { size, .. } | Ti::BindingArray { size, .. } => {
                return size.to_indexable_length(module);
            }
            Ti::ValuePointer {
                size: Some(size), ..
            } => size as _,
            Ti::Pointer { base, .. } => {
                // When assigning types to expressions, ResolveContext::Resolve
                // does a separate sub-match here instead of a full recursion,
                // so we'll do the same.
                let base_inner = &module.types[base].inner;
                match *base_inner {
                    Ti::Vector { size, .. } => size as _,
                    Ti::Matrix { columns, .. } => columns as _,
                    Ti::Array { size, .. } | Ti::BindingArray { size, .. } => {
                        return size.to_indexable_length(module)
                    }
                    _ => return Err(IndexableLengthError::TypeNotIndexable),
                }
            }
            _ => return Err(IndexableLengthError::TypeNotIndexable),
        };
        Ok(IndexableLength::Known(known_length))
    }

    /// Return the length of `self`, assuming overrides are yet to be supplied.
    ///
    /// Return the number of elements in `self`:
    ///
    /// - If `self` is a runtime-sized array, then return
    ///   [`IndexableLength::Dynamic`].
    ///
    /// - If `self` is an override-sized array, then assume that override values
    ///   have not yet been supplied, and return [`IndexableLength::Dynamic`].
    ///
    /// - Otherwise, the type simply tells us the length of `self`, so return
    ///   [`IndexableLength::Known`].
    ///
    /// If `self` is not an indexable type at all, return an error.
    ///
    /// The difference between this and `indexable_length_resolved` is that we
    /// treat override-sized arrays and dynamically-sized arrays both as
    /// [`Dynamic`], on the assumption that our callers want to treat both cases
    /// as "not yet possible to check".
    ///
    /// [`Dynamic`]: IndexableLength::Dynamic
    pub fn indexable_length_pending(
        &self,
        module: &crate::Module,
    ) -> Result<IndexableLength, IndexableLengthError> {
        let length = self.indexable_length(module);
        if let Err(IndexableLengthError::Pending(_)) = length {
            return Ok(IndexableLength::Dynamic);
        }
        length
    }

    /// Return the length of `self`, assuming overrides have been resolved.
    ///
    /// Return the number of elements in `self`:
    ///
    /// - If `self` is a runtime-sized array, then return
    ///   [`IndexableLength::Dynamic`].
    ///
    /// - If `self` is an override-sized array, then assume that the override's
    ///   value is a fully-evaluated constant expression, and return
    ///   [`IndexableLength::Known`]. Otherwise, return an error.
    ///
    /// - Otherwise, the type simply tells us the length of `self`, so return
    ///   [`IndexableLength::Known`].
    ///
    /// If `self` is not an indexable type at all, return an error.
    ///
    /// The difference between this and `indexable_length_pending` is
    /// that if `self` is override-sized, we require the override's
    /// value to be known.
    pub fn indexable_length_resolved(
        &self,
        module: &crate::Module,
    ) -> Result<IndexableLength, IndexableLengthError> {
        let length = self.indexable_length(module);

        // If the length is override-based, then try to compute its value now.
        if let Err(IndexableLengthError::Pending(size)) = length {
            if let IndexableLength::Known(computed) = size.resolve(module.to_ctx())? {
                return Ok(IndexableLength::Known(computed));
            }
        }
        length
    }
}

/// The number of elements in an indexable type.
///
/// This summarizes the length of vectors, matrices, and arrays in a way that is
/// convenient for indexing and bounds-checking code.
#[derive(Debug)]
pub enum IndexableLength {
    /// Values of this type always have the given number of elements.
    Known(u32),

    /// The number of elements is determined at runtime.
    Dynamic,
}

impl crate::ArraySize {
    pub const fn to_indexable_length(
        self,
        _module: &crate::Module,
    ) -> Result<IndexableLength, IndexableLengthError> {
        match self {
            Self::Constant(length) => Ok(IndexableLength::Known(length.get())),
            Self::Pending(_) => Err(IndexableLengthError::Pending(self)),
            Self::Dynamic => Ok(IndexableLength::Dynamic),
        }
    }
}
