vyre-conform 0.1.0

Conformance suite for vyre backends — proves byte-identical output to CPU reference
Documentation
//! Mutation enum, classification, and application dispatch.

use super::{
    arithmetic, bitwise, buffer, comparison, control, ir_specific, law, BinOpKind, Branch,
    MutationError,
};
use crate::spec::types::DataType;
use crate::spec::types::MutationClass;

/// A single adversarial source rewrite.
#[derive(Debug, Clone, PartialEq)]
pub enum Mutation {
    // Arithmetic
    /// Swap one arithmetic operator for another.
    ArithOpSwap {
        /// Operator expected in the original source.
        from: BinOpKind,
        /// Operator written into the mutated source.
        to: BinOpKind,
    },
    /// Replace `wrapping_add` with `saturating_add`.
    WrappingAddToSaturatingAdd,
    /// Replace `wrapping_add` with `checked_add`.
    WrappingAddToCheckedAdd,
    /// Increment (or decrement) the first integer literal by `by`.
    ConstantIncrement {
        /// Signed delta applied to the matched integer literal.
        by: i64,
    },
    /// Replace constant `0` with `1`.
    ConstantZeroToOne,
    /// Replace constant `1` with `0`.
    ConstantOneToZero,
    /// Replace `u32::MAX` with `0`.
    ConstantMaxToZero,
    /// Flip one bit in the first integer constant.
    IntegerConstantBitFlip {
        /// Bit index to flip.
        bit: u8,
    },
    /// Flip one representation bit in the first floating-point constant.
    FloatConstantBitFlip {
        /// Bit index to flip.
        bit: u8,
    },

    // Comparison
    /// Swap one comparison operator for another.
    CompareOpSwap {
        /// Comparison operator expected in the original source.
        from: BinOpKind,
        /// Comparison operator written into the mutated source.
        to: BinOpKind,
    },
    /// Invert a boolean result.
    CompareInvert,

    // Bitwise
    /// Swap one bitwise operator for another.
    BitOpSwap {
        /// Bitwise operator expected in the original source.
        from: BinOpKind,
        /// Bitwise operator written into the mutated source.
        to: BinOpKind,
    },
    /// Delete a bitwise-not (`!x` → `x`).
    BitNotDelete,
    /// Delete a bitwise-and mask (`x & mask` → `x`).
    BitAndMaskDelete,

    // Control flow
    /// Delete a control-flow branch.
    ControlFlowDelete {
        /// Branch arm removed from the source.
        branch: Branch,
    },
    /// Invert an `if` condition.
    ControlConditionInvert,
    /// Increment the end of a `0..n` loop range.
    ControlLoopRangeEndIncrement,
    /// Decrement the end of a `0..n` loop range.
    ControlLoopRangeEndDecrement,
    /// Increment the start of a `0..n` loop range to `1..n`.
    ControlLoopRangeStartIncrement,
    /// Replace `break` with `continue`.
    ControlBreakToContinue,
    /// Remove a `continue` statement.
    ControlContinueRemove,
    /// Skip the first control-flow branch by forcing its condition false.
    ControlFlowSkip,

    // Buffer
    /// Shift a buffer index by `by`.
    BufferIndexShift {
        /// Signed offset applied to the matched buffer index.
        by: i32,
    },
    /// Clamp a buffer index to `0`.
    BufferIndexClampFirst,
    /// Clamp a buffer index to `len - 1`.
    BufferIndexClampLast,
    /// Swap a buffer read for a write (or vice versa).
    BufferReadWriteSwap,
    /// Weaken an atomic memory ordering.
    AtomicOrderingWeaken,
    /// Weaken acquire-release atomic orderings.
    AtomicOrderingAcqRelWeaken,
    /// Change a buffer element count by a signed offset.
    BufferCountShift {
        /// Signed count offset.
        by: i32,
    },

    // IR-specific
    /// Swap a `BinOp` variant in IR definitions.
    IrBinOpSwap {
        /// IR binary operator expected in the original source.
        from: BinOpKind,
        /// IR binary operator written into the mutated source.
        to: BinOpKind,
    },
    /// Swap a `DataType` variant in signatures.
    IrDataTypeSwap {
        /// Data type expected in the original source.
        from: DataType,
        /// Data type written into the mutated source.
        to: DataType,
    },
    /// Delete a law from an `OpSpec`.
    IrDeleteLaw {
        /// Operation id whose declared law entry is removed.
        op: &'static str,
    },
    /// Swap the `reference_fn` for the wrong op.
    IrSwapReferenceFn {
        /// Operation id whose reference function is replaced.
        op: &'static str,
        /// Operation id whose reference function is incorrectly reused.
        wrong_op: &'static str,
    },
    /// Swap `BufferAccess::Storage` for `BufferAccess::Workgroup`.
    IrBufferAccessSwap,
    /// Remove a validation rule entry.
    IrRemoveValidationRule,
    /// Swap two opcodes in the bytecode converter.
    BytecodeConverterSwap {
        /// Opcode expected in the original converter.
        from_opcode: u8,
        /// Opcode written into the mutated converter.
        to_opcode: u8,
    },
    /// Emit the wrong WGSL op in the lowering table.
    IrWrongWgslOp,
    /// Change `workgroup_size` computation.
    IrWorkgroupSizeChange,
    /// Multiply a workgroup stride by a factor.
    IrWorkgroupStrideMul {
        /// Multiplicative factor.
        factor: u32,
    },
    /// Divide a workgroup stride by a divisor.
    IrWorkgroupStrideDiv {
        /// Divisor.
        divisor: u32,
    },
    /// Multiply a workgroup size by a factor.
    IrWorkgroupSizeMul {
        /// Multiplicative factor.
        factor: u32,
    },
    /// Divide a workgroup size by a divisor.
    IrWorkgroupSizeDiv {
        /// Divisor.
        divisor: u32,
    },
    /// Offset a workgroup size by a signed delta.
    IrWorkgroupSizeOffset {
        /// Signed offset.
        by: i32,
    },

    // Lowering
    /// Remove a bounds check in lowering.
    LowerRemoveBoundsCheck,
    /// Remove a shift mask in lowering.
    LowerRemoveShiftMask,

    // Law
    /// Falsely claim a law holds for an op.
    LawFalselyClaim {
        /// Law name injected into the operation declaration.
        law: &'static str,
        /// Operation id that receives the false law claim.
        op: &'static str,
    },
    /// Corrupt the identity element claimed for an op.
    LawIdentityCorrupt {
        /// Operation id whose identity element is corrupted.
        op: &'static str,
        /// Incorrect identity element written into the declaration.
        wrong: u32,
    },
    /// Corrupt the absorbing element claimed for an op.
    LawAbsorbingCorrupt {
        /// Operation id whose absorbing element is corrupted.
        op: &'static str,
        /// Incorrect absorbing element written into the declaration.
        wrong: u32,
    },
}

/// Return the class of a mutation.
#[must_use]
#[inline]
pub fn class_of(m: &Mutation) -> MutationClass {
    use MutationClass as C;
    match m {
        Mutation::ArithOpSwap { .. }
        | Mutation::WrappingAddToSaturatingAdd
        | Mutation::WrappingAddToCheckedAdd => C::ArithmeticMutations,
        Mutation::ConstantIncrement { .. }
        | Mutation::ConstantZeroToOne
        | Mutation::ConstantOneToZero
        | Mutation::ConstantMaxToZero
        | Mutation::IntegerConstantBitFlip { .. }
        | Mutation::FloatConstantBitFlip { .. } => C::ConstantMutations,
        Mutation::CompareOpSwap { .. } | Mutation::CompareInvert => C::ComparisonMutations,
        Mutation::BitOpSwap { .. } | Mutation::BitNotDelete | Mutation::BitAndMaskDelete => {
            C::BitwiseMutations
        }
        Mutation::ControlFlowDelete { .. }
        | Mutation::ControlConditionInvert
        | Mutation::ControlLoopRangeEndIncrement
        | Mutation::ControlLoopRangeEndDecrement
        | Mutation::ControlLoopRangeStartIncrement
        | Mutation::ControlBreakToContinue
        | Mutation::ControlContinueRemove
        | Mutation::ControlFlowSkip => C::ControlFlowMutations,
        Mutation::BufferIndexShift { .. }
        | Mutation::BufferIndexClampFirst
        | Mutation::BufferIndexClampLast
        | Mutation::BufferReadWriteSwap
        | Mutation::BufferCountShift { .. } => C::BufferAccessMutations,
        Mutation::AtomicOrderingWeaken | Mutation::AtomicOrderingAcqRelWeaken => {
            C::OrderingMutations
        }
        Mutation::IrBinOpSwap { .. }
        | Mutation::IrDataTypeSwap { .. }
        | Mutation::IrDeleteLaw { .. }
        | Mutation::IrSwapReferenceFn { .. }
        | Mutation::IrBufferAccessSwap
        | Mutation::IrRemoveValidationRule
        | Mutation::BytecodeConverterSwap { .. }
        | Mutation::IrWrongWgslOp
        | Mutation::IrWorkgroupSizeChange
        | Mutation::IrWorkgroupStrideMul { .. }
        | Mutation::IrWorkgroupStrideDiv { .. }
        | Mutation::IrWorkgroupSizeMul { .. }
        | Mutation::IrWorkgroupSizeDiv { .. }
        | Mutation::IrWorkgroupSizeOffset { .. } => C::IrStructuralMutations,
        Mutation::LowerRemoveBoundsCheck | Mutation::LowerRemoveShiftMask => C::LoweringMutations,
        Mutation::LawFalselyClaim { .. }
        | Mutation::LawIdentityCorrupt { .. }
        | Mutation::LawAbsorbingCorrupt { .. } => C::LawMutations,
    }
}

/// Apply a mutation to a Rust source string.
#[inline]
pub fn apply(source: &str, mutation: &Mutation) -> Result<String, MutationError> {
    match mutation {
        Mutation::ArithOpSwap { from, to } => Ok(arithmetic::apply_op_swap(source, *from, *to)),
        Mutation::WrappingAddToSaturatingAdd => {
            Ok(arithmetic::apply_wrapping_to_saturating(source))
        }
        Mutation::WrappingAddToCheckedAdd => Ok(arithmetic::apply_wrapping_to_checked(source)),
        Mutation::ConstantIncrement { by } => Ok(arithmetic::apply_constant_increment(source, *by)),
        Mutation::ConstantZeroToOne => Ok(arithmetic::apply_constant_zero_to_one(source)),
        Mutation::ConstantOneToZero => Ok(arithmetic::apply_constant_one_to_zero(source)),
        Mutation::ConstantMaxToZero => Ok(arithmetic::apply_constant_max_to_zero(source)),
        Mutation::IntegerConstantBitFlip { bit } => {
            Ok(arithmetic::apply_integer_constant_bit_flip(source, *bit))
        }
        Mutation::FloatConstantBitFlip { bit } => {
            Ok(arithmetic::apply_float_constant_bit_flip(source, *bit))
        }

        Mutation::CompareOpSwap { from, to } => Ok(comparison::apply_op_swap(source, *from, *to)),
        Mutation::CompareInvert => Ok(comparison::apply_invert(source)),

        Mutation::BitOpSwap { from, to } => Ok(bitwise::apply_op_swap(source, *from, *to)),
        Mutation::BitNotDelete => Ok(bitwise::apply_not_delete(source)),
        Mutation::BitAndMaskDelete => Ok(bitwise::apply_and_mask_delete(source)),

        Mutation::ControlFlowDelete { branch } => Ok(control::apply_branch_delete(source, *branch)),
        Mutation::ControlConditionInvert => Ok(control::apply_condition_invert(source)),
        Mutation::ControlLoopRangeEndIncrement => Ok(control::apply_loop_end_shift(source, 1)),
        Mutation::ControlLoopRangeEndDecrement => Ok(control::apply_loop_end_shift(source, -1)),
        Mutation::ControlLoopRangeStartIncrement => Ok(control::apply_loop_start_shift(source, 1)),
        Mutation::ControlBreakToContinue => Ok(control::apply_break_to_continue(source)),
        Mutation::ControlContinueRemove => Ok(control::apply_continue_remove(source)),
        Mutation::ControlFlowSkip => Ok(control::apply_flow_skip(source)),

        Mutation::BufferIndexShift { by } => Ok(buffer::apply_index_shift(source, *by)),
        Mutation::BufferIndexClampFirst => Ok(buffer::apply_index_clamp(source, "0")),
        Mutation::BufferIndexClampLast => Ok(buffer::apply_index_clamp(source, "len - 1")),
        Mutation::BufferReadWriteSwap => Ok(buffer::apply_read_write_swap(source)),
        Mutation::AtomicOrderingWeaken => Ok(buffer::apply_ordering_weaken(source)),
        Mutation::AtomicOrderingAcqRelWeaken => Ok(buffer::apply_acqrel_ordering_weaken(source)),
        Mutation::BufferCountShift { by } => Ok(buffer::apply_count_shift(source, *by)),

        Mutation::IrBinOpSwap { from, to } => Ok(ir_specific::apply_binop_swap(source, *from, *to)),
        Mutation::IrDataTypeSwap { from, to } => Ok(ir_specific::apply_datatype_swap(
            source,
            from.clone(),
            to.clone(),
        )),
        Mutation::IrDeleteLaw { op } => Ok(ir_specific::apply_delete_law(source, op)),
        Mutation::IrSwapReferenceFn { op, wrong_op } => {
            Ok(ir_specific::apply_swap_reference_fn(source, op, wrong_op))
        }
        Mutation::IrBufferAccessSwap => Ok(ir_specific::apply_buffer_access_swap(source)),
        Mutation::IrRemoveValidationRule => Ok(ir_specific::apply_remove_validation_rule(source)),
        Mutation::BytecodeConverterSwap {
            from_opcode,
            to_opcode,
        } => Ok(ir_specific::apply_bytecode_swap(
            source,
            *from_opcode,
            *to_opcode,
        )),
        Mutation::IrWrongWgslOp => Ok(ir_specific::apply_wrong_wgsl_op(source)),
        Mutation::IrWorkgroupSizeChange => Ok(ir_specific::apply_workgroup_size_change(source)),
        Mutation::IrWorkgroupStrideMul { factor } => {
            Ok(ir_specific::apply_workgroup_stride_mul(source, *factor))
        }
        Mutation::IrWorkgroupStrideDiv { divisor } => {
            Ok(ir_specific::apply_workgroup_stride_div(source, *divisor))
        }
        Mutation::IrWorkgroupSizeMul { factor } => {
            Ok(ir_specific::apply_workgroup_size_mul(source, *factor))
        }
        Mutation::IrWorkgroupSizeDiv { divisor } => {
            Ok(ir_specific::apply_workgroup_size_div(source, *divisor))
        }
        Mutation::IrWorkgroupSizeOffset { by } => {
            Ok(ir_specific::apply_workgroup_size_offset(source, *by))
        }

        Mutation::LowerRemoveBoundsCheck => Ok(ir_specific::apply_remove_bounds_check(source)),
        Mutation::LowerRemoveShiftMask => Ok(ir_specific::apply_remove_shift_mask(source)),

        Mutation::LawFalselyClaim { law, op } => Ok(law::apply_falsely_claim(source, law, op)),
        Mutation::LawIdentityCorrupt { op, wrong } => {
            Ok(law::apply_identity_corrupt(source, op, *wrong))
        }
        Mutation::LawAbsorbingCorrupt { op, wrong } => {
            Ok(law::apply_absorbing_corrupt(source, op, *wrong))
        }
    }
}

/// Return every mutation belonging to `class`.
#[must_use]
#[inline]
pub fn mutations_for(class: MutationClass) -> Vec<Mutation> {
    super::catalog::MUTATION_CATALOG
        .iter()
        .filter(|m| class_of(m) == class)
        .cloned()
        .collect()
}