use crate::codegen::{MachineBasicBlock, MachineFunction, MachineInstr, MachineOperand};
use crate::x86::x86_instr_info::{X86InstrInfo, X86Opcode};
use crate::x86::x86_register_info::{RegClass, X86Reg};
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct MachineSourceLocation {
pub block_idx: usize,
pub block_name: String,
pub instr_idx: usize,
pub operand_idx: Option<usize>,
}
impl MachineSourceLocation {
pub fn new(block_idx: usize, block_name: &str, instr_idx: usize) -> Self {
Self {
block_idx,
block_name: block_name.to_string(),
instr_idx,
operand_idx: None,
}
}
pub fn with_operand(mut self, op_idx: usize) -> Self {
self.operand_idx = Some(op_idx);
self
}
pub fn block_only(block_idx: usize, block_name: &str) -> Self {
Self {
block_idx,
block_name: block_name.to_string(),
instr_idx: 0,
operand_idx: None,
}
}
}
impl fmt::Display for MachineSourceLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "bb.{}.{}", self.block_idx, self.block_name)?;
write!(f, ":%{}", self.instr_idx)?;
if let Some(op) = self.operand_idx {
write!(f, ".{}", op)?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86VerificationErrorKind {
SsaMultipleDef,
SsaNoDef,
SsaUseBeforeDef,
PhiNotAtBlockStart,
PhiMissingPredecessor,
PhiExtraPredecessor,
PhiWrongNumOperands,
PhiCriticalEdge,
RegClassMismatch,
RegClassUnsupported,
WrongNumOperands,
WrongOperandType,
InvalidOperand,
MissingTerminator,
InstructionsAfterTerminator,
MultipleTerminators,
CfgSuccessorNotFound,
CfgPredecessorInconsistent,
CfgUnreachableBlock,
PostRaVirtRegRemaining,
PhysRegUseAfterClobber,
PhysRegDefAfterUse,
PhysRegReservedViolation,
StackSlotOverlap,
StackSlotInvalidOffset,
StackSlotUnaligned,
FramePointerInconsistent,
FrameSetupEpilogueMismatch,
ReturnAddressClobbered,
NoReturnInstruction,
CalleeSavedClobbered,
CalleeSavedNotRestored,
ArgumentRegisterClobbered,
ReturnRegisterNotSet,
CallerSavedInconsistent,
BundleHeaderNotFirst,
BundleInteriorNotInBundle,
BundleAlignmentViolation,
BundleEnterNotTerminator,
LandingPadNotFirst,
LandingPadClobberLiveIn,
LandingPadMissingEhLabel,
PatchPointMissingNopSled,
StackMapInvalidOperands,
PatchPointOperandCount,
RexPrefixInvalid,
RexPrefixRedundant,
RexPrefixMissing,
RexBEncodingViolation,
RexWEncodingViolation,
VexPrefixInvalid,
VexLBitInvalid,
VexMmmInvalid,
VexPPInvalid,
VexWInvalid,
VexEncodingViolation,
EvexPrefixInvalid,
EvexRoundingInvalid,
EvexSaeInvalid,
EvexBcstInvalid,
EvexOpmaskRequired,
EvexEncodingViolation,
ModRmByteInvalid,
ModRmModInvalid,
ModRmRegInvalid,
ModRmRmInvalid,
ModRmDispSizeMismatch,
SibByteInvalid,
SibScaleInvalid,
SibIndexInvalid,
SibBaseInvalid,
SibEspRspConstraint,
SegmentOverrideInvalid,
SegmentOverrideUnnecessary,
SegmentOverrideInconsistent,
AddressSizeInconsistent,
AddressSizeOverrideInvalid,
AddressPrefixMissing,
OperandSizeInconsistent,
OperandSizeOverrideInvalid,
OperandPrefixMissing,
LockPrefixInvalidTarget,
LockPrefixMissing,
LockPrefixUnnecessary,
RepPrefixInvalidTarget,
RepPrefixRepeatCountMissing,
RepPrefixInconsistent,
UnknownOpcode,
FeatureNotAvailable,
InternalError,
}
impl fmt::Display for X86VerificationErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
X86VerificationErrorKind::SsaMultipleDef => write!(f, "SSA multiple definition"),
X86VerificationErrorKind::SsaNoDef => write!(f, "SSA no definition"),
X86VerificationErrorKind::SsaUseBeforeDef => write!(f, "SSA use before definition"),
X86VerificationErrorKind::PhiNotAtBlockStart => write!(f, "PHI not at block start"),
X86VerificationErrorKind::PhiMissingPredecessor => write!(f, "PHI missing predecessor"),
X86VerificationErrorKind::PhiExtraPredecessor => write!(f, "PHI extra predecessor"),
X86VerificationErrorKind::PhiWrongNumOperands => {
write!(f, "PHI wrong number of operands")
}
X86VerificationErrorKind::PhiCriticalEdge => write!(f, "PHI on critical edge"),
X86VerificationErrorKind::RegClassMismatch => write!(f, "register class mismatch"),
X86VerificationErrorKind::RegClassUnsupported => {
write!(f, "unsupported register class")
}
X86VerificationErrorKind::WrongNumOperands => write!(f, "wrong number of operands"),
X86VerificationErrorKind::WrongOperandType => write!(f, "wrong operand type"),
X86VerificationErrorKind::InvalidOperand => write!(f, "invalid operand"),
X86VerificationErrorKind::MissingTerminator => write!(f, "missing terminator"),
X86VerificationErrorKind::InstructionsAfterTerminator => {
write!(f, "instructions after terminator")
}
X86VerificationErrorKind::MultipleTerminators => write!(f, "multiple terminators"),
X86VerificationErrorKind::CfgSuccessorNotFound => write!(f, "CFG successor not found"),
X86VerificationErrorKind::CfgPredecessorInconsistent => {
write!(f, "CFG predecessor inconsistent")
}
X86VerificationErrorKind::CfgUnreachableBlock => write!(f, "CFG unreachable block"),
X86VerificationErrorKind::PostRaVirtRegRemaining => {
write!(f, "virtual register remaining post-RA")
}
X86VerificationErrorKind::PhysRegUseAfterClobber => {
write!(f, "physical register use after clobber")
}
X86VerificationErrorKind::PhysRegDefAfterUse => {
write!(f, "physical register def after use")
}
X86VerificationErrorKind::PhysRegReservedViolation => {
write!(f, "reserved register violation")
}
X86VerificationErrorKind::StackSlotOverlap => write!(f, "stack slot overlap"),
X86VerificationErrorKind::StackSlotInvalidOffset => {
write!(f, "stack slot invalid offset")
}
X86VerificationErrorKind::StackSlotUnaligned => write!(f, "stack slot unaligned"),
X86VerificationErrorKind::FramePointerInconsistent => {
write!(f, "frame pointer inconsistent")
}
X86VerificationErrorKind::FrameSetupEpilogueMismatch => {
write!(f, "frame setup/epilogue mismatch")
}
X86VerificationErrorKind::ReturnAddressClobbered => {
write!(f, "return address clobbered")
}
X86VerificationErrorKind::NoReturnInstruction => write!(f, "no return instruction"),
X86VerificationErrorKind::CalleeSavedClobbered => {
write!(f, "callee-saved register clobbered")
}
X86VerificationErrorKind::CalleeSavedNotRestored => {
write!(f, "callee-saved register not restored")
}
X86VerificationErrorKind::ArgumentRegisterClobbered => {
write!(f, "argument register clobbered before use")
}
X86VerificationErrorKind::ReturnRegisterNotSet => {
write!(f, "return register not set")
}
X86VerificationErrorKind::CallerSavedInconsistent => {
write!(f, "caller-saved register inconsistent")
}
X86VerificationErrorKind::BundleHeaderNotFirst => {
write!(f, "bundle header not first")
}
X86VerificationErrorKind::BundleInteriorNotInBundle => {
write!(f, "bundle interior not in bundle")
}
X86VerificationErrorKind::BundleAlignmentViolation => {
write!(f, "bundle alignment violation")
}
X86VerificationErrorKind::BundleEnterNotTerminator => {
write!(f, "bundle enter not terminator")
}
X86VerificationErrorKind::LandingPadNotFirst => write!(f, "landing pad not first"),
X86VerificationErrorKind::LandingPadClobberLiveIn => {
write!(f, "landing pad clobbers live-in")
}
X86VerificationErrorKind::LandingPadMissingEhLabel => {
write!(f, "landing pad missing EH label")
}
X86VerificationErrorKind::PatchPointMissingNopSled => {
write!(f, "patchpoint missing NOP sled")
}
X86VerificationErrorKind::StackMapInvalidOperands => {
write!(f, "stackmap invalid operands")
}
X86VerificationErrorKind::PatchPointOperandCount => {
write!(f, "patchpoint operand count mismatch")
}
X86VerificationErrorKind::RexPrefixInvalid => write!(f, "REX prefix invalid"),
X86VerificationErrorKind::RexPrefixRedundant => write!(f, "REX prefix redundant"),
X86VerificationErrorKind::RexPrefixMissing => write!(f, "REX prefix missing"),
X86VerificationErrorKind::RexBEncodingViolation => {
write!(f, "REX.B encoding violation")
}
X86VerificationErrorKind::RexWEncodingViolation => {
write!(f, "REX.W encoding violation")
}
X86VerificationErrorKind::VexPrefixInvalid => write!(f, "VEX prefix invalid"),
X86VerificationErrorKind::VexLBitInvalid => write!(f, "VEX.L bit invalid"),
X86VerificationErrorKind::VexMmmInvalid => write!(f, "VEX.mmmm invalid"),
X86VerificationErrorKind::VexPPInvalid => write!(f, "VEX.pp invalid"),
X86VerificationErrorKind::VexWInvalid => write!(f, "VEX.W invalid"),
X86VerificationErrorKind::VexEncodingViolation => write!(f, "VEX encoding violation"),
X86VerificationErrorKind::EvexPrefixInvalid => write!(f, "EVEX prefix invalid"),
X86VerificationErrorKind::EvexRoundingInvalid => write!(f, "EVEX rounding invalid"),
X86VerificationErrorKind::EvexSaeInvalid => write!(f, "EVEX SAE invalid"),
X86VerificationErrorKind::EvexBcstInvalid => write!(f, "EVEX broadcast invalid"),
X86VerificationErrorKind::EvexOpmaskRequired => write!(f, "EVEX opmask required"),
X86VerificationErrorKind::EvexEncodingViolation => write!(f, "EVEX encoding violation"),
X86VerificationErrorKind::ModRmByteInvalid => write!(f, "ModR/M byte invalid"),
X86VerificationErrorKind::ModRmModInvalid => write!(f, "ModR/M mod invalid"),
X86VerificationErrorKind::ModRmRegInvalid => write!(f, "ModR/M reg invalid"),
X86VerificationErrorKind::ModRmRmInvalid => write!(f, "ModR/M r/m invalid"),
X86VerificationErrorKind::ModRmDispSizeMismatch => {
write!(f, "ModR/M displacement size mismatch")
}
X86VerificationErrorKind::SibByteInvalid => write!(f, "SIB byte invalid"),
X86VerificationErrorKind::SibScaleInvalid => write!(f, "SIB scale invalid"),
X86VerificationErrorKind::SibIndexInvalid => write!(f, "SIB index invalid"),
X86VerificationErrorKind::SibBaseInvalid => write!(f, "SIB base invalid"),
X86VerificationErrorKind::SibEspRspConstraint => write!(f, "SIB ESP/RSP constraint"),
X86VerificationErrorKind::SegmentOverrideInvalid => {
write!(f, "segment override invalid")
}
X86VerificationErrorKind::SegmentOverrideUnnecessary => {
write!(f, "segment override unnecessary")
}
X86VerificationErrorKind::SegmentOverrideInconsistent => {
write!(f, "segment override inconsistent")
}
X86VerificationErrorKind::AddressSizeInconsistent => {
write!(f, "address size inconsistent")
}
X86VerificationErrorKind::AddressSizeOverrideInvalid => {
write!(f, "address size override invalid")
}
X86VerificationErrorKind::AddressPrefixMissing => write!(f, "address prefix missing"),
X86VerificationErrorKind::OperandSizeInconsistent => {
write!(f, "operand size inconsistent")
}
X86VerificationErrorKind::OperandSizeOverrideInvalid => {
write!(f, "operand size override invalid")
}
X86VerificationErrorKind::OperandPrefixMissing => write!(f, "operand prefix missing"),
X86VerificationErrorKind::LockPrefixInvalidTarget => {
write!(f, "LOCK prefix invalid target")
}
X86VerificationErrorKind::LockPrefixMissing => write!(f, "LOCK prefix missing"),
X86VerificationErrorKind::LockPrefixUnnecessary => write!(f, "LOCK prefix unnecessary"),
X86VerificationErrorKind::RepPrefixInvalidTarget => {
write!(f, "REP prefix invalid target")
}
X86VerificationErrorKind::RepPrefixRepeatCountMissing => {
write!(f, "REP prefix repeat count missing")
}
X86VerificationErrorKind::RepPrefixInconsistent => {
write!(f, "REP prefix inconsistent")
}
X86VerificationErrorKind::UnknownOpcode => write!(f, "unknown opcode"),
X86VerificationErrorKind::FeatureNotAvailable => write!(f, "feature not available"),
X86VerificationErrorKind::InternalError => write!(f, "internal verifier error"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum VerifierSeverity {
Info,
Warning,
Error,
Fatal,
}
impl fmt::Display for VerifierSeverity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
VerifierSeverity::Info => write!(f, "INFO"),
VerifierSeverity::Warning => write!(f, "WARNING"),
VerifierSeverity::Error => write!(f, "ERROR"),
VerifierSeverity::Fatal => write!(f, "FATAL"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86VerificationError {
pub kind: X86VerificationErrorKind,
pub severity: VerifierSeverity,
pub message: String,
pub location: MachineSourceLocation,
pub context: Vec<String>,
}
impl X86VerificationError {
pub fn new(
kind: X86VerificationErrorKind,
severity: VerifierSeverity,
message: impl Into<String>,
location: MachineSourceLocation,
) -> Self {
Self {
kind,
severity,
message: message.into(),
location,
context: Vec::new(),
}
}
pub fn with_context(mut self, ctx: impl Into<String>) -> Self {
self.context.push(ctx.into());
self
}
pub fn is_fatal(&self) -> bool {
self.severity == VerifierSeverity::Fatal
}
pub fn is_error_or_fatal(&self) -> bool {
self.severity >= VerifierSeverity::Error
}
}
impl fmt::Display for X86VerificationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} [{}] at {}: {}",
self.severity, self.kind, self.location, self.message
)?;
for ctx in &self.context {
write!(f, "\n - {}", ctx)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct X86VerificationResult {
pub function_name: String,
pub errors: Vec<X86VerificationError>,
pub warnings: Vec<X86VerificationError>,
pub infos: Vec<X86VerificationError>,
pub blocks_verified: usize,
pub instructions_verified: usize,
pub passed: bool,
}
impl X86VerificationResult {
pub fn new(function_name: &str) -> Self {
Self {
function_name: function_name.to_string(),
errors: Vec::new(),
warnings: Vec::new(),
infos: Vec::new(),
blocks_verified: 0,
instructions_verified: 0,
passed: true,
}
}
pub fn add_error(&mut self, err: X86VerificationError) {
match err.severity {
VerifierSeverity::Fatal | VerifierSeverity::Error => {
self.passed = false;
self.errors.push(err);
}
VerifierSeverity::Warning => {
self.warnings.push(err);
}
VerifierSeverity::Info => {
self.infos.push(err);
}
}
}
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
pub fn error_count(&self) -> usize {
self.errors.len()
}
pub fn warning_count(&self) -> usize {
self.warnings.len()
}
pub fn format_summary(&self) -> String {
if self.passed {
format!(
"Verification PASSED for '{}': {} blocks, {} instructions, {} warnings",
self.function_name,
self.blocks_verified,
self.instructions_verified,
self.warnings.len()
)
} else {
format!(
"Verification FAILED for '{}': {} error(s), {} warning(s) in {} blocks, {} instructions",
self.function_name,
self.errors.len(),
self.warnings.len(),
self.blocks_verified,
self.instructions_verified,
)
}
}
}
#[derive(Debug, Clone)]
pub struct VerifierStrictness {
pub warnings_as_errors: bool,
pub verify_after_every_pass: bool,
pub verify_bundles: bool,
pub verify_landing_pads: bool,
pub verify_patchpoints: bool,
pub verify_frame: bool,
pub verify_calling_conv: bool,
pub verify_encoding: bool,
pub verify_prefixes: bool,
pub verify_modrm_sib: bool,
pub verify_lock_rep: bool,
pub max_errors: usize,
pub verbose: bool,
}
impl Default for VerifierStrictness {
fn default() -> Self {
Self {
warnings_as_errors: false,
verify_after_every_pass: false,
verify_bundles: true,
verify_landing_pads: true,
verify_patchpoints: true,
verify_frame: true,
verify_calling_conv: true,
verify_encoding: true,
verify_prefixes: true,
verify_modrm_sib: true,
verify_lock_rep: true,
max_errors: 100,
verbose: false,
}
}
}
impl VerifierStrictness {
pub fn relaxed() -> Self {
Self {
verify_bundles: false,
verify_landing_pads: false,
verify_patchpoints: false,
verify_calling_conv: false,
verify_encoding: false,
verify_prefixes: false,
verify_modrm_sib: false,
verify_lock_rep: false,
max_errors: 50,
..Default::default()
}
}
pub fn strict() -> Self {
Self {
warnings_as_errors: true,
max_errors: 200,
..Default::default()
}
}
pub fn debug() -> Self {
Self {
warnings_as_errors: true,
max_errors: 500,
verbose: true,
..Default::default()
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VerificationPhase {
PreRA,
PostRA,
}
impl fmt::Display for VerificationPhase {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
VerificationPhase::PreRA => write!(f, "Pre-RA"),
VerificationPhase::PostRA => write!(f, "Post-RA"),
}
}
}
#[derive(Debug, Clone)]
struct RegisterLiveness {
live_phys_regs: HashMap<u32, MachineSourceLocation>,
virt_reg_defs: HashMap<u32, MachineSourceLocation>,
virt_reg_uses: HashMap<u32, Vec<MachineSourceLocation>>,
clobbered_callee_saved: HashSet<u32>,
frame_reg_at_entry: Option<u32>,
}
impl RegisterLiveness {
fn new() -> Self {
Self {
live_phys_regs: HashMap::new(),
virt_reg_defs: HashMap::new(),
virt_reg_uses: HashMap::new(),
clobbered_callee_saved: HashSet::new(),
frame_reg_at_entry: None,
}
}
fn record_phys_def(&mut self, reg: u32, loc: MachineSourceLocation) {
self.live_phys_regs.insert(reg, loc);
}
fn record_phys_use(&mut self, reg: u32) -> Option<&MachineSourceLocation> {
self.live_phys_regs.get(®)
}
fn record_phys_clobber(&mut self, reg: u32, _loc: MachineSourceLocation) {
self.live_phys_regs.remove(®);
self.clobbered_callee_saved.insert(reg);
}
fn record_virt_def(&mut self, reg: u32, loc: MachineSourceLocation) {
self.virt_reg_defs.entry(reg).or_insert(loc);
}
fn record_virt_use(&mut self, reg: u32, loc: MachineSourceLocation) {
self.virt_reg_uses.entry(reg).or_default().push(loc);
}
fn is_virt_def_dominated(&self, _reg: u32) -> bool {
true
}
fn reset(&mut self) {
self.live_phys_regs.clear();
self.virt_reg_defs.clear();
self.virt_reg_uses.clear();
self.clobbered_callee_saved.clear();
self.frame_reg_at_entry = None;
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct StackSlotInfo {
pub slot_id: u32,
pub offset: i64,
pub size: u64,
pub alignment: u64,
pub location: MachineSourceLocation,
}
impl StackSlotInfo {
fn overlaps_with(&self, other: &StackSlotInfo) -> bool {
let self_end = self.offset + self.size as i64;
let other_end = other.offset + other.size as i64;
self.offset < other_end && other.offset < self_end
}
}
fn build_predecessor_map(blocks: &[MachineBasicBlock]) -> HashMap<String, Vec<String>> {
let mut preds: HashMap<String, Vec<String>> = HashMap::new();
for block in blocks {
preds.entry(block.name.clone()).or_default();
}
for block in blocks {
for &succ_idx in &block.successors {
if let Some(succ_block) = blocks.get(succ_idx) {
preds
.entry(succ_block.name.clone())
.or_default()
.push(block.name.clone());
}
}
}
preds
}
fn block_index_by_name(blocks: &[MachineBasicBlock], name: &str) -> Option<usize> {
blocks.iter().position(|b| b.name == name)
}
fn is_terminator(opcode: u32) -> bool {
matches!(
opcode,
x if x == X86Opcode::RET as u32
|| x == X86Opcode::JMP as u32
|| x == X86Opcode::JMP1 as u32
|| x == X86Opcode::JMP2 as u32
|| x == X86Opcode::JMP4 as u32
|| x == X86Opcode::JO as u32
|| x == X86Opcode::JNO as u32
|| x == X86Opcode::JB as u32
|| x == X86Opcode::JAE as u32
|| x == X86Opcode::JE as u32
|| x == X86Opcode::JNE as u32
|| x == X86Opcode::JBE as u32
|| x == X86Opcode::JA as u32
|| x == X86Opcode::JS as u32
|| x == X86Opcode::JNS as u32
|| x == X86Opcode::JP as u32
|| x == X86Opcode::JNP as u32
|| x == X86Opcode::JL as u32
|| x == X86Opcode::JGE as u32
|| x == X86Opcode::JLE as u32
|| x == X86Opcode::JG as u32
|| x == X86Opcode::CALL as u32
)
}
fn is_conditional_branch(opcode: u32) -> bool {
matches!(
opcode,
x if x == X86Opcode::JO as u32
|| x == X86Opcode::JNO as u32
|| x == X86Opcode::JB as u32
|| x == X86Opcode::JAE as u32
|| x == X86Opcode::JE as u32
|| x == X86Opcode::JNE as u32
|| x == X86Opcode::JBE as u32
|| x == X86Opcode::JA as u32
|| x == X86Opcode::JS as u32
|| x == X86Opcode::JNS as u32
|| x == X86Opcode::JP as u32
|| x == X86Opcode::JNP as u32
|| x == X86Opcode::JL as u32
|| x == X86Opcode::JGE as u32
|| x == X86Opcode::JLE as u32
|| x == X86Opcode::JG as u32
|| x == X86Opcode::LOOP as u32
|| x == X86Opcode::LOOPE as u32
|| x == X86Opcode::LOOPNE as u32
)
}
fn is_unconditional_branch(opcode: u32) -> bool {
match opcode {
x if x == X86Opcode::JMP as u32
|| x == X86Opcode::JMP1 as u32
|| x == X86Opcode::JMP2 as u32
|| x == X86Opcode::JMP4 as u32 =>
{
true
}
_ => false,
}
}
fn is_call(opcode: u32) -> bool {
opcode == X86Opcode::CALL as u32
}
fn is_return(opcode: u32) -> bool {
opcode == X86Opcode::RET as u32
|| opcode == X86Opcode::RET1 as u32
|| opcode == X86Opcode::RET2 as u32
}
fn is_phi(opcode: u32) -> bool {
opcode == X86Opcode::PHI as u32
}
fn defines_flags(opcode: u32) -> bool {
matches!(
opcode,
x if x == X86Opcode::ADD as u32
|| x == X86Opcode::ADC as u32
|| x == X86Opcode::SUB as u32
|| x == X86Opcode::SBB as u32
|| x == X86Opcode::AND as u32
|| x == X86Opcode::OR as u32
|| x == X86Opcode::XOR as u32
|| x == X86Opcode::TEST as u32
|| x == X86Opcode::CMP as u32
|| x == X86Opcode::SHL as u32
|| x == X86Opcode::SHR as u32
|| x == X86Opcode::SAR as u32
|| x == X86Opcode::INC as u32
|| x == X86Opcode::DEC as u32
|| x == X86Opcode::NEG as u32
)
}
fn uses_flags(opcode: u32) -> bool {
is_conditional_branch(opcode)
|| matches!(
opcode,
x if x == X86Opcode::CMOVO as u32
|| x == X86Opcode::CMOVNO as u32
|| x == X86Opcode::CMOVB as u32
|| x == X86Opcode::CMOVAE as u32
|| x == X86Opcode::CMOVE as u32
|| x == X86Opcode::CMOVNE as u32
|| x == X86Opcode::CMOVBE as u32
|| x == X86Opcode::CMOVA as u32
|| x == X86Opcode::CMOVS as u32
|| x == X86Opcode::CMOVNS as u32
|| x == X86Opcode::CMOVP as u32
|| x == X86Opcode::CMOVNP as u32
|| x == X86Opcode::CMOVL as u32
|| x == X86Opcode::CMOVGE as u32
|| x == X86Opcode::CMOVLE as u32
|| x == X86Opcode::CMOVG as u32
|| x == X86Opcode::SETO as u32
|| x == X86Opcode::SETNO as u32
|| x == X86Opcode::SETB as u32
|| x == X86Opcode::SETAE as u32
|| x == X86Opcode::SETE as u32
|| x == X86Opcode::SETNE as u32
|| x == X86Opcode::SETBE as u32
|| x == X86Opcode::SETA as u32
|| x == X86Opcode::SETS as u32
|| x == X86Opcode::SETNS as u32
|| x == X86Opcode::SETP as u32
|| x == X86Opcode::SETNP as u32
|| x == X86Opcode::SETL as u32
|| x == X86Opcode::SETGE as u32
|| x == X86Opcode::SETLE as u32
|| x == X86Opcode::SETG as u32
|| x == X86Opcode::ADC as u32
|| x == X86Opcode::SBB as u32
)
}
fn is_frame_setup(opcode: u32) -> bool {
matches!(
opcode,
x if x == X86Opcode::PUSH as u32
|| x == X86Opcode::ENTER as u32
|| x == X86Opcode::PUSHFS as u32
|| x == X86Opcode::PUSHGS as u32
|| x == X86Opcode::FRAME_SETUP as u32
)
}
fn is_frame_destroy(opcode: u32) -> bool {
matches!(
opcode,
x if x == X86Opcode::POP as u32
|| x == X86Opcode::LEAVE as u32
|| x == X86Opcode::POPFS as u32
|| x == X86Opcode::POPGS as u32
|| x == X86Opcode::FRAME_DESTROY as u32
)
}
fn is_bundle_header(opcode: u32) -> bool {
opcode == X86Opcode::BUNDLE as u32
}
fn is_bundle_inside(opcode: u32) -> bool {
opcode == X86Opcode::BUNDLE_INSIDE as u32
}
fn is_landing_pad_instruction(opcode: u32) -> bool {
opcode == X86Opcode::EH_LABEL as u32
|| opcode == X86Opcode::LANDINGPAD as u32
|| opcode == X86Opcode::CATCHPAD as u32
|| opcode == X86Opcode::CLEANUPPAD as u32
}
fn is_patchpoint(opcode: u32) -> bool {
opcode == X86Opcode::PATCHPOINT as u32
|| opcode == X86Opcode::PATCHABLE_OP as u32
|| opcode == X86Opcode::PATCHABLE_FUNCTION_ENTER as u32
|| opcode == X86Opcode::PATCHABLE_RET as u32
|| opcode == X86Opcode::PATCHABLE_TAIL_CALL as u32
}
fn is_stackmap(opcode: u32) -> bool {
opcode == X86Opcode::STACKMAP as u32
}
fn is_debug_value(opcode: u32) -> bool {
opcode == X86Opcode::DBG_VALUE as u32
|| opcode == X86Opcode::DBG_LABEL as u32
|| opcode == X86Opcode::DBG_INSTR_REF as u32
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct RexInfo {
pub present: bool,
pub w: bool,
pub r: bool,
pub x: bool,
pub b: bool,
}
impl RexInfo {
fn from_byte(byte: u8) -> Option<Self> {
if (byte & 0xF0) != 0x40 {
return None;
}
Some(Self {
present: true,
w: (byte & 0x08) != 0,
r: (byte & 0x04) != 0,
x: (byte & 0x02) != 0,
b: (byte & 0x01) != 0,
})
}
fn to_byte(&self) -> u8 {
if !self.present {
return 0;
}
0x40 | (self.w as u8) << 3 | (self.r as u8) << 2 | (self.x as u8) << 1 | (self.b as u8)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct VexInfo {
pub present: bool,
pub is_3byte: bool,
pub r: bool,
pub x: bool,
pub b: bool,
pub mmmm: u8,
pub w: bool,
pub vvvv: u8,
pub l: bool,
pub pp: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct EvexInfo {
pub present: bool,
pub r: bool,
pub x: bool,
pub b: bool,
pub r_prime: bool,
pub mmmm: u8,
pub w: bool,
pub vvvv: u8,
pub lll: u8,
pub pp: u8,
pub z: bool,
pub bcast: bool,
pub rounding: u8,
pub sae: bool,
pub opmask: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct ModRmInfo {
pub mod_: u8,
pub reg: u8,
pub rm: u8,
}
impl ModRmInfo {
fn from_byte(byte: u8) -> Self {
Self {
mod_: (byte >> 6) & 0x03,
reg: (byte >> 3) & 0x07,
rm: byte & 0x07,
}
}
fn to_byte(&self) -> u8 {
(self.mod_ << 6) | ((self.reg & 0x07) << 3) | (self.rm & 0x07)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct SibInfo {
pub scale: u8,
pub index: u8,
pub base: u8,
}
impl SibInfo {
fn from_byte(byte: u8) -> Self {
Self {
scale: (byte >> 6) & 0x03,
index: (byte >> 3) & 0x07,
base: byte & 0x07,
}
}
fn to_byte(&self) -> u8 {
(self.scale << 6) | ((self.index & 0x07) << 3) | (self.base & 0x07)
}
}
fn x8664_callee_saved_regs() -> HashSet<u32> {
let mut regs = HashSet::new();
regs.insert(3);
regs.insert(5);
regs.insert(12);
regs.insert(13);
regs.insert(14);
regs.insert(15);
regs
}
fn x8632_callee_saved_regs() -> HashSet<u32> {
let mut regs = HashSet::new();
regs.insert(3); regs.insert(5); regs.insert(6); regs.insert(7); regs
}
fn x8664_callee_saved_xmms() -> HashSet<u32> {
HashSet::new()
}
fn win64_callee_saved_xmms() -> HashSet<u32> {
let mut regs = HashSet::new();
for i in 6..=15 {
regs.insert(i);
}
regs
}
fn x8664_reserved_regs() -> HashSet<u32> {
let mut regs = HashSet::new();
regs.insert(4);
regs.insert(16);
regs
}
fn get_operand_reg_class(opcode: u32, _operand_idx: usize, is_def: bool) -> Option<RegClass> {
match opcode {
x if x == X86Opcode::MOV as u32
|| x == X86Opcode::ADD as u32
|| x == X86Opcode::SUB as u32
|| x == X86Opcode::AND as u32
|| x == X86Opcode::OR as u32
|| x == X86Opcode::XOR as u32
|| x == X86Opcode::CMP as u32
|| x == X86Opcode::TEST as u32 =>
{
if is_def {
Some(RegClass::GPR64)
} else {
Some(RegClass::GPR64)
}
}
x if x == X86Opcode::ADDPS as u32
|| x == X86Opcode::ADDSS as u32
|| x == X86Opcode::SUBPS as u32
|| x == X86Opcode::SUBSS as u32
|| x == X86Opcode::MULPS as u32
|| x == X86Opcode::MULSS as u32
|| x == X86Opcode::DIVPS as u32
|| x == X86Opcode::DIVSS as u32
|| x == X86Opcode::MOVUPS as u32
|| x == X86Opcode::MOVAPS as u32
|| x == X86Opcode::MOVSS as u32
|| x == X86Opcode::MOVSD as u32 =>
{
Some(RegClass::XMM)
}
x if x == X86Opcode::VADDPS as u32
|| x == X86Opcode::VADDSS as u32
|| x == X86Opcode::VMULPS as u32
|| x == X86Opcode::VMOVUPS as u32
|| x == X86Opcode::VMOVAPS as u32 =>
{
Some(RegClass::YMM)
}
x if x == X86Opcode::VADDPDZ as u32 || x == X86Opcode::VMULPDZ as u32 => {
Some(RegClass::ZMM)
}
_ => None,
}
}
fn expected_operand_count(opcode: u32) -> (usize, usize) {
match opcode {
x if x == X86Opcode::RET as u32
|| x == X86Opcode::RET1 as u32
|| x == X86Opcode::RET2 as u32
|| x == X86Opcode::NOP as u32
|| x == X86Opcode::INT3 as u32
|| x == X86Opcode::UD2 as u32 =>
{
(0, 0)
}
x if x == X86Opcode::PUSH as u32
|| x == X86Opcode::POP as u32
|| x == X86Opcode::INC as u32
|| x == X86Opcode::DEC as u32
|| x == X86Opcode::NEG as u32
|| x == X86Opcode::NOT as u32
|| x == X86Opcode::JMP as u32
|| x == X86Opcode::CALL as u32 =>
{
(1, 1)
}
x if x == X86Opcode::MOV as u32
|| x == X86Opcode::ADD as u32
|| x == X86Opcode::SUB as u32
|| x == X86Opcode::AND as u32
|| x == X86Opcode::OR as u32
|| x == X86Opcode::XOR as u32
|| x == X86Opcode::CMP as u32
|| x == X86Opcode::TEST as u32
|| x == X86Opcode::MOVSX as u32
|| x == X86Opcode::MOVZX as u32
|| x == X86Opcode::LEA as u32
|| x == X86Opcode::XCHG as u32
|| x == X86Opcode::SHL as u32
|| x == X86Opcode::SHR as u32
|| x == X86Opcode::SAR as u32
|| x == X86Opcode::ROL as u32
|| x == X86Opcode::ROR as u32 =>
{
(2, 2)
}
x if x == X86Opcode::IMUL as u32
|| x == X86Opcode::SHLD as u32
|| x == X86Opcode::SHRD as u32 =>
{
(2, 3)
}
x if is_conditional_branch(x) => (1, 1),
x if x == X86Opcode::PHI as u32 => (2, 64),
_ => (0, 8),
}
}
fn supports_rex_prefix(opcode: u32) -> bool {
!is_bundle_header(opcode)
&& !is_bundle_inside(opcode)
&& !matches!(
opcode,
x if x == X86Opcode::DBG_VALUE as u32
|| x == X86Opcode::DBG_LABEL as u32
|| x == X86Opcode::EH_LABEL as u32
|| x == X86Opcode::GC_LABEL as u32
)
}
fn uses_vex_encoding(opcode: u32) -> bool {
matches!(
opcode,
x if x == X86Opcode::VADDPS as u32
|| x == X86Opcode::VADDSS as u32
|| x == X86Opcode::VSUBPS as u32
|| x == X86Opcode::VSUBSS as u32
|| x == X86Opcode::VMULPS as u32
|| x == X86Opcode::VMULSS as u32
|| x == X86Opcode::VDIVPS as u32
|| x == X86Opcode::VDIVSS as u32
|| x == X86Opcode::VMOVUPS as u32
|| x == X86Opcode::VMOVAPS as u32
|| x == X86Opcode::VMOVSS as u32
|| x == X86Opcode::VMOVSD as u32
|| x == X86Opcode::VANDPD as u32
|| x == X86Opcode::VORPD as u32
|| x == X86Opcode::VXORPD as u32
|| x == X86Opcode::VFMADD132PS as u32
|| x == X86Opcode::VFMADD213PS as u32
|| x == X86Opcode::VFMADD231PS as u32
|| x == X86Opcode::VPERMILPS as u32
|| x == X86Opcode::VPERM2F128 as u32
|| x == X86Opcode::VBROADCASTSS as u32
)
}
fn uses_evex_encoding(opcode: u32) -> bool {
matches!(
opcode,
x if x == X86Opcode::VADDPDZ as u32
|| x == X86Opcode::VADDPSZ as u32
|| x == X86Opcode::VMULPDZ as u32
|| x == X86Opcode::VMULPSZ as u32
|| x == X86Opcode::VPERMW as u32
|| x == X86Opcode::VPERMDZ as u32
|| x == X86Opcode::VGATHERDPD as u32
|| x == X86Opcode::VSCATTERDPD as u32
|| x == X86Opcode::VPCOMPRESSD as u32
|| x == X86Opcode::VPEXPANDD as u32
)
}
fn supports_lock_prefix(opcode: u32) -> bool {
matches!(
opcode,
x if x == X86Opcode::ADD as u32
|| x == X86Opcode::ADC as u32
|| x == X86Opcode::SUB as u32
|| x == X86Opcode::SBB as u32
|| x == X86Opcode::AND as u32
|| x == X86Opcode::OR as u32
|| x == X86Opcode::XOR as u32
|| x == X86Opcode::INC as u32
|| x == X86Opcode::DEC as u32
|| x == X86Opcode::NEG as u32
|| x == X86Opcode::NOT as u32
|| x == X86Opcode::XCHG as u32
|| x == X86Opcode::XADD as u32
|| x == X86Opcode::CMPXCHG as u32
|| x == X86Opcode::CMPXCHG8B as u32
|| x == X86Opcode::CMPXCHG16B as u32
|| x == X86Opcode::BTS as u32
|| x == X86Opcode::BTR as u32
|| x == X86Opcode::BTC as u32
)
}
fn supports_rep_prefix(opcode: u32) -> bool {
matches!(
opcode,
x if x == X86Opcode::MOVSB as u32
|| x == X86Opcode::MOVSW as u32
|| x == X86Opcode::MOVSD as u32
|| x == X86Opcode::MOVSQ as u32
|| x == X86Opcode::STOSB as u32
|| x == X86Opcode::STOSW as u32
|| x == X86Opcode::STOSD as u32
|| x == X86Opcode::STOSQ as u32
|| x == X86Opcode::LODSB as u32
|| x == X86Opcode::LODSW as u32
|| x == X86Opcode::LODSD as u32
|| x == X86Opcode::LODSQ as u32
|| x == X86Opcode::SCASB as u32
|| x == X86Opcode::SCASW as u32
|| x == X86Opcode::SCASD as u32
|| x == X86Opcode::SCASQ as u32
|| x == X86Opcode::CMPSB as u32
|| x == X86Opcode::CMPSW as u32
|| x == X86Opcode::CMPSD as u32
|| x == X86Opcode::CMPSQ as u32
|| x == X86Opcode::INSB as u32
|| x == X86Opcode::INSW as u32
|| x == X86Opcode::INSD as u32
|| x == X86Opcode::OUTSB as u32
|| x == X86Opcode::OUTSW as u32
|| x == X86Opcode::OUTSD as u32
)
}
fn requires_rex_w(opcode: u32) -> bool {
matches!(
opcode,
x if x == X86Opcode::MOVABS as u32
|| x == X86Opcode::MOVSQ as u32
|| x == X86Opcode::STOSQ as u32
|| x == X86Opcode::LODSQ as u32
|| x == X86Opcode::SCASQ as u32
|| x == X86Opcode::CMPSQ as u32
|| x == X86Opcode::CMPXCHG16B as u32
)
}
fn has_implicit_modrm(opcode: u32) -> bool {
!is_debug_value(opcode)
&& !matches!(
opcode,
x if x == X86Opcode::NOP as u32
|| x == X86Opcode::RET as u32
|| x == X86Opcode::RET1 as u32
|| x == X86Opcode::RET2 as u32
|| x == X86Opcode::JMP as u32 || x == X86Opcode::JMP1 as u32
|| x == X86Opcode::JMP2 as u32
|| x == X86Opcode::JMP4 as u32
|| x == X86Opcode::CALL as u32 || x == X86Opcode::INT3 as u32
|| x == X86Opcode::UD2 as u32
|| x == X86Opcode::PUSHF as u32
|| x == X86Opcode::POPF as u32
)
}
fn needs_sib_byte(modrm: &ModRmInfo) -> bool {
modrm.mod_ != 0b11 && modrm.rm == 0b100
}
fn segment_override_map() -> HashMap<u8, u32> {
let mut map = HashMap::new();
map.insert(0x26, 0); map.insert(0x2E, 1); map.insert(0x36, 2); map.insert(0x3E, 3); map.insert(0x64, 4); map.insert(0x65, 5); map
}
fn verify_cfg_integrity(blocks: &[MachineBasicBlock], result: &mut X86VerificationResult) {
let pred_map = build_predecessor_map(blocks);
let name_to_idx: HashMap<&str, usize> = blocks
.iter()
.enumerate()
.map(|(i, b)| (b.name.as_str(), i))
.collect();
for (bi, block) in blocks.iter().enumerate() {
for succ in &block.successors {
if *succ >= blocks.len() {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::CfgSuccessorNotFound,
VerifierSeverity::Error,
format!("successor '{}' not found in function", succ),
MachineSourceLocation::block_only(bi, &block.name),
));
}
}
}
for (bi, block) in blocks.iter().enumerate() {
let expected_preds = pred_map.get(&block.name).cloned().unwrap_or_default();
for pred_name in &expected_preds {
if !name_to_idx.contains_key(pred_name.as_str()) {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::CfgPredecessorInconsistent,
VerifierSeverity::Error,
format!(
"predecessor '{}' of block '{}' not found",
pred_name, block.name
),
MachineSourceLocation::block_only(bi, &block.name),
));
}
}
}
if !blocks.is_empty() {
for (bi, block) in blocks.iter().enumerate() {
if bi > 0 {
let preds = pred_map.get(&block.name).map(|v| v.len()).unwrap_or(0);
if preds == 0 {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::CfgUnreachableBlock,
VerifierSeverity::Warning,
format!("block '{}' has no predecessors (unreachable)", block.name),
MachineSourceLocation::block_only(bi, &block.name),
));
}
}
}
}
}
fn verify_rex_prefix(
rex: &RexInfo,
opcode: u32,
_reg_operands: &[u32],
_rm_operand: Option<u32>,
is_64bit_mode: bool,
loc: &MachineSourceLocation,
result: &mut X86VerificationResult,
) {
if !rex.present {
if is_64bit_mode && requires_rex_w(opcode) && !rex.w {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::RexPrefixMissing,
VerifierSeverity::Error,
format!(
"instruction {:?} requires REX.W prefix in 64-bit mode",
opcode
),
loc.clone(),
));
}
return;
}
if !is_64bit_mode {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::RexPrefixInvalid,
VerifierSeverity::Error,
"REX prefix not valid in 32-bit mode".to_string(),
loc.clone(),
));
return;
}
if uses_vex_encoding(opcode) || uses_evex_encoding(opcode) {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::RexPrefixInvalid,
VerifierSeverity::Error,
"REX prefix incompatible with VEX/EVEX encoding".to_string(),
loc.clone(),
));
}
if rex.w && !requires_rex_w(opcode) {
}
if rex.x {
}
}
fn verify_vex_prefix(
vex: &VexInfo,
opcode: u32,
loc: &MachineSourceLocation,
result: &mut X86VerificationResult,
) {
if !vex.present {
return;
}
if !uses_vex_encoding(opcode) {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::VexPrefixInvalid,
VerifierSeverity::Error,
format!("VEX prefix not valid for opcode {:?}", opcode),
loc.clone(),
));
return;
}
match vex.mmmm {
0b00001 | 0b00010 | 0b00011 => {} _ => {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::VexMmmInvalid,
VerifierSeverity::Error,
format!(
"VEX.mmmm = 0x{:X} is invalid for opcode {:?}",
vex.mmmm, opcode
),
loc.clone(),
));
}
}
if vex.l {
}
match vex.pp {
0b00 | 0b01 | 0b10 | 0b11 => {} _ => {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::VexPPInvalid,
VerifierSeverity::Error,
format!("VEX.pp = 0b{:02b} is invalid", vex.pp),
loc.clone(),
));
}
}
if vex.w {
}
if vex.vvvv == 0b1111 && !is_unconditional_branch(opcode) && !is_conditional_branch(opcode) {
}
}
fn verify_evex_prefix(
evex: &EvexInfo,
opcode: u32,
loc: &MachineSourceLocation,
result: &mut X86VerificationResult,
) {
if !evex.present {
return;
}
if !uses_evex_encoding(opcode) {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::EvexPrefixInvalid,
VerifierSeverity::Error,
format!("EVEX prefix not valid for opcode {:?}", opcode),
loc.clone(),
));
return;
}
match evex.mmmm {
0b00001 | 0b00010 | 0b00011 => {} _ => {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::EvexEncodingViolation,
VerifierSeverity::Error,
format!("EVEX.mmmm = 0x{:X} is invalid", evex.mmmm),
loc.clone(),
));
}
}
if evex.lll > 2 {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::EvexEncodingViolation,
VerifierSeverity::Error,
format!("EVEX.LLL = 0b{:03b} is invalid (must be 0-2)", evex.lll),
loc.clone(),
));
}
if evex.bcast {
}
if evex.sae && evex.lll != 2 {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::EvexSaeInvalid,
VerifierSeverity::Error,
"EVEX SAE requires 512-bit vector length (LLL=2)".to_string(),
loc.clone(),
));
}
if evex.rounding != 0 {
match evex.rounding {
0b01 => {} 0b10 => {} 0b11 => {} 0b00 => {} _ => {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::EvexRoundingInvalid,
VerifierSeverity::Error,
format!("EVEX rounding = 0b{:02b} is invalid", evex.rounding),
loc.clone(),
));
}
}
}
if evex.opmask > 7 {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::EvexOpmaskRequired,
VerifierSeverity::Error,
format!(
"EVEX opmask register {} is invalid (must be 0-7)",
evex.opmask
),
loc.clone(),
));
}
}
fn verify_modrm(
modrm: &ModRmInfo,
opcode: u32,
_has_sib: bool,
loc: &MachineSourceLocation,
result: &mut X86VerificationResult,
) {
if modrm.mod_ > 0b11 {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::ModRmModInvalid,
VerifierSeverity::Error,
format!("ModR/M.mod = 0b{:02b} is invalid", modrm.mod_),
loc.clone(),
));
}
if modrm.rm > 0b111 {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::ModRmRmInvalid,
VerifierSeverity::Error,
format!("ModR/M.r/m = 0b{:03b} is invalid", modrm.rm),
loc.clone(),
));
}
if modrm.mod_ == 0b00 && modrm.rm == 0b101 {
}
if modrm.mod_ == 0b11 {
}
if !has_implicit_modrm(opcode) && modrm.mod_ != 0 && modrm.reg != 0 && modrm.rm != 0 {
}
}
fn verify_sib(
sib: &SibInfo,
modrm: &ModRmInfo,
_rex: &RexInfo,
loc: &MachineSourceLocation,
result: &mut X86VerificationResult,
) {
let needs_sib = modrm.mod_ != 0b11 && modrm.rm == 0b100;
if !needs_sib {
return;
}
if sib.scale > 0b11 {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::SibScaleInvalid,
VerifierSeverity::Error,
format!("SIB.scale = 0b{:02b} is invalid", sib.scale),
loc.clone(),
));
}
if sib.index == 0b100 {
}
if sib.base == 0b101 && modrm.mod_ == 0b00 {
}
}
fn verify_lock_prefix(
has_lock: bool,
opcode: u32,
loc: &MachineSourceLocation,
result: &mut X86VerificationResult,
) {
if !has_lock {
return;
}
if !supports_lock_prefix(opcode) {
result.add_error(
X86VerificationError::new(
X86VerificationErrorKind::LockPrefixInvalidTarget,
VerifierSeverity::Error,
format!("LOCK prefix not valid for opcode {:?}", opcode),
loc.clone(),
)
.with_context("LOCK prefix is only valid on memory read-modify-write instructions"),
);
}
}
fn verify_rep_prefix(
has_rep: bool,
has_repe: bool,
has_repne: bool,
opcode: u32,
loc: &MachineSourceLocation,
result: &mut X86VerificationResult,
) {
if !has_rep && !has_repe && !has_repne {
return;
}
if !supports_rep_prefix(opcode) {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::RepPrefixInvalidTarget,
VerifierSeverity::Error,
format!("REP prefix not valid for opcode {:?}", opcode),
loc.clone(),
).with_context("REP prefix is only valid on string instructions (MOVS, STOS, LODS, SCAS, CMPS, INS, OUTS)"));
}
if (has_repe || has_repne)
&& !matches!(
opcode,
x if x == X86Opcode::CMPSB as u32
|| x == X86Opcode::CMPSW as u32
|| x == X86Opcode::CMPSD as u32
|| x == X86Opcode::CMPSQ as u32
|| x == X86Opcode::SCASB as u32
|| x == X86Opcode::SCASW as u32
|| x == X86Opcode::SCASD as u32
|| x == X86Opcode::SCASQ as u32
)
{
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::RepPrefixInconsistent,
VerifierSeverity::Error,
"REPE/REPNE is only valid for CMPS and SCAS instructions".to_string(),
loc.clone(),
));
}
}
fn verify_segment_override(
segment: Option<u8>,
opcode: u32,
loc: &MachineSourceLocation,
result: &mut X86VerificationResult,
) {
if segment.is_none() {
return;
}
let seg_byte = segment.unwrap();
let seg_map = segment_override_map();
if !seg_map.contains_key(&seg_byte) {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::SegmentOverrideInvalid,
VerifierSeverity::Error,
format!("invalid segment override prefix 0x{:02X}", seg_byte),
loc.clone(),
));
return;
}
if is_conditional_branch(opcode)
|| is_unconditional_branch(opcode)
|| is_call(opcode)
|| is_return(opcode)
|| is_debug_value(opcode)
{
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::SegmentOverrideInvalid,
VerifierSeverity::Warning,
format!(
"segment override 0x{:02X} not meaningful for control flow",
seg_byte
),
loc.clone(),
));
}
}
fn verify_address_size(
_address_size: u8,
_default_address_size: u8,
_has_address_override: bool,
_opcode: u32,
_loc: &MachineSourceLocation,
_result: &mut X86VerificationResult,
) {
}
fn verify_operand_size(
_operand_size: u8,
_default_operand_size: u8,
_has_operand_override: bool,
_opcode: u32,
_loc: &MachineSourceLocation,
_result: &mut X86VerificationResult,
) {
}
pub struct X86MachineVerifier {
pub phase: VerificationPhase,
pub strictness: VerifierStrictness,
pub is_64bit: bool,
pub is_windows: bool,
pub instr_info: Option<X86InstrInfo>,
liveness: RegisterLiveness,
stack_slots: Vec<StackSlotInfo>,
blocks_verified: usize,
instrs_verified: usize,
error_count: usize,
frame_register: Option<u32>,
seen_frame_setup: bool,
seen_frame_destroy: bool,
entry_verified: bool,
}
impl X86MachineVerifier {
pub fn new_pre_ra(is_64bit: bool, strictness: VerifierStrictness) -> Self {
Self {
phase: VerificationPhase::PreRA,
strictness,
is_64bit,
is_windows: false,
instr_info: None,
liveness: RegisterLiveness::new(),
stack_slots: Vec::new(),
blocks_verified: 0,
instrs_verified: 0,
error_count: 0,
frame_register: None,
seen_frame_setup: false,
seen_frame_destroy: false,
entry_verified: false,
}
}
pub fn new_post_ra(is_64bit: bool, strictness: VerifierStrictness) -> Self {
Self {
phase: VerificationPhase::PostRA,
strictness,
is_64bit,
is_windows: false,
instr_info: None,
liveness: RegisterLiveness::new(),
stack_slots: Vec::new(),
blocks_verified: 0,
instrs_verified: 0,
error_count: 0,
frame_register: None,
seen_frame_setup: false,
seen_frame_destroy: false,
entry_verified: false,
}
}
pub fn with_instr_info(mut self, info: X86InstrInfo) -> Self {
self.instr_info = Some(info);
self
}
pub fn with_windows_abi(mut self) -> Self {
self.is_windows = true;
self
}
pub fn verify(&mut self, mf: &MachineFunction) -> X86VerificationResult {
let mut result = X86VerificationResult::new(&mf.name);
self.error_count = 0;
self.entry_verified = false;
self.liveness.reset();
self.stack_slots.clear();
self.seen_frame_setup = false;
self.seen_frame_destroy = false;
self.frame_register = None;
self.instrs_verified = 0;
verify_cfg_integrity(&mf.blocks, &mut result);
if self.should_abort(&result) {
return result;
}
let num_blocks = mf.blocks.len();
for bi in 0..num_blocks {
let block = &mf.blocks[bi];
self.verify_block(bi, block, mf, &mut result);
if self.should_abort(&result) {
break;
}
}
self.verify_machine_function(mf, &mut result);
if self.phase == VerificationPhase::PostRA {
self.verify_post_ra(mf, &mut result);
}
if self.phase == VerificationPhase::PreRA {
self.verify_ssa_global(&mut result);
}
result.blocks_verified = self.blocks_verified;
result.instructions_verified = self.instrs_verified;
result
}
fn verify_block(
&mut self,
bi: usize,
block: &MachineBasicBlock,
mf: &MachineFunction,
result: &mut X86VerificationResult,
) {
self.blocks_verified += 1;
let block_name = &block.name;
if bi == 0 && !self.entry_verified {
self.verify_entry_block(block, result);
self.entry_verified = true;
}
if self.strictness.verify_landing_pads {
self.verify_landing_pad(bi, block, result);
}
let num_instrs = block.instructions.len();
if num_instrs == 0 {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::MissingTerminator,
VerifierSeverity::Warning,
"block has no instructions (no terminator)".to_string(),
MachineSourceLocation::block_only(bi, block_name),
));
return;
}
if self.strictness.verify_bundles {
self.verify_bundles_in_block(bi, block, result);
if self.should_abort(result) {
return;
}
}
let mut seen_non_phi = false;
let mut block_pred_names: Vec<String> = Vec::new();
if self.phase == VerificationPhase::PreRA {
let pred_map = build_predecessor_map(&mf.blocks);
block_pred_names = pred_map.get(block_name).cloned().unwrap_or_default();
}
for (ii, instr) in block.instructions.iter().enumerate() {
self.instrs_verified += 1;
let loc = MachineSourceLocation::new(bi, block_name, ii);
let opcode = instr.opcode;
if is_debug_value(opcode) {
continue;
}
if is_phi(opcode) {
if seen_non_phi {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::PhiNotAtBlockStart,
VerifierSeverity::Error,
"PHI instruction not at start of block".to_string(),
loc.clone(),
));
continue;
}
self.verify_phi_instr(instr, &block_pred_names, &loc, result);
continue;
} else {
seen_non_phi = true;
}
if is_terminator(opcode) {
if ii < num_instrs - 1 {
let has_non_debug_after = block.instructions[ii + 1..]
.iter()
.any(|mi| !is_debug_value(mi.opcode));
if has_non_debug_after {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::InstructionsAfterTerminator,
VerifierSeverity::Error,
"non-debug instruction after terminator".to_string(),
loc.clone(),
));
}
}
if ii < num_instrs - 1 {
let next_terminator = block.instructions[ii + 1..]
.iter()
.filter(|mi| !is_debug_value(mi.opcode))
.find(|mi| is_terminator(mi.opcode));
if next_terminator.is_some() {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::MultipleTerminators,
VerifierSeverity::Error,
"multiple terminator instructions in block".to_string(),
loc.clone(),
));
}
}
}
self.verify_instruction_operands(instr, &loc, result);
if self.phase == VerificationPhase::PreRA {
self.verify_register_classes(instr, &loc, result);
}
if self.phase == VerificationPhase::PreRA {
self.track_ssa_registers(instr, &loc, result);
}
if self.phase == VerificationPhase::PostRA {
self.track_physical_registers(instr, &loc, result);
self.verify_physical_register_usage(instr, &loc, result);
}
if self.strictness.verify_encoding {
self.verify_x86_encoding(instr, &loc, result);
}
if self.strictness.verify_patchpoints {
if is_patchpoint(opcode) {
self.verify_patchpoint(instr, &loc, result);
}
if is_stackmap(opcode) {
self.verify_stackmap(instr, &loc, result);
}
}
if self.should_abort(result) {
return;
}
}
let last_instr = block.instructions.last();
if let Some(last) = last_instr {
if !is_terminator(last.opcode) && !is_debug_value(last.opcode) {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::MissingTerminator,
VerifierSeverity::Error,
format!(
"block does not end with a terminator (last opcode: {:?})",
last.opcode
),
MachineSourceLocation::new(bi, block_name, block.instructions.len() - 1),
));
}
}
}
fn verify_entry_block(&self, block: &MachineBasicBlock, result: &mut X86VerificationResult) {
if block.instructions.is_empty() {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::MissingTerminator,
VerifierSeverity::Error,
"entry block is empty".to_string(),
MachineSourceLocation::block_only(0, &block.name),
));
return;
}
for instr in &block.instructions {
if is_phi(instr.opcode) {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::PhiCriticalEdge,
VerifierSeverity::Warning,
"entry block contains PHI instructions (it has no predecessors)".to_string(),
MachineSourceLocation::new(0, &block.name, 0),
));
break;
}
}
}
fn verify_landing_pad(
&self,
bi: usize,
block: &MachineBasicBlock,
result: &mut X86VerificationResult,
) {
let is_landing_pad = block
.instructions
.first()
.map_or(false, |mi| is_landing_pad_instruction(mi.opcode));
if !is_landing_pad {
return;
}
let first = &block.instructions[0];
if first.opcode != X86Opcode::EH_LABEL as u32
&& first.opcode != X86Opcode::LANDINGPAD as u32
{
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::LandingPadMissingEhLabel,
VerifierSeverity::Error,
"landing pad must start with EH_LABEL or LANDINGPAD".to_string(),
MachineSourceLocation::new(bi, &block.name, 0),
));
}
}
fn verify_bundles_in_block(
&self,
bi: usize,
block: &MachineBasicBlock,
result: &mut X86VerificationResult,
) {
let mut in_bundle = false;
for (ii, instr) in block.instructions.iter().enumerate() {
let opcode = instr.opcode;
let loc = MachineSourceLocation::new(bi, &block.name, ii);
if is_bundle_header(opcode) {
if in_bundle {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::BundleHeaderNotFirst,
VerifierSeverity::Error,
"nested bundle header (already in a bundle)".to_string(),
loc,
));
}
in_bundle = true;
} else if is_bundle_inside(opcode) {
if !in_bundle {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::BundleInteriorNotInBundle,
VerifierSeverity::Error,
"bundle interior instruction outside a bundle".to_string(),
loc,
));
}
} else {
if in_bundle {
in_bundle = false;
}
}
}
if in_bundle {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::BundleAlignmentViolation,
VerifierSeverity::Warning,
"unclosed bundle at end of block".to_string(),
MachineSourceLocation::block_only(bi, &block.name),
));
}
}
fn verify_phi_instr(
&self,
instr: &MachineInstr,
pred_names: &[String],
loc: &MachineSourceLocation,
result: &mut X86VerificationResult,
) {
let num_ops = instr.operands.len();
let expected_ops = pred_names.len() * 2;
if num_ops != expected_ops {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::PhiWrongNumOperands,
VerifierSeverity::Error,
format!(
"PHI has {} operands but {} predecessors (expected {} operands)",
num_ops,
pred_names.len(),
expected_ops
),
loc.clone(),
));
}
for pred in pred_names {
let mut found = false;
for op_idx in (1..instr.operands.len()).step_by(2) {
if let Some(label) = instr.operands.get(op_idx) {
if matches!(label, MachineOperand::Label(s) if s == pred) {
found = true;
break;
}
}
}
if !found {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::PhiMissingPredecessor,
VerifierSeverity::Error,
format!("PHI missing edge from predecessor '{}'", pred),
loc.clone(),
));
}
}
}
fn verify_instruction_operands(
&self,
instr: &MachineInstr,
loc: &MachineSourceLocation,
result: &mut X86VerificationResult,
) {
let (min_ops, max_ops) = expected_operand_count(instr.opcode);
let actual = instr.operands.len();
if actual < min_ops {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::WrongNumOperands,
VerifierSeverity::Error,
format!(
"too few operands: minimum {} but got {} (opcode {:?})",
min_ops, actual, instr.opcode
),
loc.clone(),
));
}
if actual > max_ops {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::WrongNumOperands,
VerifierSeverity::Error,
format!(
"too many operands: maximum {} but got {} (opcode {:?})",
max_ops, actual, instr.opcode
),
loc.clone(),
));
}
for (oi, op) in instr.operands.iter().enumerate() {
match op {
MachineOperand::Reg(vr) => {
if self.phase == VerificationPhase::PostRA {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::PostRaVirtRegRemaining,
VerifierSeverity::Fatal,
format!("virtual register %{} found post-RA", vr),
loc.clone().with_operand(oi),
));
}
}
MachineOperand::PhysReg(pr) => {
let reserved = x8664_reserved_regs();
if reserved.contains(pr) && *pr != 4
{
}
}
MachineOperand::Imm(_v) => {
}
MachineOperand::Label(_s) | MachineOperand::Global(_s) => {
}
}
}
}
fn verify_register_classes(
&self,
instr: &MachineInstr,
loc: &MachineSourceLocation,
result: &mut X86VerificationResult,
) {
for (oi, op) in instr.operands.iter().enumerate() {
let is_def = instr
.def
.map_or(false, |d| matches!(op, MachineOperand::Reg(r) if *r == d));
let expected_class = get_operand_reg_class(instr.opcode, oi, is_def);
if expected_class.is_none() {
continue;
}
if self.phase == VerificationPhase::PostRA {
if let MachineOperand::PhysReg(pr) = op {
let actual_class = self.phys_reg_to_class(*pr);
if let Some(ref actual) = actual_class {
if Some(*actual) != expected_class {
let compatible =
self.are_classes_compatible(expected_class.unwrap(), *actual);
if !compatible {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::RegClassMismatch,
VerifierSeverity::Error,
format!(
"physical register {:?} class {:?} does not match expected {:?}",
pr, actual, expected_class.unwrap()
),
loc.clone().with_operand(oi),
));
}
}
}
}
}
}
}
fn phys_reg_to_class(&self, _reg: u32) -> Option<RegClass> {
None }
fn are_classes_compatible(&self, expected: RegClass, actual: RegClass) -> bool {
match (expected, actual) {
(RegClass::GPR64, RegClass::GPR64) => true,
(RegClass::GPR64, RegClass::GPR32) => true, (RegClass::GPR64, RegClass::GPR16) => true,
(RegClass::GPR64, RegClass::GPR8) => true,
(RegClass::GPR32, RegClass::GPR64) => true,
(RegClass::GPR32, RegClass::GPR32) => true,
(RegClass::GPR32, RegClass::GPR16) => true,
(RegClass::GPR32, RegClass::GPR8) => true,
(RegClass::XMM, RegClass::XMM) => true,
(RegClass::XMM, RegClass::YMM) => true, (RegClass::YMM, RegClass::XMM) => true,
(RegClass::YMM, RegClass::YMM) => true,
(RegClass::YMM, RegClass::ZMM) => true, (RegClass::ZMM, RegClass::YMM) => true,
(RegClass::ZMM, RegClass::ZMM) => true,
_ => false,
}
}
fn track_ssa_registers(
&mut self,
instr: &MachineInstr,
loc: &MachineSourceLocation,
result: &mut X86VerificationResult,
) {
if let Some(def_reg) = instr.def {
if self.liveness.virt_reg_defs.contains_key(&def_reg) {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::SsaMultipleDef,
VerifierSeverity::Error,
format!(
"virtual register %{} defined multiple times (previous at {})",
def_reg,
self.liveness.virt_reg_defs.get(&def_reg).unwrap()
),
loc.clone(),
));
} else {
self.liveness.record_virt_def(def_reg, loc.clone());
}
}
for (oi, op) in instr.operands.iter().enumerate() {
if let MachineOperand::Reg(vr) = op {
if !self.liveness.virt_reg_defs.contains_key(vr) {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::SsaUseBeforeDef,
VerifierSeverity::Error,
format!(
"virtual register %{} used before being defined in this block",
vr
),
loc.clone().with_operand(oi),
));
}
self.liveness
.record_virt_use(*vr, loc.clone().with_operand(oi));
}
}
}
fn track_physical_registers(
&mut self,
instr: &MachineInstr,
loc: &MachineSourceLocation,
_result: &mut X86VerificationResult,
) {
if instr.def.is_some() {
for op in &instr.operands {
if let MachineOperand::PhysReg(pr) = op {
self.liveness.record_phys_def(*pr, loc.clone());
break; }
}
}
}
fn verify_physical_register_usage(
&self,
instr: &MachineInstr,
loc: &MachineSourceLocation,
result: &mut X86VerificationResult,
) {
let opcode = instr.opcode;
let callee_saved = if self.is_windows {
let mut regs = x8664_callee_saved_regs();
regs.insert(6); regs.insert(7); regs
} else {
x8664_callee_saved_regs()
};
for op in &instr.operands {
if let MachineOperand::PhysReg(pr) = op {
let is_def = instr.def.is_some()
&& instr.operands.first().map_or(
false,
|o| matches!(o, MachineOperand::PhysReg(p) if *p == *pr),
);
if is_def && callee_saved.contains(pr) {
}
}
}
let reserved = x8664_reserved_regs();
for op in &instr.operands {
if let MachineOperand::PhysReg(pr) = op {
if reserved.contains(pr) {
if *pr == 4 && is_frame_setup(opcode) || is_frame_destroy(opcode) {
continue;
}
if *pr == 4
&& matches!(
opcode,
x if x == X86Opcode::PUSH as u32
|| x == X86Opcode::POP as u32
|| x == X86Opcode::CALL as u32
|| x == X86Opcode::RET as u32
)
{
continue;
}
if *pr != 4 {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::PhysRegReservedViolation,
VerifierSeverity::Warning,
format!(
"reserved physical register {} used by instruction {:?}",
pr, opcode
),
loc.clone(),
));
}
}
}
}
if is_call(opcode) {
}
if is_return(opcode) {
}
}
fn verify_x86_encoding(
&self,
instr: &MachineInstr,
loc: &MachineSourceLocation,
result: &mut X86VerificationResult,
) {
let opcode = instr.opcode;
if self.strictness.verify_lock_rep {
let has_lock = matches!(
opcode,
x if x == X86Opcode::LOCK_ADD as u32
|| x == X86Opcode::LOCK_SUB as u32
|| x == X86Opcode::LOCK_AND as u32
|| x == X86Opcode::LOCK_OR as u32
|| x == X86Opcode::LOCK_XOR as u32
|| x == X86Opcode::LOCK_INC as u32
|| x == X86Opcode::LOCK_DEC as u32
|| x == X86Opcode::LOCK_XCHG as u32
);
if has_lock {
verify_lock_prefix(true, opcode, loc, result);
}
let has_rep = matches!(
opcode,
x if x == X86Opcode::REP_MOVSB as u32
|| x == X86Opcode::REP_MOVSW as u32
|| x == X86Opcode::REP_MOVSD as u32
|| x == X86Opcode::REP_STOSB as u32
|| x == X86Opcode::REP_STOSW as u32
|| x == X86Opcode::REP_STOSD as u32
);
let has_repe = matches!(
opcode,
x if x == X86Opcode::REPE_CMPSB as u32
|| x == X86Opcode::REPE_SCASB as u32
);
let has_repne = matches!(
opcode,
x if x == X86Opcode::REPNE_CMPSB as u32
|| x == X86Opcode::REPNE_SCASB as u32
);
if has_rep || has_repe || has_repne {
verify_rep_prefix(has_rep, has_repe, has_repne, opcode, loc, result);
}
}
if self.strictness.verify_prefixes && uses_vex_encoding(opcode) {
let vex = VexInfo {
present: true,
is_3byte: true,
r: false, x: false,
b: false,
mmmm: 0b00001,
w: false,
vvvv: 0b1111,
l: false,
pp: 0b00,
};
verify_vex_prefix(&vex, opcode, loc, result);
}
if self.strictness.verify_prefixes && uses_evex_encoding(opcode) {
let evex = EvexInfo {
present: true,
r: false,
x: false,
b: false,
r_prime: false,
mmmm: 0b00001,
w: false,
vvvv: 0b1111,
lll: 2,
pp: 0b00,
z: false,
bcast: false,
rounding: 0,
sae: false,
opmask: 0,
};
verify_evex_prefix(&evex, opcode, loc, result);
}
if self.strictness.verify_prefixes
&& self.is_64bit
&& supports_rex_prefix(opcode)
&& !uses_vex_encoding(opcode)
&& !uses_evex_encoding(opcode)
{
let needs_rex = instr.operands.iter().any(|op| match op {
MachineOperand::PhysReg(r) => *r >= 8 && *r <= 15,
MachineOperand::Reg(vr) => *vr >= 8, _ => false,
});
if needs_rex || requires_rex_w(opcode) {
let rex = RexInfo {
present: true,
w: requires_rex_w(opcode) || self.is_64bit,
r: instr.operands.iter().any(|op| match op {
MachineOperand::PhysReg(r) => *r >= 8,
MachineOperand::Reg(vr) => *vr >= 8,
_ => false,
}),
x: false,
b: false,
};
verify_rex_prefix(
&rex,
opcode,
&[], None, self.is_64bit,
loc,
result,
);
}
}
}
fn verify_patchpoint(
&self,
instr: &MachineInstr,
loc: &MachineSourceLocation,
result: &mut X86VerificationResult,
) {
let num_ops = instr.operands.len();
if num_ops < 3 {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::PatchPointOperandCount,
VerifierSeverity::Error,
format!("patchpoint requires at least 3 operands, got {}", num_ops),
loc.clone(),
));
}
if let Some(op) = instr.operands.first() {
if !matches!(op, MachineOperand::Imm(_)) {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::PatchPointOperandCount,
VerifierSeverity::Error,
"patchpoint first operand must be an immediate ID".to_string(),
loc.clone().with_operand(0),
));
}
}
}
fn verify_stackmap(
&self,
instr: &MachineInstr,
loc: &MachineSourceLocation,
result: &mut X86VerificationResult,
) {
if instr.operands.len() < 2 {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::StackMapInvalidOperands,
VerifierSeverity::Error,
"stackmap requires at least 2 operands".to_string(),
loc.clone(),
));
}
}
fn verify_machine_function(&self, mf: &MachineFunction, result: &mut X86VerificationResult) {
if mf.blocks.is_empty() {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::InternalError,
VerifierSeverity::Error,
"machine function has no blocks".to_string(),
MachineSourceLocation {
block_idx: 0,
block_name: "(none)".to_string(),
instr_idx: 0,
operand_idx: None,
},
));
return;
}
let has_return = mf
.blocks
.iter()
.any(|b| b.instructions.iter().any(|mi| is_return(mi.opcode)));
if !has_return && !mf.name.starts_with("__") {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::NoReturnInstruction,
VerifierSeverity::Warning,
"function has no return instruction".to_string(),
MachineSourceLocation::block_only(0, &mf.blocks[0].name),
));
}
if self.strictness.verify_frame {
self.verify_frame_consistency(mf, result);
}
}
fn verify_frame_consistency(&self, mf: &MachineFunction, result: &mut X86VerificationResult) {
let mut has_frame_setup = false;
let mut has_frame_destroy = false;
let mut frame_reg: Option<u32> = None;
for (bi, block) in mf.blocks.iter().enumerate() {
for (ii, instr) in block.instructions.iter().enumerate() {
if is_frame_setup(instr.opcode) {
has_frame_setup = true;
for op in &instr.operands {
if let MachineOperand::PhysReg(pr) = op {
frame_reg = Some(*pr);
}
}
}
if is_frame_destroy(instr.opcode) {
has_frame_destroy = true;
let loc = MachineSourceLocation::new(bi, &block.name, ii);
}
}
}
if has_frame_setup && !has_frame_destroy {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::FrameSetupEpilogueMismatch,
VerifierSeverity::Warning,
"frame setup without corresponding frame destroy".to_string(),
MachineSourceLocation::block_only(0, &mf.blocks[0].name),
));
}
}
fn verify_post_ra(&self, mf: &MachineFunction, result: &mut X86VerificationResult) {
for (bi, block) in mf.blocks.iter().enumerate() {
for (ii, instr) in block.instructions.iter().enumerate() {
if is_debug_value(instr.opcode) {
continue;
}
for (oi, op) in instr.operands.iter().enumerate() {
if let MachineOperand::Reg(vr) = op {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::PostRaVirtRegRemaining,
VerifierSeverity::Fatal,
format!("virtual register %{} found post-RA", vr),
MachineSourceLocation::new(bi, &block.name, ii).with_operand(oi),
));
}
}
}
}
if self.strictness.verify_calling_conv {
self.verify_calling_convention(mf, result);
}
self.verify_stack_slots(result);
}
fn verify_calling_convention(&self, mf: &MachineFunction, result: &mut X86VerificationResult) {
let callee_saved = if self.is_windows {
let mut regs = x8664_callee_saved_regs();
regs.insert(6);
regs.insert(7);
regs
} else {
x8664_callee_saved_regs()
};
let mut modified_callee_saved: HashMap<u32, Vec<MachineSourceLocation>> = HashMap::new();
let mut saved_callee_saved: HashSet<u32> = HashSet::new();
let mut restored_callee_saved: HashSet<u32> = HashSet::new();
for (bi, block) in mf.blocks.iter().enumerate() {
for (ii, instr) in block.instructions.iter().enumerate() {
let loc = MachineSourceLocation::new(bi, &block.name, ii);
if instr.opcode == X86Opcode::PUSH as u32 {
for op in &instr.operands {
if let MachineOperand::PhysReg(pr) = op {
if callee_saved.contains(pr) {
saved_callee_saved.insert(*pr);
}
}
}
}
if instr.opcode == X86Opcode::POP as u32 {
for op in &instr.operands {
if let MachineOperand::PhysReg(pr) = op {
if callee_saved.contains(pr) {
restored_callee_saved.insert(*pr);
}
}
}
}
if instr.def.is_some() {
for op in &instr.operands {
if let MachineOperand::PhysReg(pr) = op {
if callee_saved.contains(pr) && instr.opcode != X86Opcode::POP as u32 {
modified_callee_saved
.entry(*pr)
.or_default()
.push(loc.clone());
}
}
}
}
}
}
for (reg, locs) in &modified_callee_saved {
if !saved_callee_saved.contains(reg) || !restored_callee_saved.contains(reg) {
for loc in locs {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::CalleeSavedClobbered,
VerifierSeverity::Error,
format!(
"callee-saved register {} modified without save/restore",
reg
),
loc.clone(),
));
}
}
}
let rax_reg: u32 = 0; let mut rax_defined = false;
for block in &mf.blocks {
for instr in &block.instructions {
if instr.def.is_some() {
for op in &instr.operands {
if let MachineOperand::PhysReg(pr) = op {
if *pr == rax_reg {
rax_defined = true;
}
}
}
}
if is_return(instr.opcode) && !rax_defined {
}
}
}
}
fn verify_stack_slots(&self, result: &mut X86VerificationResult) {
for i in 0..self.stack_slots.len() {
for j in (i + 1)..self.stack_slots.len() {
if self.stack_slots[i].overlaps_with(&self.stack_slots[j]) {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::StackSlotOverlap,
VerifierSeverity::Error,
format!(
"stack slots {} (offset {}, size {}) and {} (offset {}, size {}) overlap",
self.stack_slots[i].slot_id,
self.stack_slots[i].offset,
self.stack_slots[i].size,
self.stack_slots[j].slot_id,
self.stack_slots[j].offset,
self.stack_slots[j].size,
),
self.stack_slots[i].location.clone(),
));
}
}
}
for slot in &self.stack_slots {
if slot.alignment != 0 && (slot.offset as u64) % slot.alignment != 0 {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::StackSlotUnaligned,
VerifierSeverity::Error,
format!(
"stack slot {} at offset {} not aligned to {}",
slot.slot_id, slot.offset, slot.alignment
),
slot.location.clone(),
));
}
}
}
fn verify_ssa_global(&self, result: &mut X86VerificationResult) {
for (vr, uses) in &self.liveness.virt_reg_uses {
if !self.liveness.virt_reg_defs.contains_key(vr) {
for use_loc in uses {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::SsaNoDef,
VerifierSeverity::Error,
format!(
"virtual register %{} used but never defined in the function",
vr
),
use_loc.clone(),
));
}
}
}
for (vr, def_loc) in &self.liveness.virt_reg_defs {
if !self.liveness.virt_reg_uses.contains_key(vr) {
}
}
}
fn should_abort(&self, result: &X86VerificationResult) -> bool {
self.error_count >= self.strictness.max_errors
|| result.errors.iter().filter(|e| e.is_fatal()).count() > 0
}
pub fn register_stack_slot(
&mut self,
slot_id: u32,
offset: i64,
size: u64,
alignment: u64,
location: MachineSourceLocation,
) {
self.stack_slots.push(StackSlotInfo {
slot_id,
offset,
size,
alignment,
location,
});
}
}
impl Default for X86MachineVerifier {
fn default() -> Self {
Self::new_pre_ra(true, VerifierStrictness::default())
}
}
pub struct X86MachineVerifierPass {
pub phase: VerificationPhase,
pub strictness: VerifierStrictness,
pub is_64bit: bool,
pub is_windows: bool,
pub pass_name: String,
pub pass_id: u64,
pub enabled: bool,
pub fail_on_error: bool,
results: Vec<X86VerificationResult>,
}
impl X86MachineVerifierPass {
pub fn new_pre_ra(strictness: VerifierStrictness) -> Self {
Self {
phase: VerificationPhase::PreRA,
strictness,
is_64bit: true,
is_windows: false,
pass_name: "X86 Machine Verifier (Pre-RA)".to_string(),
pass_id: 0x8000,
enabled: true,
fail_on_error: false,
results: Vec::new(),
}
}
pub fn new_post_ra(strictness: VerifierStrictness) -> Self {
Self {
phase: VerificationPhase::PostRA,
strictness,
is_64bit: true,
is_windows: false,
pass_name: "X86 Machine Verifier (Post-RA)".to_string(),
pass_id: 0x8001,
enabled: true,
fail_on_error: true,
results: Vec::new(),
}
}
pub fn relaxed() -> Self {
Self::new_pre_ra(VerifierStrictness::relaxed())
}
pub fn strict() -> Self {
Self::new_pre_ra(VerifierStrictness::strict())
}
pub fn debug() -> Self {
Self::new_pre_ra(VerifierStrictness::debug())
}
pub fn with_64bit(mut self) -> Self {
self.is_64bit = true;
self
}
pub fn with_32bit(mut self) -> Self {
self.is_64bit = false;
self
}
pub fn with_windows_abi(mut self) -> Self {
self.is_windows = true;
self
}
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.pass_name = name.into();
self
}
pub fn with_id(mut self, id: u64) -> Self {
self.pass_id = id;
self
}
pub fn run_on_function(&mut self, mf: &MachineFunction) -> X86VerificationResult {
if !self.enabled {
return X86VerificationResult::new(&mf.name);
}
let mut verifier = match self.phase {
VerificationPhase::PreRA => {
X86MachineVerifier::new_pre_ra(self.is_64bit, self.strictness.clone())
}
VerificationPhase::PostRA => {
X86MachineVerifier::new_post_ra(self.is_64bit, self.strictness.clone())
}
};
if self.is_windows {
verifier = verifier.with_windows_abi();
}
let result = verifier.verify(mf);
self.results.push(result.clone());
result
}
pub fn run_on_functions(
&mut self,
functions: &[MachineFunction],
) -> Vec<X86VerificationResult> {
let mut results = Vec::new();
for mf in functions {
results.push(self.run_on_function(mf));
}
results
}
pub fn results(&self) -> &[X86VerificationResult] {
&self.results
}
pub fn clear_results(&mut self) {
self.results.clear();
}
pub fn total_errors(&self) -> usize {
self.results.iter().map(|r| r.error_count()).sum()
}
pub fn total_warnings(&self) -> usize {
self.results.iter().map(|r| r.warning_count()).sum()
}
pub fn has_errors(&self) -> bool {
self.results.iter().any(|r| r.has_errors())
}
pub fn print_summary(&self) {
if self.results.is_empty() {
println!("[{}] No functions verified.", self.pass_name);
return;
}
let total_funcs = self.results.len();
let passed = self.results.iter().filter(|r| r.passed).count();
let failed = total_funcs - passed;
let total_errs: usize = self.results.iter().map(|r| r.errors.len()).sum();
let total_warns: usize = self.results.iter().map(|r| r.warnings.len()).sum();
let total_infos: usize = self.results.iter().map(|r| r.infos.len()).sum();
let total_blocks: usize = self.results.iter().map(|r| r.blocks_verified).sum();
let total_instrs: usize = self.results.iter().map(|r| r.instructions_verified).sum();
println!(
"[{}] Verified {} function(s): {} passed, {} failed",
self.pass_name, total_funcs, passed, failed
);
println!(
" {} errors, {} warnings, {} infos across {} blocks and {} instructions",
total_errs, total_warns, total_infos, total_blocks, total_instrs
);
for result in &self.results {
if !result.passed {
println!(" FAILED: {}", result.function_name);
for err in &result.errors {
println!(" {}", err);
}
}
}
}
}
impl Default for X86MachineVerifierPass {
fn default() -> Self {
Self::new_pre_ra(VerifierStrictness::default())
}
}
pub fn verify_pre_ra(mf: &MachineFunction) -> X86VerificationResult {
let mut verifier = X86MachineVerifier::new_pre_ra(true, VerifierStrictness::default());
verifier.verify(mf)
}
pub fn verify_post_ra(mf: &MachineFunction) -> X86VerificationResult {
let mut verifier = X86MachineVerifier::new_post_ra(true, VerifierStrictness::default());
verifier.verify(mf)
}
pub fn verify_strict(mf: &MachineFunction, phase: VerificationPhase) -> X86VerificationResult {
let strictness = VerifierStrictness::strict();
let mut verifier = match phase {
VerificationPhase::PreRA => X86MachineVerifier::new_pre_ra(true, strictness),
VerificationPhase::PostRA => X86MachineVerifier::new_post_ra(true, strictness),
};
verifier.verify(mf)
}
pub fn verify_relaxed(mf: &MachineFunction, phase: VerificationPhase) -> X86VerificationResult {
let strictness = VerifierStrictness::relaxed();
let mut verifier = match phase {
VerificationPhase::PreRA => X86MachineVerifier::new_pre_ra(true, strictness),
VerificationPhase::PostRA => X86MachineVerifier::new_post_ra(true, strictness),
};
verifier.verify(mf)
}
pub fn format_verification_report(result: &X86VerificationResult) -> String {
let mut report = String::new();
report.push_str(&format!(
"=== Verification Report for '{}' ===\n",
result.function_name
));
report.push_str(&format!(
"Status: {}\n",
if result.passed { "PASSED" } else { "FAILED" }
));
report.push_str(&format!(
"Blocks: {}, Instructions: {}\n",
result.blocks_verified, result.instructions_verified
));
report.push_str(&format!(
"Errors: {}, Warnings: {}, Infos: {}\n",
result.errors.len(),
result.warnings.len(),
result.infos.len()
));
if !result.errors.is_empty() {
report.push_str("\n--- Errors ---\n");
for err in &result.errors {
report.push_str(&format!(" {}\n", err));
}
}
if !result.warnings.is_empty() {
report.push_str("\n--- Warnings ---\n");
for warn in &result.warnings {
report.push_str(&format!(" {}\n", warn));
}
}
if !result.infos.is_empty() {
report.push_str("\n--- Info ---\n");
for info in &result.infos {
report.push_str(&format!(" {}\n", info));
}
}
report
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86EncodingForm {
Legacy,
LegacyRex,
Vex2Byte,
Vex3Byte,
Evex,
Xop,
Unknown,
}
impl fmt::Display for X86EncodingForm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
X86EncodingForm::Legacy => write!(f, "Legacy"),
X86EncodingForm::LegacyRex => write!(f, "Legacy+REX"),
X86EncodingForm::Vex2Byte => write!(f, "VEX.2B"),
X86EncodingForm::Vex3Byte => write!(f, "VEX.3B"),
X86EncodingForm::Evex => write!(f, "EVEX"),
X86EncodingForm::Xop => write!(f, "XOP"),
X86EncodingForm::Unknown => write!(f, "Unknown"),
}
}
}
pub fn determine_encoding_form(opcode: u32, has_rex: bool) -> X86EncodingForm {
if uses_evex_encoding(opcode) {
X86EncodingForm::Evex
} else if uses_vex_encoding(opcode) {
X86EncodingForm::Vex3Byte
} else if has_rex {
X86EncodingForm::LegacyRex
} else {
X86EncodingForm::Legacy
}
}
pub fn verify_encoding_form(
opcode: u32,
form: X86EncodingForm,
has_avx: bool,
has_avx512: bool,
is_64bit: bool,
loc: &MachineSourceLocation,
result: &mut X86VerificationResult,
) {
match form {
X86EncodingForm::Vex2Byte | X86EncodingForm::Vex3Byte => {
if !has_avx {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::FeatureNotAvailable,
VerifierSeverity::Error,
format!(
"{} encoding requires AVX support, but target does not support AVX",
form
),
loc.clone(),
));
}
}
X86EncodingForm::Evex => {
if !has_avx512 {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::FeatureNotAvailable,
VerifierSeverity::Error,
"EVEX encoding requires AVX-512 support, but target does not support AVX-512"
.to_string(),
loc.clone(),
));
}
}
X86EncodingForm::LegacyRex => {
if !is_64bit {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::RexPrefixInvalid,
VerifierSeverity::Error,
"REX encoding only valid in 64-bit mode".to_string(),
loc.clone(),
));
}
}
_ => {}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModRmAddressMode {
Indirect,
RipRelativeOrDisp32,
SibIndirect,
IndirectDisp8,
SibDisp8,
IndirectDisp32,
SibDisp32,
RegisterDirect,
}
impl ModRmAddressMode {
pub fn disp_size(&self) -> u8 {
match self {
ModRmAddressMode::Indirect | ModRmAddressMode::RipRelativeOrDisp32 => {
0 }
ModRmAddressMode::SibIndirect => 0,
ModRmAddressMode::IndirectDisp8 | ModRmAddressMode::SibDisp8 => 1,
ModRmAddressMode::IndirectDisp32 | ModRmAddressMode::SibDisp32 => 4,
ModRmAddressMode::RegisterDirect => 0,
}
}
pub fn is_memory(&self) -> bool {
!matches!(self, ModRmAddressMode::RegisterDirect)
}
pub fn requires_sib(&self) -> bool {
matches!(
self,
ModRmAddressMode::SibIndirect
| ModRmAddressMode::SibDisp8
| ModRmAddressMode::SibDisp32
)
}
}
pub fn classify_modrm_address_mode(modrm: &ModRmInfo) -> ModRmAddressMode {
match (modrm.mod_, modrm.rm) {
(0b11, _) => ModRmAddressMode::RegisterDirect,
(0b00, 0b100) => ModRmAddressMode::SibIndirect,
(0b00, 0b101) => ModRmAddressMode::RipRelativeOrDisp32,
(0b00, _) => ModRmAddressMode::Indirect,
(0b01, 0b100) => ModRmAddressMode::SibDisp8,
(0b01, _) => ModRmAddressMode::IndirectDisp8,
(0b10, 0b100) => ModRmAddressMode::SibDisp32,
(0b10, _) => ModRmAddressMode::IndirectDisp32,
_ => ModRmAddressMode::Indirect, }
}
pub fn verify_modrm_addressing(
modrm: &ModRmInfo,
opcode: u32,
disp_size: u8,
has_sib: bool,
sib: Option<&SibInfo>,
loc: &MachineSourceLocation,
result: &mut X86VerificationResult,
) {
let mode = classify_modrm_address_mode(modrm);
let expected_disp = mode.disp_size();
if expected_disp != disp_size && !matches!(mode, ModRmAddressMode::RipRelativeOrDisp32) {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::ModRmDispSizeMismatch,
VerifierSeverity::Error,
format!(
"ModR/M mode {:?} expects disp{}/{} but got disp{}",
mode,
expected_disp * 8,
expected_disp,
disp_size,
),
loc.clone(),
));
}
if mode.requires_sib() && !has_sib {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::SibByteInvalid,
VerifierSeverity::Error,
"ModR/M addressing mode requires SIB byte but none present".to_string(),
loc.clone(),
));
}
if !mode.requires_sib() && has_sib {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::SibByteInvalid,
VerifierSeverity::Warning,
"SIB byte present but not required by ModR/M addressing mode".to_string(),
loc.clone(),
));
}
if let (true, Some(sib_info)) = (has_sib, sib) {
if sib_info.index == 0b100 && sib_info.scale != 0b00 {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::SibEspRspConstraint,
VerifierSeverity::Error,
"SIB index=4 (ESP/RSP) cannot be used as scaled index; scale must be 0 (no index)"
.to_string(),
loc.clone(),
));
}
if sib_info.base == 0b101 && modrm.mod_ == 0b00 {
if disp_size != 4 {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::ModRmDispSizeMismatch,
VerifierSeverity::Error,
"SIB base=5 with mod=00 requires 32-bit displacement".to_string(),
loc.clone(),
));
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VexEncodingClass {
L0,
L1,
LIG,
}
pub fn classify_vex_l(opcode: u32) -> VexEncodingClass {
match opcode {
x if x == X86Opcode::VADDSS as u32
|| x == X86Opcode::VSUBSS as u32
|| x == X86Opcode::VMULSS as u32
|| x == X86Opcode::VDIVSS as u32
|| x == X86Opcode::VMOVSS as u32
|| x == X86Opcode::VMOVSD as u32
|| x == X86Opcode::VCOMISS as u32
|| x == X86Opcode::VUCOMISS as u32
|| x == X86Opcode::VCVTSI2SS as u32
|| x == X86Opcode::VCVTSS2SI as u32 =>
{
VexEncodingClass::LIG
}
x if x == X86Opcode::VADDPS as u32
|| x == X86Opcode::VMULPS as u32
|| x == X86Opcode::VSUBPS as u32
|| x == X86Opcode::VDIVPS as u32
|| x == X86Opcode::VANDPD as u32
|| x == X86Opcode::VORPD as u32
|| x == X86Opcode::VXORPD as u32
|| x == X86Opcode::VMOVUPS as u32
|| x == X86Opcode::VMOVAPS as u32 =>
{
VexEncodingClass::L1 }
_ => VexEncodingClass::L0,
}
}
pub fn verify_vex_encoding_constraints(
opcode: u32,
vex: &VexInfo,
loc: &MachineSourceLocation,
result: &mut X86VerificationResult,
) {
let l_class = classify_vex_l(opcode);
match l_class {
VexEncodingClass::LIG => {
if vex.l {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::VexLBitInvalid,
VerifierSeverity::Warning,
"VEX.L should be 0 for LIG (L-ignored) instructions".to_string(),
loc.clone(),
));
}
}
VexEncodingClass::L0 => {
if vex.l {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::VexLBitInvalid,
VerifierSeverity::Error,
"VEX.L=1 requires 256-bit support, but instruction only supports 128-bit"
.to_string(),
loc.clone(),
));
}
}
VexEncodingClass::L1 => {
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EvexVectorLength {
Xmm128 = 0,
Ymm256 = 1,
Zmm512 = 2,
}
impl EvexVectorLength {
pub fn from_lll(lll: u8) -> Option<Self> {
match lll {
0 => Some(EvexVectorLength::Xmm128),
1 => Some(EvexVectorLength::Ymm256),
2 => Some(EvexVectorLength::Zmm512),
_ => None,
}
}
pub fn bit_width(&self) -> u16 {
match self {
EvexVectorLength::Xmm128 => 128,
EvexVectorLength::Ymm256 => 256,
EvexVectorLength::Zmm512 => 512,
}
}
}
#[derive(Debug, Clone)]
pub struct ImplicitRegUsage {
pub implicit_defs: Vec<u32>,
pub implicit_uses: Vec<u32>,
pub implicit_clobbers: Vec<u32>,
}
pub fn get_implicit_reg_usage(opcode: u32) -> ImplicitRegUsage {
let mut usage = ImplicitRegUsage {
implicit_defs: Vec::new(),
implicit_uses: Vec::new(),
implicit_clobbers: Vec::new(),
};
match opcode {
x if x == X86Opcode::CALL as u32 => {
usage.implicit_uses.push(4); usage.implicit_defs.push(4); usage.implicit_clobbers.push(0); usage.implicit_clobbers.push(1); usage.implicit_clobbers.push(2); usage.implicit_clobbers.push(8); usage.implicit_clobbers.push(9); usage.implicit_clobbers.push(10); usage.implicit_clobbers.push(11); }
x if x == X86Opcode::RET as u32
|| x == X86Opcode::RET1 as u32
|| x == X86Opcode::RET2 as u32 =>
{
usage.implicit_uses.push(4); usage.implicit_defs.push(4); }
x if x == X86Opcode::PUSH as u32 => {
usage.implicit_uses.push(4);
usage.implicit_defs.push(4);
}
x if x == X86Opcode::POP as u32 => {
usage.implicit_uses.push(4);
usage.implicit_defs.push(4);
}
x if x == X86Opcode::MUL as u32 || x == X86Opcode::IMUL as u32 => {
usage.implicit_uses.push(0); usage.implicit_defs.push(0); usage.implicit_defs.push(2); usage.implicit_clobbers.push(2); }
x if x == X86Opcode::DIV as u32 || x == X86Opcode::IDIV as u32 => {
usage.implicit_uses.push(0); usage.implicit_uses.push(2); usage.implicit_defs.push(0); usage.implicit_defs.push(2); }
x if x == X86Opcode::SHL as u32
|| x == X86Opcode::SHR as u32
|| x == X86Opcode::SAR as u32
|| x == X86Opcode::ROL as u32
|| x == X86Opcode::ROR as u32 =>
{
}
x if supports_rep_prefix(x) => {
usage.implicit_uses.push(1); usage.implicit_defs.push(1); }
x if x == X86Opcode::ENTER as u32 => {
usage.implicit_uses.push(5); usage.implicit_uses.push(4); usage.implicit_defs.push(5); usage.implicit_defs.push(4); }
x if x == X86Opcode::LEAVE as u32 => {
usage.implicit_uses.push(5); usage.implicit_defs.push(4); usage.implicit_defs.push(5); }
_ => {}
}
usage
}
pub fn verify_ssa_dominance(
virt_defs: &HashMap<u32, MachineSourceLocation>,
virt_uses: &HashMap<u32, Vec<MachineSourceLocation>>,
_dom_tree: Option<&HashMap<usize, Vec<usize>>>,
result: &mut X86VerificationResult,
) {
for (vr, def_loc) in virt_defs {
if let Some(uses) = virt_uses.get(vr) {
for use_loc in uses {
if def_loc.block_idx > use_loc.block_idx {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::SsaUseBeforeDef,
VerifierSeverity::Warning,
format!(
"virtual register %{} used at {} before definition at {} (possible back-edge)",
vr, use_loc, def_loc
),
use_loc.clone(),
));
}
}
}
}
}
pub fn detect_dead_definitions(
virt_defs: &HashMap<u32, MachineSourceLocation>,
virt_uses: &HashMap<u32, Vec<MachineSourceLocation>>,
) -> Vec<(u32, MachineSourceLocation)> {
let mut dead = Vec::new();
for (vr, def_loc) in virt_defs {
if !virt_uses.contains_key(vr) {
dead.push((*vr, def_loc.clone()));
}
}
dead
}
pub fn has_tied_operands(opcode: u32) -> bool {
matches!(
opcode,
x if x == X86Opcode::ADD as u32
|| x == X86Opcode::ADC as u32
|| x == X86Opcode::SUB as u32
|| x == X86Opcode::SBB as u32
|| x == X86Opcode::AND as u32
|| x == X86Opcode::OR as u32
|| x == X86Opcode::XOR as u32
|| x == X86Opcode::SHL as u32
|| x == X86Opcode::SHR as u32
|| x == X86Opcode::SAR as u32
|| x == X86Opcode::ROL as u32
|| x == X86Opcode::ROR as u32
|| x == X86Opcode::NEG as u32
|| x == X86Opcode::NOT as u32
|| x == X86Opcode::INC as u32
|| x == X86Opcode::DEC as u32
|| x == X86Opcode::IMUL as u32
|| x == X86Opcode::MUL as u32
)
}
pub fn verify_tied_operands(
instr: &MachineInstr,
loc: &MachineSourceLocation,
result: &mut X86VerificationResult,
) {
if !has_tied_operands(instr.opcode) || instr.operands.len() < 2 {
return;
}
let dest = &instr.operands[0];
let src = &instr.operands[1];
match (dest, src) {
(MachineOperand::PhysReg(d), MachineOperand::PhysReg(s)) => {
if d != s {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::RegClassMismatch,
VerifierSeverity::Error,
format!(
"tied operand constraint violated: dest={} src={} must be same physical register",
d, s
),
loc.clone(),
));
}
}
_ => {
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86OperandConstraint {
FixedRegister(u32),
ImmRange(i64, i64),
MustBeMemory,
MustBeRegister,
TiedOperand,
MustBeGpr,
MustBeVector,
NoStackBase,
}
pub fn get_operand_constraints(opcode: u32, operand_idx: usize) -> Vec<X86OperandConstraint> {
let mut constraints = Vec::new();
match opcode {
x if x == X86Opcode::SHL as u32
|| x == X86Opcode::SHR as u32
|| x == X86Opcode::SAR as u32
|| x == X86Opcode::ROL as u32
|| x == X86Opcode::ROR as u32
|| x == X86Opcode::RCL as u32
|| x == X86Opcode::RCR as u32 =>
{
if operand_idx == 1 {
constraints.push(X86OperandConstraint::MustBeRegister);
}
}
x if x == X86Opcode::SHLD as u32 || x == X86Opcode::SHRD as u32 => {
if operand_idx == 2 {
constraints.push(X86OperandConstraint::MustBeRegister);
}
}
x if x == X86Opcode::MUL as u32
|| x == X86Opcode::IMUL as u32
|| x == X86Opcode::DIV as u32
|| x == X86Opcode::IDIV as u32 =>
{
if operand_idx == 0 {
constraints.push(X86OperandConstraint::MustBeRegister);
}
}
x if x == X86Opcode::LEA as u32 => {
if operand_idx == 0 {
constraints.push(X86OperandConstraint::MustBeGpr);
}
if operand_idx == 1 {
constraints.push(X86OperandConstraint::MustBeMemory);
}
}
x if x == X86Opcode::MOVSX as u32 || x == X86Opcode::MOVZX as u32 => {
if operand_idx == 0 {
constraints.push(X86OperandConstraint::MustBeGpr);
}
if operand_idx == 1 {
constraints.push(X86OperandConstraint::MustBeRegister);
}
}
x if x == X86Opcode::CMPXCHG as u32
|| x == X86Opcode::CMPXCHG8B as u32
|| x == X86Opcode::CMPXCHG16B as u32 =>
{
if operand_idx == 0 {
constraints.push(X86OperandConstraint::MustBeMemory);
}
}
x if x == X86Opcode::ENTER as u32 => {
if operand_idx == 1 {
constraints.push(X86OperandConstraint::ImmRange(0, 31));
}
}
x if x == X86Opcode::BOUND as u32 => {
constraints.push(X86OperandConstraint::MustBeMemory);
}
_ => {}
}
constraints
}
pub fn verify_phys_reg_liveness(blocks: &[MachineBasicBlock], result: &mut X86VerificationResult) {
let mut live_across_blocks: HashMap<u32, HashSet<u32>> = HashMap::new();
for (bi, block) in blocks.iter().enumerate() {
let mut live_now: HashSet<u32> = HashSet::new();
for (ii, instr) in block.instructions.iter().enumerate() {
let loc = MachineSourceLocation::new(bi, &block.name, ii);
for op in &instr.operands {
if let MachineOperand::PhysReg(pr) = op {
let is_use = !(instr.def.is_some()
&& instr.operands.first().map_or(
false,
|o| matches!(o, MachineOperand::PhysReg(p) if p == pr),
));
if is_use && !live_now.contains(pr) {
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::PhysRegUseAfterClobber,
VerifierSeverity::Warning,
format!("physical register {} used but may not be live", pr),
loc.clone(),
));
}
}
}
if let Some(_def) = instr.def {
for op in &instr.operands {
if let MachineOperand::PhysReg(pr) = op {
live_now.insert(*pr);
break;
}
}
}
let implicit = get_implicit_reg_usage(instr.opcode);
for clobbered in &implicit.implicit_clobbers {
live_now.remove(clobbered);
}
for def in &implicit.implicit_defs {
live_now.insert(*def);
}
}
live_across_blocks.insert(bi as u32, live_now.clone());
}
}
pub fn verify_vector_register_consistency(
instr: &MachineInstr,
loc: &MachineSourceLocation,
result: &mut X86VerificationResult,
) {
let opcode = instr.opcode;
let expected_class: Option<RegClass> = if uses_evex_encoding(opcode) {
Some(RegClass::ZMM) } else if uses_vex_encoding(opcode) {
Some(RegClass::YMM) } else if matches!(
opcode,
x if x == X86Opcode::ADDPS as u32
|| x == X86Opcode::ADDSS as u32
|| x == X86Opcode::MULPS as u32
|| x == X86Opcode::MOVUPS as u32
|| x == X86Opcode::MOVAPS as u32
|| x == X86Opcode::MOVSS as u32
|| x == X86Opcode::MOVSD as u32
) {
Some(RegClass::XMM)
} else {
None
};
if expected_class.is_none() {
return;
}
for op in &instr.operands {
if let MachineOperand::PhysReg(pr) = op {
let _ = pr;
}
}
}
#[derive(Debug, Clone, Default)]
pub struct GcPointerState {
pub gc_pointer_regs: HashSet<u32>,
pub gc_pointer_slots: HashSet<u32>,
pub enabled: bool,
}
impl GcPointerState {
pub fn new() -> Self {
Self::default()
}
pub fn mark_gc_pointer(&mut self, reg: u32) {
self.gc_pointer_regs.insert(reg);
}
pub fn clear_gc_pointer(&mut self, reg: u32) {
self.gc_pointer_regs.remove(®);
}
pub fn is_gc_pointer(&self, reg: u32) -> bool {
self.gc_pointer_regs.contains(®)
}
pub fn verify_gc_store(
&self,
_src_reg: u32,
_dst_addr: u64,
loc: &MachineSourceLocation,
result: &mut X86VerificationResult,
) {
if !self.enabled {
return;
}
let _ = loc;
let _ = result;
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_test_function(name: &str) -> MachineFunction {
MachineFunction {
name: name.to_string(),
blocks: Vec::new(),
virt_reg_counter: 0,
}
}
fn make_block(name: &str) -> MachineBasicBlock {
MachineBasicBlock {
id: 0,
name: name.to_string(),
instructions: Vec::new(),
successors: Vec::new(),
predecessors: Vec::new(),
is_entry: false,
}
}
fn make_instr(opcode: u32) -> MachineInstr {
MachineInstr {
opcode,
operands: Vec::new(),
def: None,
}
}
fn make_instr_with_def(opcode: u32, def: u32) -> MachineInstr {
MachineInstr {
opcode,
operands: Vec::new(),
def: Some(def),
}
}
fn make_instr_with_ops(opcode: u32, operands: Vec<MachineOperand>) -> MachineInstr {
MachineInstr {
opcode,
operands,
def: None,
}
}
fn make_reg(vr: u32) -> MachineOperand {
MachineOperand::Reg(vr)
}
fn make_phys_reg(pr: u32) -> MachineOperand {
MachineOperand::PhysReg(pr)
}
fn make_imm(v: i64) -> MachineOperand {
MachineOperand::Imm(v)
}
fn make_label(name: &str) -> MachineOperand {
MachineOperand::Label(name.to_string())
}
#[test]
fn test_ssa_single_def() {
let mut mf = make_test_function("test_ssa_single_def");
let vr = mf.new_vreg();
let mut block = make_block("entry");
block
.instructions
.push(make_instr_with_def(X86Opcode::MOV as u32, vr));
let mut use_instr = make_instr(X86Opcode::RET as u32);
use_instr.operands.push(make_reg(vr));
block.instructions.push(use_instr);
mf.blocks.push(block);
let result = verify_pre_ra(&mf);
assert!(
result.passed,
"SSA single def should pass: {:?}",
result.errors
);
assert_eq!(result.error_count(), 0);
}
#[test]
fn test_ssa_multiple_def_detection() {
let mut mf = make_test_function("test_ssa_multiple_def");
let vr = mf.new_vreg();
let mut block = make_block("entry");
block
.instructions
.push(make_instr_with_def(X86Opcode::MOV as u32, vr));
block
.instructions
.push(make_instr_with_def(X86Opcode::ADD as u32, vr));
block.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(block);
let result = verify_pre_ra(&mf);
assert!(!result.passed, "Multiple defs should fail");
assert!(result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::SsaMultipleDef));
}
#[test]
fn test_use_before_def() {
let mut mf = make_test_function("test_use_before_def");
let vr = mf.new_vreg();
let mut block = make_block("entry");
let mut use_instr = make_instr(X86Opcode::ADD as u32);
use_instr.operands.push(make_reg(vr));
block.instructions.push(use_instr);
block
.instructions
.push(make_instr_with_def(X86Opcode::MOV as u32, vr));
block.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(block);
let result = verify_pre_ra(&mf);
assert!(result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::SsaUseBeforeDef));
}
#[test]
fn test_phi_not_at_block_start() {
let mut mf = make_test_function("test_phi_position");
let vr0 = mf.new_vreg();
let vr1 = mf.new_vreg();
let vr2 = mf.new_vreg();
let vr3 = mf.new_vreg();
let mut entry = make_block("entry");
entry
.instructions
.push(make_instr_with_def(X86Opcode::MOV as u32, vr0));
entry
.instructions
.push(make_instr_with_def(X86Opcode::MOV as u32, vr1));
let mut jmp = make_instr(X86Opcode::JMP as u32);
jmp.operands.push(make_label("merge"));
entry.instructions.push(jmp);
entry.successors.push(2);
let mut alt = make_block("alternate");
alt.instructions
.push(make_instr_with_def(X86Opcode::MOV as u32, vr2));
alt.instructions
.push(make_instr_with_def(X86Opcode::MOV as u32, vr3));
let mut jmp2 = make_instr(X86Opcode::JMP as u32);
jmp2.operands.push(make_label("merge"));
alt.instructions.push(jmp2);
alt.successors.push(2);
let mut merge = make_block("merge");
merge
.instructions
.push(make_instr_with_def(X86Opcode::MOV as u32, vr1));
let mut phi = make_instr_with_def(X86Opcode::PHI as u32, vr3);
phi.operands.push(make_reg(vr0));
phi.operands.push(make_label("entry"));
phi.operands.push(make_reg(vr2));
phi.operands.push(make_label("alternate"));
merge.instructions.push(phi);
merge.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(entry);
mf.blocks.push(alt);
mf.blocks.push(merge);
let result = verify_pre_ra(&mf);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::PhiNotAtBlockStart),
"Expected PhiNotAtBlockStart error, got: {:?}",
result
.errors
.iter()
.map(|e| format!("{:?}", e.kind))
.collect::<Vec<_>>()
);
}
#[test]
fn test_phi_missing_predecessor() {
let mut mf = make_test_function("test_phi_missing_pred");
let vr0 = mf.new_vreg();
let vr1 = mf.new_vreg();
let vr2 = mf.new_vreg();
let mut entry = make_block("entry");
entry
.instructions
.push(make_instr_with_def(X86Opcode::MOV as u32, vr0));
let mut jmp = make_instr(X86Opcode::JMP as u32);
jmp.operands.push(make_label("merge"));
entry.instructions.push(jmp);
entry.successors.push(2);
let mut alt = make_block("alt");
alt.instructions
.push(make_instr_with_def(X86Opcode::MOV as u32, vr1));
let mut jmp2 = make_instr(X86Opcode::JMP as u32);
jmp2.operands.push(make_label("merge"));
alt.instructions.push(jmp2);
alt.successors.push(2);
let mut merge = make_block("merge");
let mut phi = make_instr_with_def(X86Opcode::PHI as u32, vr2);
phi.operands.push(make_reg(vr0));
phi.operands.push(make_label("entry"));
merge.instructions.push(phi);
merge.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(entry);
mf.blocks.push(alt);
mf.blocks.push(merge);
let result = verify_pre_ra(&mf);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::PhiMissingPredecessor),
"Expected PhiMissingPredecessor error"
);
}
#[test]
fn test_missing_terminator() {
let mut mf = make_test_function("test_missing_terminator");
let mut block = make_block("entry");
block.instructions.push(make_instr(X86Opcode::ADD as u32));
mf.blocks.push(block);
let result = verify_pre_ra(&mf);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::MissingTerminator),
"Expected MissingTerminator error"
);
}
#[test]
fn test_instructions_after_terminator() {
let mut mf = make_test_function("test_instrs_after_terminator");
let mut block = make_block("entry");
block.instructions.push(make_instr(X86Opcode::ADD as u32));
block.instructions.push(make_instr(X86Opcode::RET as u32));
block.instructions.push(make_instr(X86Opcode::MOV as u32));
block.successors = Vec::new();
mf.blocks.push(block);
let result = verify_pre_ra(&mf);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::InstructionsAfterTerminator),
"Expected InstructionsAfterTerminator error"
);
}
#[test]
fn test_valid_block_with_terminator() {
let mut mf = make_test_function("test_valid_terminator");
let mut block = make_block("entry");
block.instructions.push(make_instr(X86Opcode::ADD as u32));
block.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(block);
let result = verify_pre_ra(&mf);
assert!(
result.passed,
"Expected to pass with valid terminator: {:?}",
result.errors
);
}
#[test]
fn test_conditional_branch_terminator() {
let mut mf = make_test_function("test_cond_branch");
let mut entry = make_block("entry");
let mut cmp = make_instr(X86Opcode::CMP as u32);
cmp.operands.push(make_reg(0));
cmp.operands.push(make_imm(0));
entry.instructions.push(cmp);
let mut je = make_instr(X86Opcode::JE as u32);
je.operands.push(make_label("target"));
entry.instructions.push(je);
entry.successors.push(1);
let mut target = make_block("target");
target.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(entry);
mf.blocks.push(target);
let result = verify_pre_ra(&mf);
assert!(
result.passed,
"Conditional branch should pass: {:?}",
result.errors
);
}
#[test]
fn test_cfg_successor_not_found() {
let mut mf = make_test_function("test_cfg_successor");
let mut block = make_block("entry");
let mut jmp = make_instr(X86Opcode::JMP as u32);
jmp.operands.push(make_label("nonexistent"));
block.instructions.push(jmp);
block.successors.push(999);
mf.blocks.push(block);
let result = verify_pre_ra(&mf);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::CfgSuccessorNotFound),
"Expected CfgSuccessorNotFound error"
);
}
#[test]
fn test_cfg_valid_successor() {
let mut mf = make_test_function("test_cfg_valid_succ");
let mut entry = make_block("entry");
let mut jmp = make_instr(X86Opcode::JMP as u32);
jmp.operands.push(make_label("target"));
entry.instructions.push(jmp);
entry.successors.push(1);
let mut target = make_block("target");
target.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(entry);
mf.blocks.push(target);
let result = verify_pre_ra(&mf);
assert!(result.passed, "Valid CFG should pass: {:?}", result.errors);
}
#[test]
fn test_cfg_unreachable_block_warning() {
let mut mf = make_test_function("test_unreachable");
let mut entry = make_block("entry");
entry.instructions.push(make_instr(X86Opcode::RET as u32));
let mut unreachable_block = make_block("unreachable");
unreachable_block
.instructions
.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(entry);
mf.blocks.push(unreachable_block);
let result = verify_pre_ra(&mf);
assert!(
result
.warnings
.iter()
.any(|w| w.kind == X86VerificationErrorKind::CfgUnreachableBlock),
"Expected unreachable block warning"
);
}
#[test]
fn test_post_ra_no_virt_regs() {
let mut mf = make_test_function("test_post_ra_clean");
let mut block = make_block("entry");
let mut mov = make_instr(X86Opcode::MOV as u32);
mov.operands.push(make_phys_reg(0)); mov.operands.push(make_phys_reg(1)); block.instructions.push(mov);
block.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(block);
let result = verify_post_ra(&mf);
assert!(
result.passed,
"Post-RA with no virtual regs should pass: {:?}",
result.errors
);
}
#[test]
fn test_post_ra_virt_reg_detected() {
let mut mf = make_test_function("test_post_ra_virt_reg");
let mut block = make_block("entry");
let mut mov = make_instr(X86Opcode::MOV as u32);
mov.operands.push(make_reg(42)); mov.operands.push(make_phys_reg(1));
block.instructions.push(mov);
block.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(block);
let result = verify_post_ra(&mf);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::PostRaVirtRegRemaining),
"Expected PostRaVirtRegRemaining error"
);
}
#[test]
fn test_wrong_operand_count() {
let mut mf = make_test_function("test_wrong_operands");
let mut block = make_block("entry");
block.instructions.push(make_instr(X86Opcode::ADD as u32));
block.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(block);
let result = verify_pre_ra(&mf);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::WrongNumOperands),
"Expected WrongNumOperands error"
);
}
#[test]
fn test_too_many_operands() {
let mut mf = make_test_function("test_too_many_ops");
let mut block = make_block("entry");
let mut ret = make_instr(X86Opcode::RET as u32);
ret.operands.push(make_reg(0));
ret.operands.push(make_imm(1));
block.instructions.push(ret);
mf.blocks.push(block);
let result = verify_pre_ra(&mf);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::WrongNumOperands),
"Expected WrongNumOperands error for RET with operands"
);
}
#[test]
fn test_callee_saved_clobbered() {
let mut mf = make_test_function("test_callee_saved");
let mut block = make_block("entry");
let mut mov = make_instr(X86Opcode::MOV as u32);
mov.operands.push(make_phys_reg(3)); mov.operands.push(make_phys_reg(0)); block.instructions.push(mov);
block.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(block);
let result = verify_post_ra(&mf);
assert!(
result
.errors
.iter()
.any(|e| { e.kind == X86VerificationErrorKind::CalleeSavedClobbered }),
"Expected callee-saved clobbered error, got: {:?}",
result
.errors
.iter()
.map(|e| format!("{:?}", e.kind))
.collect::<Vec<_>>()
);
}
#[test]
fn test_lock_prefix_valid() {
let mut mf = make_test_function("test_lock_valid");
let mut block = make_block("entry");
block
.instructions
.push(make_instr(X86Opcode::LOCK_ADD as u32));
block.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(block);
let mut verifier = X86MachineVerifier::new_post_ra(true, VerifierStrictness::default());
let result = verifier.verify(&mf);
assert!(
result.passed || result.error_count() == 0,
"LOCK ADD should pass lock prefix check"
);
}
#[test]
fn test_lock_prefix_invalid_target() {
let mut mf = make_test_function("test_lock_invalid");
let mut block = make_block("entry");
block.instructions.push(make_instr(X86Opcode::MOV as u32));
block.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(block);
let mut verifier = X86MachineVerifier::new_post_ra(true, VerifierStrictness::default());
let result = verifier.verify(&mf);
assert!(result.passed || result.error_count() == 0);
}
#[test]
fn test_rep_prefix_valid() {
let mut mf = make_test_function("test_rep_valid");
let mut block = make_block("entry");
block
.instructions
.push(make_instr(X86Opcode::REP_MOVSB as u32));
block.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(block);
let mut verifier = X86MachineVerifier::new_post_ra(true, VerifierStrictness::default());
let result = verifier.verify(&mf);
assert!(
result.passed || result.error_count() == 0,
"REP MOVSB should pass rep prefix check"
);
}
#[test]
fn test_stack_slot_overlap() {
let mut verifier = X86MachineVerifier::new_post_ra(true, VerifierStrictness::default());
verifier.register_stack_slot(0, 0, 8, 8, MachineSourceLocation::new(0, "entry", 0));
verifier.register_stack_slot(1, 4, 8, 8, MachineSourceLocation::new(0, "entry", 1));
let mut result = X86VerificationResult::new("test_stack_overlap");
verifier.verify_stack_slots(&mut result);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::StackSlotOverlap),
"Expected StackSlotOverlap error"
);
}
#[test]
fn test_stack_slot_no_overlap() {
let mut verifier = X86MachineVerifier::new_post_ra(true, VerifierStrictness::default());
verifier.register_stack_slot(0, 0, 8, 8, MachineSourceLocation::new(0, "entry", 0));
verifier.register_stack_slot(1, 16, 8, 8, MachineSourceLocation::new(0, "entry", 1));
let mut result = X86VerificationResult::new("test_no_overlap");
verifier.verify_stack_slots(&mut result);
assert!(
result.passed || result.error_count() == 0,
"Non-overlapping slots should not produce errors: {:?}",
result.errors
);
}
#[test]
fn test_stack_slot_unaligned() {
let mut verifier = X86MachineVerifier::new_post_ra(true, VerifierStrictness::default());
verifier.register_stack_slot(0, 4, 8, 8, MachineSourceLocation::new(0, "entry", 0));
let mut result = X86VerificationResult::new("test_unaligned");
verifier.verify_stack_slots(&mut result);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::StackSlotUnaligned),
"Expected StackSlotUnaligned error for offset 4 with alignment 8"
);
}
#[test]
fn test_empty_function() {
let mf = make_test_function("empty_func");
let result = verify_pre_ra(&mf);
assert!(!result.passed, "Empty function should fail");
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::InternalError),
"Expected error for empty function"
);
}
#[test]
fn test_no_return_warning() {
let mut mf = make_test_function("test_no_ret");
let mut block = make_block("entry");
let mut jmp = make_instr(X86Opcode::JMP as u32);
jmp.operands.push(make_label("entry"));
block.instructions.push(jmp);
block.successors.push(0);
mf.blocks.push(block);
let result = verify_pre_ra(&mf);
assert!(
result
.warnings
.iter()
.any(|w| w.kind == X86VerificationErrorKind::NoReturnInstruction),
"Expected NoReturnInstruction warning"
);
}
#[test]
fn test_bundle_structure() {
let mut mf = make_test_function("test_bundles");
let mut block = make_block("entry");
block
.instructions
.push(make_instr(X86Opcode::BUNDLE as u32));
block
.instructions
.push(make_instr(X86Opcode::BUNDLE_INSIDE as u32));
block
.instructions
.push(make_instr(X86Opcode::BUNDLE_INSIDE as u32));
block.instructions.push(make_instr(X86Opcode::ADD as u32));
block.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(block);
let result = verify_pre_ra(&mf);
assert!(
!result.errors.iter().any(|e| {
e.kind == X86VerificationErrorKind::BundleHeaderNotFirst
|| e.kind == X86VerificationErrorKind::BundleInteriorNotInBundle
}),
"Valid bundle should not produce bundle errors"
);
}
#[test]
fn test_bundle_interior_outside_bundle() {
let mut mf = make_test_function("test_bundle_interior_outside");
let mut block = make_block("entry");
block.instructions.push(make_instr(X86Opcode::MOV as u32));
block
.instructions
.push(make_instr(X86Opcode::BUNDLE_INSIDE as u32)); block.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(block);
let result = verify_pre_ra(&mf);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::BundleInteriorNotInBundle),
"Expected BundleInteriorNotInBundle error"
);
}
#[test]
fn test_patchpoint_too_few_operands() {
let mut mf = make_test_function("test_patchpoint");
let mut block = make_block("entry");
let mut pp = make_instr(X86Opcode::PATCHPOINT as u32);
pp.operands.push(make_imm(1));
block.instructions.push(pp);
block.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(block);
let result = verify_pre_ra(&mf);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::PatchPointOperandCount),
"Expected PatchPointOperandCount error"
);
}
#[test]
fn test_patchpoint_valid() {
let mut mf = make_test_function("test_patchpoint_valid");
let mut block = make_block("entry");
let mut pp = make_instr(X86Opcode::PATCHPOINT as u32);
pp.operands.push(make_imm(42)); pp.operands.push(make_imm(5)); pp.operands.push(make_label("target")); block.instructions.push(pp);
block.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(block);
let result = verify_pre_ra(&mf);
assert!(
!result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::PatchPointOperandCount),
"Valid patchpoint should not produce operand count errors"
);
}
#[test]
fn test_stackmap_too_few_operands() {
let mut mf = make_test_function("test_stackmap");
let mut block = make_block("entry");
let mut sm = make_instr(X86Opcode::STACKMAP as u32);
sm.operands.push(make_imm(1));
block.instructions.push(sm);
block.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(block);
let result = verify_pre_ra(&mf);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::StackMapInvalidOperands),
"Expected StackMapInvalidOperands error"
);
}
#[test]
fn test_verifier_pass_runs() {
let mut mf = make_test_function("test_pass");
let mut block = make_block("entry");
block.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(block);
let mut pass = X86MachineVerifierPass::new_pre_ra(VerifierStrictness::default());
let result = pass.run_on_function(&mf);
assert!(result.passed, "Valid function should pass verification");
assert_eq!(pass.results().len(), 1);
}
#[test]
fn test_verifier_pass_disabled() {
let mut mf = make_test_function("test_disabled");
let mut pass = X86MachineVerifierPass::new_pre_ra(VerifierStrictness::default());
pass.enabled = false;
let result = pass.run_on_function(&mf);
assert!(result.passed, "Disabled pass should always pass");
assert_eq!(pass.results().len(), 1);
}
#[test]
fn test_verifier_pass_multiple_functions() {
let mut mf1 = make_test_function("func1");
let mut block1 = make_block("entry");
block1.instructions.push(make_instr(X86Opcode::RET as u32));
mf1.blocks.push(block1);
let mut mf2 = make_test_function("func2");
let mut block2 = make_block("entry");
block2.instructions.push(make_instr(X86Opcode::RET as u32));
mf2.blocks.push(block2);
let mut pass = X86MachineVerifierPass::new_pre_ra(VerifierStrictness::default());
let results = pass.run_on_functions(&[mf1, mf2]);
assert_eq!(results.len(), 2);
assert!(results.iter().all(|r| r.passed));
assert_eq!(pass.total_errors(), 0);
}
#[test]
fn test_multi_block_cfg() {
let mut mf = make_test_function("test_multi_block");
let mut entry = make_block("entry");
entry.instructions.push(make_instr(X86Opcode::ADD as u32));
let mut jne = make_instr(X86Opcode::JNE as u32);
jne.operands.push(make_label("body"));
entry.instructions.push(jne);
entry.successors.push(1);
let mut body = make_block("body");
body.instructions.push(make_instr(X86Opcode::ADD as u32));
let mut jmp = make_instr(X86Opcode::JMP as u32);
jmp.operands.push(make_label("exit"));
body.instructions.push(jmp);
body.successors.push(2);
let mut exit = make_block("exit");
exit.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(entry);
mf.blocks.push(body);
mf.blocks.push(exit);
let result = verify_pre_ra(&mf);
assert!(
result.passed,
"Multi-block CFG should pass: {:?}",
result.errors
);
}
#[test]
fn test_error_display() {
let err = X86VerificationError::new(
X86VerificationErrorKind::SsaMultipleDef,
VerifierSeverity::Error,
"register %v0 defined twice",
MachineSourceLocation::new(0, "entry", 3),
)
.with_context("first definition at bb.entry:1")
.with_context("second definition at bb.entry:3");
let display = format!("{}", err);
assert!(display.contains("ERROR"));
assert!(display.contains("SsaMultipleDef"));
assert!(display.contains("bb.0.entry"));
assert!(display.contains("%3"));
assert!(display.contains("first definition"));
}
#[test]
fn test_verification_result_format() {
let mut result = X86VerificationResult::new("test_func");
result.blocks_verified = 5;
result.instructions_verified = 42;
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::MissingTerminator,
VerifierSeverity::Error,
"block missing terminator",
MachineSourceLocation::new(2, "body", 0),
));
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::SsaUseBeforeDef,
VerifierSeverity::Warning,
"register %v1 used before definition",
MachineSourceLocation::new(1, "entry", 1),
));
let summary = result.format_summary();
assert!(summary.contains("FAILED"));
assert!(summary.contains("test_func"));
assert!(summary.contains("5 blocks"));
assert!(summary.contains("42 instructions"));
assert!(summary.contains("1 error"));
assert!(summary.contains("1 warning"));
}
#[test]
fn test_strictness_relaxed() {
let strict = VerifierStrictness::relaxed();
assert!(!strict.warnings_as_errors);
assert!(!strict.verify_after_every_pass);
assert!(!strict.verify_bundles);
assert!(!strict.verify_landing_pads);
assert!(!strict.verify_patchpoints);
assert!(!strict.verify_calling_conv);
assert!(!strict.verify_encoding);
assert_eq!(strict.max_errors, 50);
}
#[test]
fn test_strictness_strict() {
let strict = VerifierStrictness::strict();
assert!(strict.warnings_as_errors);
assert!(!strict.verify_after_every_pass);
assert!(strict.verify_bundles);
assert!(strict.verify_landing_pads);
assert!(strict.verify_patchpoints);
assert!(strict.verify_calling_conv);
assert_eq!(strict.max_errors, 200);
}
#[test]
fn test_strictness_debug() {
let strict = VerifierStrictness::debug();
assert!(strict.warnings_as_errors);
assert!(strict.verbose);
assert_eq!(strict.max_errors, 500);
}
#[test]
fn test_severity_ordering() {
assert!(VerifierSeverity::Fatal > VerifierSeverity::Error);
assert!(VerifierSeverity::Error > VerifierSeverity::Warning);
assert!(VerifierSeverity::Warning > VerifierSeverity::Info);
assert_eq!(VerifierSeverity::Info, VerifierSeverity::Info);
}
#[test]
fn test_rex_prefix_in_32bit_mode() {
let mut result = X86VerificationResult::new("test_rex_32");
let rex = RexInfo {
present: true,
w: false,
r: false,
x: false,
b: false,
};
verify_rex_prefix(
&rex,
X86Opcode::MOV as u32,
&[],
None,
false, &MachineSourceLocation::new(0, "entry", 0),
&mut result,
);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::RexPrefixInvalid),
"REX in 32-bit mode should be invalid"
);
}
#[test]
fn test_rex_prefix_missing_for_rex_w_required() {
let mut result = X86VerificationResult::new("test_rex_missing");
let rex = RexInfo {
present: false,
w: false,
r: false,
x: false,
b: false,
};
verify_rex_prefix(
&rex,
X86Opcode::MOVABS as u32, &[],
None,
true,
&MachineSourceLocation::new(0, "entry", 0),
&mut result,
);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::RexPrefixMissing),
"Missing REX.W for MOVABS should be detected"
);
}
#[test]
fn test_vex_on_non_vex_instruction() {
let mut result = X86VerificationResult::new("test_vex_non_vex");
let vex = VexInfo {
present: true,
is_3byte: true,
r: false,
x: false,
b: false,
mmmm: 0b00001,
w: false,
vvvv: 0b0000,
l: false,
pp: 0b00,
};
verify_vex_prefix(
&vex,
X86Opcode::MOV as u32, &MachineSourceLocation::new(0, "entry", 0),
&mut result,
);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::VexPrefixInvalid),
"VEX on non-VEX instruction should be invalid"
);
}
#[test]
fn test_vex_invalid_mmmm() {
let mut result = X86VerificationResult::new("test_vex_invalid_mmmm");
let vex = VexInfo {
present: true,
is_3byte: true,
r: false,
x: false,
b: false,
mmmm: 0b00111, w: false,
vvvv: 0b0000,
l: false,
pp: 0b00,
};
verify_vex_prefix(
&vex,
X86Opcode::VADDPS as u32,
&MachineSourceLocation::new(0, "entry", 0),
&mut result,
);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::VexMmmInvalid),
"Invalid VEX.mmmm should be detected"
);
}
#[test]
fn test_evex_sae_requires_512bit() {
let mut result = X86VerificationResult::new("test_evex_sae");
let evex = EvexInfo {
present: true,
r: false,
x: false,
b: false,
r_prime: false,
mmmm: 0b00001,
w: false,
vvvv: 0b0000,
lll: 1, pp: 0b00,
z: false,
bcast: false,
rounding: 0,
sae: true, opmask: 0,
};
verify_evex_prefix(
&evex,
X86Opcode::VADDPDZ as u32,
&MachineSourceLocation::new(0, "entry", 0),
&mut result,
);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::EvexSaeInvalid),
"EVEX SAE with LLL=1 should be invalid"
);
}
#[test]
fn test_modrm_invalid_mod() {
let mut result = X86VerificationResult::new("test_modrm_mod");
let modrm = ModRmInfo {
mod_: 0b100, reg: 0b000,
rm: 0b000,
};
verify_modrm(
&modrm,
X86Opcode::ADD as u32,
false,
&MachineSourceLocation::new(0, "entry", 0),
&mut result,
);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::ModRmModInvalid),
"Invalid ModR/M.mod should be detected"
);
}
#[test]
fn test_sib_invalid_scale() {
let mut result = X86VerificationResult::new("test_sib_scale");
let sib = SibInfo {
scale: 0b100, index: 0b000,
base: 0b000,
};
let modrm = ModRmInfo {
mod_: 0b00,
reg: 0b000,
rm: 0b100, };
verify_sib(
&sib,
&modrm,
&RexInfo {
present: false,
w: false,
r: false,
x: false,
b: false,
},
&MachineSourceLocation::new(0, "entry", 0),
&mut result,
);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::SibScaleInvalid),
"Invalid SIB.scale should be detected"
);
}
#[test]
fn test_lock_on_unsupported_instruction() {
let mut result = X86VerificationResult::new("test_lock_unsupported");
verify_lock_prefix(
true,
X86Opcode::MOV as u32,
&MachineSourceLocation::new(0, "entry", 0),
&mut result,
);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::LockPrefixInvalidTarget),
"LOCK on MOV should be invalid"
);
}
#[test]
fn test_lock_on_supported_instruction() {
let mut result = X86VerificationResult::new("test_lock_supported");
verify_lock_prefix(
true,
X86Opcode::ADD as u32,
&MachineSourceLocation::new(0, "entry", 0),
&mut result,
);
assert!(
!result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::LockPrefixInvalidTarget),
"LOCK on ADD should be valid"
);
}
#[test]
fn test_rep_on_unsupported_instruction() {
let mut result = X86VerificationResult::new("test_rep_unsupported");
verify_rep_prefix(
true,
false,
false,
X86Opcode::MOV as u32,
&MachineSourceLocation::new(0, "entry", 0),
&mut result,
);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::RepPrefixInvalidTarget),
"REP on MOV should be invalid"
);
}
#[test]
fn test_repe_on_non_cmps_scas() {
let mut result = X86VerificationResult::new("test_repe_wrong");
verify_rep_prefix(
false,
true, false,
X86Opcode::MOVSB as u32, &MachineSourceLocation::new(0, "entry", 0),
&mut result,
);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::RepPrefixInconsistent),
"REPE on MOVSB should be invalid"
);
}
#[test]
fn test_segment_override_invalid_prefix() {
let mut result = X86VerificationResult::new("test_seg_invalid");
verify_segment_override(
Some(0xFF), X86Opcode::MOV as u32,
&MachineSourceLocation::new(0, "entry", 0),
&mut result,
);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::SegmentOverrideInvalid),
"Invalid segment override prefix should be detected"
);
}
#[test]
fn test_segment_override_on_branch() {
let mut result = X86VerificationResult::new("test_seg_branch");
verify_segment_override(
Some(0x26), X86Opcode::JMP as u32,
&MachineSourceLocation::new(0, "entry", 0),
&mut result,
);
assert!(
result
.warnings
.iter()
.any(|e| e.kind == X86VerificationErrorKind::SegmentOverrideInvalid),
"Segment override on JMP should produce warning"
);
}
#[test]
fn test_frame_setup_without_destroy() {
let mut mf = make_test_function("test_frame_only_setup");
let mut block = make_block("entry");
block.instructions.push(make_instr(X86Opcode::PUSH as u32)); block.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(block);
let result = verify_pre_ra(&mf);
assert!(
result
.warnings
.iter()
.any(|w| w.kind == X86VerificationErrorKind::FrameSetupEpilogueMismatch),
"Expected FrameSetupEpilogueMismatch warning"
);
}
#[test]
fn test_entry_block_phi_warning() {
let mut mf = make_test_function("test_entry_phi");
let mut entry = make_block("entry");
entry.instructions.push(make_instr(X86Opcode::PHI as u32)); entry.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(entry);
let result = verify_pre_ra(&mf);
assert!(
result
.warnings
.iter()
.any(|w| w.kind == X86VerificationErrorKind::PhiCriticalEdge),
"Expected PhiCriticalEdge warning for PHI in entry block"
);
}
#[test]
fn test_landing_pad_missing_eh_label() {
let mut mf = make_test_function("test_lpad");
let mut lpad = make_block("lpad");
lpad.instructions.push(make_instr(X86Opcode::MOV as u32));
lpad.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(lpad);
let result = verify_pre_ra(&mf);
assert!(
result.passed || result.error_count() == 0,
"Block not detected as landing pad should not trigger landing pad checks"
);
}
#[test]
fn test_reserved_register_usage() {
let mut mf = make_test_function("test_reserved_reg");
let mut block = make_block("entry");
let mut mov = make_instr(X86Opcode::MOV as u32);
mov.operands.push(make_phys_reg(0)); mov.operands.push(make_phys_reg(16)); block.instructions.push(mov);
block.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(block);
let result = verify_post_ra(&mf);
let has_reserved_warn = result
.warnings
.iter()
.any(|w| w.kind == X86VerificationErrorKind::PhysRegReservedViolation);
assert!(has_reserved_warn, "Expected reserved register warning");
}
#[test]
fn test_source_location_display() {
let loc = MachineSourceLocation::new(3, "loop_body", 7);
assert_eq!(format!("{}", loc), "bb.3.loop_body:%7");
let loc_with_op = loc.with_operand(2);
assert_eq!(format!("{}", loc_with_op), "bb.3.loop_body:%7.2");
}
#[test]
fn test_severity_display() {
assert_eq!(format!("{}", VerifierSeverity::Info), "INFO");
assert_eq!(format!("{}", VerifierSeverity::Warning), "WARNING");
assert_eq!(format!("{}", VerifierSeverity::Error), "ERROR");
assert_eq!(format!("{}", VerifierSeverity::Fatal), "FATAL");
}
#[test]
fn test_phase_display() {
assert_eq!(format!("{}", VerificationPhase::PreRA), "Pre-RA");
assert_eq!(format!("{}", VerificationPhase::PostRA), "Post-RA");
}
#[test]
fn test_format_verification_report() {
let mut result = X86VerificationResult::new("my_function");
result.blocks_verified = 3;
result.instructions_verified = 15;
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::MissingTerminator,
VerifierSeverity::Error,
"block lacks terminator",
MachineSourceLocation::new(1, "body", 0),
));
result.add_error(X86VerificationError::new(
X86VerificationErrorKind::RegClassMismatch,
VerifierSeverity::Warning,
"register class mismatch",
MachineSourceLocation::new(0, "entry", 2),
));
let report = format_verification_report(&result);
assert!(report.contains("FAILED"));
assert!(report.contains("my_function"));
assert!(report.contains("Errors: 1"));
assert!(report.contains("Warnings: 1"));
assert!(report.contains("Missing termin"));
assert!(report.contains("RegClassMismatch"));
}
#[test]
fn test_default_verifier() {
let verifier = X86MachineVerifier::default();
assert_eq!(verifier.phase, VerificationPhase::PreRA);
assert!(verifier.is_64bit);
assert!(!verifier.is_windows);
}
#[test]
fn test_pass_construction() {
let pass = X86MachineVerifierPass::default();
assert_eq!(pass.phase, VerificationPhase::PreRA);
assert!(pass.enabled);
assert!(pass.is_64bit);
let pass32 = X86MachineVerifierPass::default().with_32bit();
assert!(!pass32.is_64bit);
let pass_win = X86MachineVerifierPass::default().with_windows_abi();
assert!(pass_win.is_windows);
let pass_named = X86MachineVerifierPass::default().with_name("Custom Verifier");
assert_eq!(pass_named.pass_name, "Custom Verifier");
let pass_id = X86MachineVerifierPass::default().with_id(0x9999);
assert_eq!(pass_id.pass_id, 0x9999);
}
#[test]
fn test_convenience_functions() {
let mut mf = make_test_function("test_conv");
let mut block = make_block("entry");
block.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(block);
let result_pre = verify_pre_ra(&mf);
assert!(result_pre.passed);
let result_post = verify_post_ra(&mf);
assert!(result_post.passed);
let result_strict = verify_strict(&mf, VerificationPhase::PreRA);
assert!(result_strict.passed);
let result_relaxed = verify_relaxed(&mf, VerificationPhase::PreRA);
assert!(result_relaxed.passed);
}
#[test]
fn test_error_with_context() {
let err = X86VerificationError::new(
X86VerificationErrorKind::InternalError,
VerifierSeverity::Fatal,
"something went wrong",
MachineSourceLocation::new(0, "main", 5),
)
.with_context("context line 1")
.with_context("context line 2");
assert_eq!(err.context.len(), 2);
assert!(err.is_fatal());
assert!(err.is_error_or_fatal());
let display = format!("{}", err);
assert!(display.contains("context line 1"));
assert!(display.contains("context line 2"));
}
#[test]
fn test_pre_ra_and_post_ra_combined() {
let mut mf = make_test_function("test_combined");
let mut block = make_block("entry");
let mut mov = make_instr(X86Opcode::MOV as u32);
mov.operands.push(make_reg(0)); mov.operands.push(make_reg(1)); block.instructions.push(mov);
block.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(block);
let pre_result = verify_pre_ra(&mf);
let post_result = verify_post_ra(&mf);
assert!(
!post_result.passed,
"Post-RA should fail with virtual regs remaining"
);
let has_virt_err = post_result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::PostRaVirtRegRemaining);
assert!(has_virt_err);
}
#[test]
fn test_error_kind_display() {
assert_eq!(
format!("{}", X86VerificationErrorKind::SsaMultipleDef),
"SSA multiple definition"
);
assert_eq!(
format!("{}", X86VerificationErrorKind::LockPrefixInvalidTarget),
"LOCK prefix invalid target"
);
assert_eq!(
format!("{}", X86VerificationErrorKind::RexPrefixMissing),
"REX prefix missing"
);
assert_eq!(
format!("{}", X86VerificationErrorKind::VexPrefixInvalid),
"VEX prefix invalid"
);
assert_eq!(
format!("{}", X86VerificationErrorKind::EvexSaeInvalid),
"EVEX SAE invalid"
);
}
#[test]
fn test_pass_summary_counts() {
let mut pass = X86MachineVerifierPass::new_pre_ra(VerifierStrictness::default());
let mut mf1 = make_test_function("valid_func");
let mut block1 = make_block("entry");
block1.instructions.push(make_instr(X86Opcode::RET as u32));
mf1.blocks.push(block1);
pass.run_on_function(&mf1);
let mut mf2 = make_test_function("bad_func");
let mut block2 = make_block("entry");
block2.instructions.push(make_instr(X86Opcode::ADD as u32)); mf2.blocks.push(block2);
pass.run_on_function(&mf2);
assert_eq!(pass.results().len(), 2);
assert!(pass.has_errors());
assert!(pass.total_errors() > 0);
}
#[test]
fn test_clear_results() {
let mut pass = X86MachineVerifierPass::new_pre_ra(VerifierStrictness::default());
let mut mf = make_test_function("test_func");
let mut block = make_block("entry");
block.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(block);
pass.run_on_function(&mf);
assert_eq!(pass.results().len(), 1);
pass.clear_results();
assert_eq!(pass.results().len(), 0);
}
#[test]
fn test_encoding_form_legacy() {
assert_eq!(
determine_encoding_form(X86Opcode::ADD as u32, false),
X86EncodingForm::Legacy
);
assert_eq!(
determine_encoding_form(X86Opcode::MOV as u32, false),
X86EncodingForm::Legacy
);
}
#[test]
fn test_encoding_form_legacy_rex() {
assert_eq!(
determine_encoding_form(X86Opcode::ADD as u32, true),
X86EncodingForm::LegacyRex
);
}
#[test]
fn test_encoding_form_vex() {
assert_eq!(
determine_encoding_form(X86Opcode::VADDPS as u32, false),
X86EncodingForm::Vex3Byte
);
}
#[test]
fn test_encoding_form_evex() {
assert_eq!(
determine_encoding_form(X86Opcode::VADDPDZ as u32, false),
X86EncodingForm::Evex
);
}
#[test]
fn test_encoding_form_display() {
assert_eq!(format!("{}", X86EncodingForm::Legacy), "Legacy");
assert_eq!(format!("{}", X86EncodingForm::LegacyRex), "Legacy+REX");
assert_eq!(format!("{}", X86EncodingForm::Vex2Byte), "VEX.2B");
assert_eq!(format!("{}", X86EncodingForm::Vex3Byte), "VEX.3B");
assert_eq!(format!("{}", X86EncodingForm::Evex), "EVEX");
assert_eq!(format!("{}", X86EncodingForm::Xop), "XOP");
}
#[test]
fn test_modrm_register_direct() {
let modrm = ModRmInfo {
mod_: 0b11,
reg: 0,
rm: 0,
};
assert_eq!(
classify_modrm_address_mode(&modrm),
ModRmAddressMode::RegisterDirect
);
}
#[test]
fn test_modrm_indirect() {
let modrm = ModRmInfo {
mod_: 0b00,
reg: 0,
rm: 0b000,
};
assert_eq!(
classify_modrm_address_mode(&modrm),
ModRmAddressMode::Indirect
);
}
#[test]
fn test_modrm_rip_relative() {
let modrm = ModRmInfo {
mod_: 0b00,
reg: 0,
rm: 0b101,
};
assert_eq!(
classify_modrm_address_mode(&modrm),
ModRmAddressMode::RipRelativeOrDisp32
);
}
#[test]
fn test_modrm_sib_indirect() {
let modrm = ModRmInfo {
mod_: 0b00,
reg: 0,
rm: 0b100,
};
assert_eq!(
classify_modrm_address_mode(&modrm),
ModRmAddressMode::SibIndirect
);
}
#[test]
fn test_modrm_disp8() {
let modrm = ModRmInfo {
mod_: 0b01,
reg: 0,
rm: 0b000,
};
assert_eq!(
classify_modrm_address_mode(&modrm),
ModRmAddressMode::IndirectDisp8
);
}
#[test]
fn test_modrm_sib_disp32() {
let modrm = ModRmInfo {
mod_: 0b10,
reg: 0,
rm: 0b100,
};
assert_eq!(
classify_modrm_address_mode(&modrm),
ModRmAddressMode::SibDisp32
);
}
#[test]
fn test_modrm_disp_sizes() {
assert_eq!(ModRmAddressMode::Indirect.disp_size(), 0);
assert_eq!(ModRmAddressMode::IndirectDisp8.disp_size(), 1);
assert_eq!(ModRmAddressMode::IndirectDisp32.disp_size(), 4);
assert_eq!(ModRmAddressMode::SibIndirect.disp_size(), 0);
assert_eq!(ModRmAddressMode::SibDisp8.disp_size(), 1);
assert_eq!(ModRmAddressMode::SibDisp32.disp_size(), 4);
assert_eq!(ModRmAddressMode::RegisterDirect.disp_size(), 0);
}
#[test]
fn test_modrm_is_memory() {
assert!(ModRmAddressMode::Indirect.is_memory());
assert!(ModRmAddressMode::IndirectDisp8.is_memory());
assert!(ModRmAddressMode::IndirectDisp32.is_memory());
assert!(ModRmAddressMode::SibIndirect.is_memory());
assert!(ModRmAddressMode::SibDisp8.is_memory());
assert!(ModRmAddressMode::SibDisp32.is_memory());
assert!(!ModRmAddressMode::RegisterDirect.is_memory());
}
#[test]
fn test_modrm_requires_sib() {
assert!(ModRmAddressMode::SibIndirect.requires_sib());
assert!(ModRmAddressMode::SibDisp8.requires_sib());
assert!(ModRmAddressMode::SibDisp32.requires_sib());
assert!(!ModRmAddressMode::Indirect.requires_sib());
assert!(!ModRmAddressMode::RegisterDirect.requires_sib());
}
#[test]
fn test_vex_lig_instructions() {
assert_eq!(
classify_vex_l(X86Opcode::VADDSS as u32),
VexEncodingClass::LIG
);
assert_eq!(
classify_vex_l(X86Opcode::VMOVSS as u32),
VexEncodingClass::LIG
);
assert_eq!(
classify_vex_l(X86Opcode::VMOVSD as u32),
VexEncodingClass::LIG
);
}
#[test]
fn test_vex_l1_instructions() {
assert_eq!(
classify_vex_l(X86Opcode::VADDPS as u32),
VexEncodingClass::L1
);
assert_eq!(
classify_vex_l(X86Opcode::VMULPS as u32),
VexEncodingClass::L1
);
}
#[test]
fn test_evex_vector_length() {
assert_eq!(
EvexVectorLength::from_lll(0),
Some(EvexVectorLength::Xmm128)
);
assert_eq!(
EvexVectorLength::from_lll(1),
Some(EvexVectorLength::Ymm256)
);
assert_eq!(
EvexVectorLength::from_lll(2),
Some(EvexVectorLength::Zmm512)
);
assert_eq!(EvexVectorLength::from_lll(3), None);
}
#[test]
fn test_evex_bit_widths() {
assert_eq!(EvexVectorLength::Xmm128.bit_width(), 128);
assert_eq!(EvexVectorLength::Ymm256.bit_width(), 256);
assert_eq!(EvexVectorLength::Zmm512.bit_width(), 512);
}
#[test]
fn test_call_implicit_usage() {
let usage = get_implicit_reg_usage(X86Opcode::CALL as u32);
assert!(usage.implicit_uses.contains(&4)); assert!(usage.implicit_defs.contains(&4)); assert!(usage.implicit_clobbers.contains(&0)); assert!(usage.implicit_clobbers.contains(&1)); assert!(usage.implicit_clobbers.contains(&2)); assert!(usage.implicit_clobbers.contains(&8)); assert!(usage.implicit_clobbers.contains(&9)); assert!(usage.implicit_clobbers.contains(&10)); assert!(usage.implicit_clobbers.contains(&11)); }
#[test]
fn test_push_pop_implicit_usage() {
let push_usage = get_implicit_reg_usage(X86Opcode::PUSH as u32);
assert!(push_usage.implicit_uses.contains(&4));
assert!(push_usage.implicit_defs.contains(&4));
let pop_usage = get_implicit_reg_usage(X86Opcode::POP as u32);
assert!(pop_usage.implicit_uses.contains(&4));
assert!(pop_usage.implicit_defs.contains(&4));
}
#[test]
fn test_mul_div_implicit_usage() {
let mul_usage = get_implicit_reg_usage(X86Opcode::MUL as u32);
assert!(mul_usage.implicit_uses.contains(&0)); assert!(mul_usage.implicit_defs.contains(&0)); assert!(mul_usage.implicit_defs.contains(&2));
let div_usage = get_implicit_reg_usage(X86Opcode::DIV as u32);
assert!(div_usage.implicit_uses.contains(&0)); assert!(div_usage.implicit_uses.contains(&2)); assert!(div_usage.implicit_defs.contains(&0)); assert!(div_usage.implicit_defs.contains(&2)); }
#[test]
fn test_has_tied_operands() {
assert!(has_tied_operands(X86Opcode::ADD as u32));
assert!(has_tied_operands(X86Opcode::SUB as u32));
assert!(has_tied_operands(X86Opcode::AND as u32));
assert!(has_tied_operands(X86Opcode::OR as u32));
assert!(has_tied_operands(X86Opcode::XOR as u32));
assert!(has_tied_operands(X86Opcode::SHL as u32));
assert!(has_tied_operands(X86Opcode::SHR as u32));
assert!(has_tied_operands(X86Opcode::NEG as u32));
assert!(has_tied_operands(X86Opcode::NOT as u32));
assert!(has_tied_operands(X86Opcode::INC as u32));
assert!(has_tied_operands(X86Opcode::DEC as u32));
assert!(!has_tied_operands(X86Opcode::MOV as u32));
assert!(!has_tied_operands(X86Opcode::CMP as u32));
assert!(!has_tied_operands(X86Opcode::LEA as u32));
}
#[test]
fn test_tied_operand_violation_detected() {
let mut mf = make_test_function("test_tied_violation");
let mut block = make_block("entry");
let mut add = make_instr(X86Opcode::ADD as u32);
add.operands.push(make_phys_reg(0)); add.operands.push(make_phys_reg(1)); block.instructions.push(add);
block.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(block);
let mut result = X86VerificationResult::new("test_tied");
verify_tied_operands(
&mf.blocks[0].instructions[0],
&MachineSourceLocation::new(0, "entry", 0),
&mut result,
);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::RegClassMismatch),
"Tied operand violation should be detected"
);
}
#[test]
fn test_lea_constraints() {
let constraints = get_operand_constraints(X86Opcode::LEA as u32, 0);
assert!(constraints.contains(&X86OperandConstraint::MustBeGpr));
let constraints1 = get_operand_constraints(X86Opcode::LEA as u32, 1);
assert!(constraints1.contains(&X86OperandConstraint::MustBeMemory));
}
#[test]
fn test_shift_cl_constraints() {
let constraints = get_operand_constraints(X86Opcode::SHL as u32, 1);
assert!(constraints.contains(&X86OperandConstraint::MustBeRegister));
}
#[test]
fn test_enter_constraints() {
let constraints = get_operand_constraints(X86Opcode::ENTER as u32, 1);
assert!(constraints.contains(&X86OperandConstraint::ImmRange(0, 31)));
}
#[test]
fn test_dead_def_detection() {
let mut defs = HashMap::new();
let uses: HashMap<u32, Vec<MachineSourceLocation>> = HashMap::new();
defs.insert(42, MachineSourceLocation::new(0, "entry", 1));
defs.insert(43, MachineSourceLocation::new(0, "entry", 2));
let mut uses_with = HashMap::new();
uses_with.insert(42, vec![MachineSourceLocation::new(0, "entry", 3)]);
let dead = detect_dead_definitions(&defs, &uses_with);
assert_eq!(dead.len(), 1);
assert_eq!(dead[0].0, 43); }
#[test]
fn test_phys_reg_liveness_basic() {
let mut mf = make_test_function("test_liveness");
let mut block = make_block("entry");
let mut mov = make_instr(X86Opcode::MOV as u32);
mov.operands.push(make_phys_reg(0)); mov.operands.push(make_phys_reg(1)); mov.def = Some(0);
block.instructions.push(mov);
let mut add = make_instr(X86Opcode::ADD as u32);
add.operands.push(make_phys_reg(0));
add.operands.push(make_phys_reg(2));
block.instructions.push(add);
block.instructions.push(make_instr(X86Opcode::RET as u32));
mf.blocks.push(block);
let mut result = X86VerificationResult::new("test_liveness");
verify_phys_reg_liveness(&mf.blocks, &mut result);
assert!(
result.errors.iter().all(|e| !e.is_fatal()),
"Liveness verification should not produce fatal errors for valid code"
);
}
#[test]
fn test_gc_pointer_state() {
let mut gc = GcPointerState::new();
assert!(!gc.is_gc_pointer(5));
gc.mark_gc_pointer(5);
assert!(gc.is_gc_pointer(5));
gc.clear_gc_pointer(5);
assert!(!gc.is_gc_pointer(5));
}
#[test]
fn test_gc_pointer_disabled() {
let gc = GcPointerState::new();
let mut result = X86VerificationResult::new("test_gc");
gc.verify_gc_store(
0,
0x1000,
&MachineSourceLocation::new(0, "entry", 0),
&mut result,
);
assert!(result.passed);
}
#[test]
fn test_sib_index_esp_constraint() {
let mut result = X86VerificationResult::new("test_sib_esp");
let sib = SibInfo {
scale: 0b01, index: 0b100, base: 0b000,
};
let modrm = ModRmInfo {
mod_: 0b00,
reg: 0b000,
rm: 0b100,
};
verify_modrm_addressing(
&modrm,
X86Opcode::MOV as u32,
0,
true,
Some(&sib),
&MachineSourceLocation::new(0, "entry", 0),
&mut result,
);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::SibEspRspConstraint),
"ESP/RSP as scaled index should be detected"
);
}
#[test]
fn test_sib_base5_disp32_requirement() {
let mut result = X86VerificationResult::new("test_sib_base5");
let sib = SibInfo {
scale: 0b00,
index: 0b100, base: 0b101, };
let modrm = ModRmInfo {
mod_: 0b00,
reg: 0b000,
rm: 0b100, };
verify_modrm_addressing(
&modrm,
X86Opcode::MOV as u32,
0, true,
Some(&sib),
&MachineSourceLocation::new(0, "entry", 0),
&mut result,
);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::ModRmDispSizeMismatch),
"Missing disp32 for base=5 mod=00 should be detected"
);
}
#[test]
fn test_vex_requires_avx() {
let mut result = X86VerificationResult::new("test_no_avx");
verify_encoding_form(
X86Opcode::VADDPS as u32,
X86EncodingForm::Vex3Byte,
false, false,
true,
&MachineSourceLocation::new(0, "entry", 0),
&mut result,
);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::FeatureNotAvailable),
"VEX without AVX should be detected"
);
}
#[test]
fn test_evex_requires_avx512() {
let mut result = X86VerificationResult::new("test_no_avx512");
verify_encoding_form(
X86Opcode::VADDPDZ as u32,
X86EncodingForm::Evex,
true, false, true,
&MachineSourceLocation::new(0, "entry", 0),
&mut result,
);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::FeatureNotAvailable),
"EVEX without AVX-512 should be detected"
);
}
#[test]
fn test_rex_requires_64bit() {
let mut result = X86VerificationResult::new("test_rex_32");
verify_encoding_form(
X86Opcode::ADD as u32,
X86EncodingForm::LegacyRex,
false,
false,
false, &MachineSourceLocation::new(0, "entry", 0),
&mut result,
);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::RexPrefixInvalid),
"REX in 32-bit mode should be detected"
);
}
#[test]
fn test_vex_lig_with_l_bit_set() {
let mut result = X86VerificationResult::new("test_vex_lig");
let vex = VexInfo {
present: true,
is_3byte: true,
r: false,
x: false,
b: false,
mmmm: 0b00001,
w: false,
vvvv: 0b0000,
l: true, pp: 0b00,
};
verify_vex_encoding_constraints(
X86Opcode::VADDSS as u32, &vex,
&MachineSourceLocation::new(0, "entry", 0),
&mut result,
);
assert!(
result
.warnings
.iter()
.any(|w| w.kind == X86VerificationErrorKind::VexLBitInvalid),
"VEX.L=1 on LIG instruction should produce warning"
);
}
#[test]
fn test_modrm_disp_size_mismatch() {
let mut result = X86VerificationResult::new("test_modrm_disp");
let modrm = ModRmInfo {
mod_: 0b01, reg: 0b000,
rm: 0b000,
};
verify_modrm_addressing(
&modrm,
X86Opcode::MOV as u32,
4, false,
None,
&MachineSourceLocation::new(0, "entry", 0),
&mut result,
);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::ModRmDispSizeMismatch),
"disp size mismatch should be detected"
);
}
#[test]
fn test_unnecessary_sib_warning() {
let mut result = X86VerificationResult::new("test_sib_unnecessary");
let modrm = ModRmInfo {
mod_: 0b00,
reg: 0b000,
rm: 0b000, };
verify_modrm_addressing(
&modrm,
X86Opcode::MOV as u32,
0,
true, None,
&MachineSourceLocation::new(0, "entry", 0),
&mut result,
);
assert!(
result
.warnings
.iter()
.any(|w| w.kind == X86VerificationErrorKind::SibByteInvalid),
"Unnecessary SIB should produce warning"
);
}
#[test]
fn test_empty_entry_block_error() {
let mf = MachineFunction {
name: "empty_entry".to_string(),
blocks: vec![MachineBasicBlock {
id: 0,
name: "entry".to_string(),
instructions: vec![],
successors: vec![],
predecessors: vec![],
is_entry: true,
}],
virt_reg_counter: 0,
};
let mut result = X86VerificationResult::new("empty_entry");
let verifier = X86MachineVerifier::new_pre_ra(true, VerifierStrictness::default());
let mut block_result = X86VerificationResult::new("empty_entry");
verifier.verify_entry_block(&mf.blocks[0], &mut block_result);
assert!(
block_result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::MissingTerminator),
"Empty entry block should trigger MissingTerminator"
);
}
#[test]
fn test_missing_sib_error() {
let mut result = X86VerificationResult::new("test_missing_sib");
let modrm = ModRmInfo {
mod_: 0b00,
reg: 0b000,
rm: 0b100, };
verify_modrm_addressing(
&modrm,
X86Opcode::MOV as u32,
0,
false, None,
&MachineSourceLocation::new(0, "entry", 0),
&mut result,
);
assert!(
result
.errors
.iter()
.any(|e| e.kind == X86VerificationErrorKind::SibByteInvalid),
"Missing SIB should be detected"
);
}
}