Path: blob/main/cranelift/codegen/src/isa/aarch64/pcc.rs
1693 views
//! Proof-carrying code checking for AArch64 VCode.12use crate::ir::MemFlags;3use crate::ir::pcc::*;4use crate::ir::types::*;5use crate::isa::aarch64::inst::Inst;6use crate::isa::aarch64::inst::args::{Cond, PairAMode, ShiftOp};7use crate::isa::aarch64::inst::regs::zero_reg;8use crate::isa::aarch64::inst::{ALUOp, MoveWideOp};9use crate::isa::aarch64::inst::{AMode, ExtendOp};10use crate::machinst::Reg;11use crate::machinst::pcc::*;12use crate::machinst::{InsnIndex, VCode};13use crate::trace;1415fn extend_fact(ctx: &FactContext, value: &Fact, mode: ExtendOp) -> Option<Fact> {16match mode {17ExtendOp::UXTB => ctx.uextend(value, 8, 64),18ExtendOp::UXTH => ctx.uextend(value, 16, 64),19ExtendOp::UXTW => ctx.uextend(value, 32, 64),20ExtendOp::UXTX => Some(value.clone()),21ExtendOp::SXTB => ctx.sextend(value, 8, 64),22ExtendOp::SXTH => ctx.sextend(value, 16, 64),23ExtendOp::SXTW => ctx.sextend(value, 32, 64),24ExtendOp::SXTX => None,25}26}2728/// Flow-state between facts.29#[derive(Clone, Debug, Default)]30pub struct FactFlowState {31cmp_flags: Option<(Fact, Fact)>,32}3334pub(crate) fn check(35ctx: &FactContext,36vcode: &mut VCode<Inst>,37inst_idx: InsnIndex,38state: &mut FactFlowState,39) -> PccResult<()> {40let inst = &vcode[inst_idx];41trace!("Checking facts on inst: {:?}", inst);4243// We only persist flag state for one instruction, because we44// can't exhaustively enumerate all flags-effecting ops; so take45// the `cmp_state` here and perhaps use it below but don't let it46// remain.47let cmp_flags = state.cmp_flags.take();48trace!(" * with cmp_flags = {cmp_flags:?}");4950match *inst {51Inst::Args { .. } => {52// Defs on the args have "axiomatic facts": we trust the53// ABI code to pass through the values unharmed, so the54// facts given to us in the CLIF should still be true.55Ok(())56}57Inst::ULoad8 { rd, ref mem, flags }58| Inst::SLoad8 { rd, ref mem, flags }59| Inst::ULoad16 { rd, ref mem, flags }60| Inst::SLoad16 { rd, ref mem, flags }61| Inst::ULoad32 { rd, ref mem, flags }62| Inst::SLoad32 { rd, ref mem, flags }63| Inst::ULoad64 { rd, ref mem, flags } => {64let access_ty = inst.mem_type().unwrap();65check_load(ctx, Some(rd.to_reg()), flags, mem, vcode, access_ty)66}67Inst::FpuLoad16 { ref mem, flags, .. }68| Inst::FpuLoad32 { ref mem, flags, .. }69| Inst::FpuLoad64 { ref mem, flags, .. }70| Inst::FpuLoad128 { ref mem, flags, .. } => {71let access_ty = inst.mem_type().unwrap();72check_load(ctx, None, flags, mem, vcode, access_ty)73}74Inst::LoadP64 { ref mem, flags, .. } => check_load_pair(ctx, flags, mem, vcode, 16),75Inst::FpuLoadP64 { ref mem, flags, .. } => check_load_pair(ctx, flags, mem, vcode, 16),76Inst::FpuLoadP128 { ref mem, flags, .. } => check_load_pair(ctx, flags, mem, vcode, 32),77Inst::VecLoadReplicate {78rn, flags, size, ..79} => check_load_addr(ctx, flags, rn, vcode, size.lane_size().ty()),80Inst::LoadAcquire {81access_ty,82rn,83flags,84..85} => check_load_addr(ctx, flags, rn, vcode, access_ty),8687Inst::Store8 { rd, ref mem, flags }88| Inst::Store16 { rd, ref mem, flags }89| Inst::Store32 { rd, ref mem, flags }90| Inst::Store64 { rd, ref mem, flags } => {91let access_ty = inst.mem_type().unwrap();92check_store(ctx, Some(rd), flags, mem, vcode, access_ty)93}94Inst::FpuStore16 { ref mem, flags, .. }95| Inst::FpuStore32 { ref mem, flags, .. }96| Inst::FpuStore64 { ref mem, flags, .. }97| Inst::FpuStore128 { ref mem, flags, .. } => {98let access_ty = inst.mem_type().unwrap();99check_store(ctx, None, flags, mem, vcode, access_ty)100}101Inst::StoreP64 { ref mem, flags, .. } => check_store_pair(ctx, flags, mem, vcode, 16),102Inst::FpuStoreP64 { ref mem, flags, .. } => check_store_pair(ctx, flags, mem, vcode, 16),103Inst::FpuStoreP128 { ref mem, flags, .. } => check_store_pair(ctx, flags, mem, vcode, 32),104Inst::StoreRelease {105access_ty,106rn,107flags,108..109} => check_store_addr(ctx, flags, rn, vcode, access_ty),110111Inst::AluRRR {112alu_op: ALUOp::Add | ALUOp::AddS,113size,114rd,115rn,116rm,117} => check_binop(ctx, vcode, 64, rd, rn, rm, |rn, rm| {118clamp_range(119ctx,12064,121size.bits().into(),122ctx.add(rn, rm, size.bits().into()),123)124}),125Inst::AluRRImm12 {126alu_op: ALUOp::Add | ALUOp::AddS,127size,128rd,129rn,130imm12,131} => check_unop(ctx, vcode, 64, rd, rn, |rn| {132let imm12: i64 = imm12.value().into();133clamp_range(134ctx,13564,136size.bits().into(),137ctx.offset(&rn, size.bits().into(), imm12),138)139}),140Inst::AluRRImm12 {141alu_op: ALUOp::Sub,142size,143rd,144rn,145imm12,146} => check_unop(ctx, vcode, 64, rd, rn, |rn| {147let imm12: i64 = imm12.value().into();148clamp_range(149ctx,15064,151size.bits().into(),152ctx.offset(&rn, size.bits().into(), -imm12),153)154}),155Inst::AluRRR {156alu_op: ALUOp::Sub,157size,158rd,159rn,160rm,161} => check_binop(ctx, vcode, 64, rd, rn, rm, |rn, rm| {162if let Some(k) = rm.as_const(64) {163clamp_range(164ctx,16564,166size.bits().into(),167ctx.offset(rn, size.bits().into(), -(k as i64)),168)169} else {170clamp_range(ctx, 64, size.bits().into(), None)171}172}),173Inst::AluRRRShift {174alu_op: ALUOp::Add | ALUOp::AddS,175size,176rd,177rn,178rm,179shiftop,180} if shiftop.op() == ShiftOp::LSL && has_fact(vcode, rn) && has_fact(vcode, rm) => {181check_binop(ctx, vcode, 64, rd, rn, rm, |rn, rm| {182let rm_shifted = fail_if_missing(ctx.shl(183&rm,184size.bits().into(),185shiftop.amt().value().into(),186))?;187clamp_range(188ctx,18964,190size.bits().into(),191ctx.add(&rn, &rm_shifted, size.bits().into()),192)193})194}195Inst::AluRRRExtend {196alu_op: ALUOp::Add | ALUOp::AddS,197size,198rd,199rn,200rm,201extendop,202} if has_fact(vcode, rn) && has_fact(vcode, rm) => {203check_binop(ctx, vcode, 64, rd, rn, rm, |rn, rm| {204let rm_extended = fail_if_missing(extend_fact(ctx, rm, extendop))?;205clamp_range(206ctx,20764,208size.bits().into(),209ctx.add(&rn, &rm_extended, size.bits().into()),210)211})212}213Inst::AluRRImmShift {214alu_op: ALUOp::Lsl,215size,216rd,217rn,218immshift,219} if has_fact(vcode, rn) => check_unop(ctx, vcode, 64, rd, rn, |rn| {220clamp_range(221ctx,22264,223size.bits().into(),224ctx.shl(&rn, size.bits().into(), immshift.value().into()),225)226}),227Inst::Extend {228rd,229rn,230signed: false,231from_bits,232to_bits,233} if has_fact(vcode, rn) => check_unop(ctx, vcode, 64, rd, rn, |rn| {234clamp_range(235ctx,23664,237to_bits.into(),238ctx.uextend(&rn, from_bits.into(), to_bits.into()),239)240}),241242Inst::AluRRR {243alu_op: ALUOp::SubS,244size,245rd,246rn,247rm,248} if rd.to_reg() == zero_reg() => {249// Compare.250let rn = get_fact_or_default(vcode, rn, size.bits().into());251let rm = get_fact_or_default(vcode, rm, size.bits().into());252state.cmp_flags = Some((rn, rm));253Ok(())254}255256Inst::AluRRImmLogic {257alu_op: ALUOp::Orr,258size,259rd,260rn,261imml,262} if rn == zero_reg() => {263let constant = imml.value();264check_constant(ctx, vcode, rd, size.bits().into(), constant)265}266267Inst::AluRRR { rd, size, .. }268| Inst::AluRRImm12 { rd, size, .. }269| Inst::AluRRRShift { rd, size, .. }270| Inst::AluRRRExtend { rd, size, .. }271| Inst::AluRRImmLogic { rd, size, .. }272| Inst::AluRRImmShift { rd, size, .. } => check_output(ctx, vcode, rd, &[], |_vcode| {273clamp_range(ctx, 64, size.bits().into(), None)274}),275276Inst::Extend {277rd,278from_bits,279to_bits,280..281} => check_output(ctx, vcode, rd, &[], |_vcode| {282clamp_range(ctx, to_bits.into(), from_bits.into(), None)283}),284285Inst::MovWide {286op: MoveWideOp::MovZ,287imm,288size: _,289rd,290} => {291let constant = u64::from(imm.bits) << (imm.shift * 16);292check_constant(ctx, vcode, rd, 64, constant)293}294295Inst::MovWide {296op: MoveWideOp::MovN,297imm,298size,299rd,300} => {301let constant = !(u64::from(imm.bits) << (imm.shift * 16)) & size.max_value();302check_constant(ctx, vcode, rd, 64, constant)303}304305Inst::MovK { rd, rn, imm, .. } => {306let input = get_fact_or_default(vcode, rn, 64);307if let Some(input_constant) = input.as_const(64) {308let mask = 0xffff << (imm.shift * 16);309let constant = u64::from(imm.bits) << (imm.shift * 16);310let constant = (input_constant & !mask) | constant;311check_constant(ctx, vcode, rd, 64, constant)312} else {313check_output(ctx, vcode, rd, &[], |_vcode| {314Ok(Some(Fact::max_range_for_width(64)))315})316}317}318319Inst::CSel { rd, cond, rn, rm }320if (cond == Cond::Hs || cond == Cond::Hi) && cmp_flags.is_some() =>321{322let (cmp_lhs, cmp_rhs) = cmp_flags.unwrap();323trace!("CSel: cmp {cond:?} ({cmp_lhs:?}, {cmp_rhs:?})");324325check_output(ctx, vcode, rd, &[], |vcode| {326// We support transitivity-based reasoning. If the327// comparison establishes that328//329// (x+K1) <= (y+K2)330//331// then on the true-side of the select we can edit the maximum332// in a DynamicMem or DynamicRange by replacing x's with y's333// with appropriate offsets -- this widens the range.334//335// Likewise, on the false-side of the select we can336// replace y's with x's -- this also widens the range. On337// the false side we know the inequality is strict, so we338// can offset by one.339340// True side: lhs >= rhs (Hs) or lhs > rhs (Hi).341let rn = get_fact_or_default(vcode, rn, 64);342let lhs_kind = match cond {343Cond::Hs => InequalityKind::Loose,344Cond::Hi => InequalityKind::Strict,345_ => unreachable!(),346};347let rn = ctx.apply_inequality(&rn, &cmp_lhs, &cmp_rhs, lhs_kind);348// false side: rhs < lhs (Hs) or rhs <= lhs (Hi).349let rm = get_fact_or_default(vcode, rm, 64);350let rhs_kind = match cond {351Cond::Hs => InequalityKind::Strict,352Cond::Hi => InequalityKind::Loose,353_ => unreachable!(),354};355let rm = ctx.apply_inequality(&rm, &cmp_rhs, &cmp_lhs, rhs_kind);356let union = ctx.union(&rn, &rm);357// Union the two facts.358clamp_range(ctx, 64, 64, union)359})360}361362_ if vcode.inst_defines_facts(inst_idx) => Err(PccError::UnsupportedFact),363364_ => Ok(()),365}366}367368fn check_load(369ctx: &FactContext,370rd: Option<Reg>,371flags: MemFlags,372addr: &AMode,373vcode: &VCode<Inst>,374ty: Type,375) -> PccResult<()> {376let result_fact = rd.and_then(|rd| vcode.vreg_fact(rd.into()));377let bits = u16::try_from(ty.bits()).unwrap();378check_addr(379ctx,380flags,381addr,382vcode,383ty,384LoadOrStore::Load {385result_fact,386from_bits: bits,387to_bits: bits,388},389)390}391392fn check_store(393ctx: &FactContext,394rd: Option<Reg>,395flags: MemFlags,396addr: &AMode,397vcode: &VCode<Inst>,398ty: Type,399) -> PccResult<()> {400let stored_fact = rd.and_then(|rd| vcode.vreg_fact(rd.into()));401check_addr(402ctx,403flags,404addr,405vcode,406ty,407LoadOrStore::Store { stored_fact },408)409}410411fn check_addr<'a>(412ctx: &FactContext,413flags: MemFlags,414addr: &AMode,415vcode: &VCode<Inst>,416ty: Type,417op: LoadOrStore<'a>,418) -> PccResult<()> {419if !flags.checked() {420return Ok(());421}422423trace!("check_addr: {:?}", addr);424425let check = |addr: &Fact, ty: Type| -> PccResult<()> {426match op {427LoadOrStore::Load {428result_fact,429from_bits,430to_bits,431} => {432let loaded_fact =433clamp_range(ctx, to_bits, from_bits, ctx.load(addr, ty)?.cloned())?;434trace!(435"checking a load: loaded_fact = {loaded_fact:?} result_fact = {result_fact:?}"436);437if ctx.subsumes_fact_optionals(loaded_fact.as_ref(), result_fact) {438Ok(())439} else {440Err(PccError::UnsupportedFact)441}442}443LoadOrStore::Store { stored_fact } => ctx.store(addr, ty, stored_fact),444}445};446447match addr {448&AMode::RegReg { rn, rm } => {449let rn = get_fact_or_default(vcode, rn, 64);450let rm = get_fact_or_default(vcode, rm, 64);451let sum = fail_if_missing(ctx.add(&rn, &rm, 64))?;452trace!("rn = {rn:?} rm = {rm:?} sum = {sum:?}");453check(&sum, ty)454}455&AMode::RegScaled { rn, rm } => {456let rn = get_fact_or_default(vcode, rn, 64);457let rm = get_fact_or_default(vcode, rm, 64);458let rm_scaled = fail_if_missing(ctx.scale(&rm, 64, ty.bytes()))?;459let sum = fail_if_missing(ctx.add(&rn, &rm_scaled, 64))?;460check(&sum, ty)461}462&AMode::RegScaledExtended { rn, rm, extendop } => {463let rn = get_fact_or_default(vcode, rn, 64);464let rm = get_fact_or_default(vcode, rm, 64);465let rm_extended = fail_if_missing(extend_fact(ctx, &rm, extendop))?;466let rm_scaled = fail_if_missing(ctx.scale(&rm_extended, 64, ty.bytes()))?;467let sum = fail_if_missing(ctx.add(&rn, &rm_scaled, 64))?;468check(&sum, ty)469}470&AMode::RegExtended { rn, rm, extendop } => {471let rn = get_fact_or_default(vcode, rn, 64);472let rm = get_fact_or_default(vcode, rm, 64);473let rm_extended = fail_if_missing(extend_fact(ctx, &rm, extendop))?;474let sum = fail_if_missing(ctx.add(&rn, &rm_extended, 64))?;475check(&sum, ty)?;476Ok(())477}478&AMode::Unscaled { rn, simm9 } => {479let rn = get_fact_or_default(vcode, rn, 64);480let sum = fail_if_missing(ctx.offset(&rn, 64, simm9.value.into()))?;481check(&sum, ty)482}483&AMode::UnsignedOffset { rn, uimm12 } => {484let rn = get_fact_or_default(vcode, rn, 64);485// N.B.: the architecture scales the immediate in the486// encoded instruction by the size of the loaded type, so487// e.g. an offset field of 4095 can mean a load of offset488// 32760 (= 4095 * 8) for I64s. The `UImm12Scaled` type489// stores the *scaled* value, so we don't need to multiply490// (again) by the type's size here.491let offset: u64 = uimm12.value().into();492// This `unwrap()` will always succeed because the value493// will always be positive and much smaller than494// `i64::MAX` (see above).495let sum = fail_if_missing(ctx.offset(&rn, 64, i64::try_from(offset).unwrap()))?;496check(&sum, ty)497}498&AMode::Label { .. } | &AMode::Const { .. } => {499// Always accept: labels and constants must be within the500// generated code (else they won't be resolved).501Ok(())502}503&AMode::RegOffset { rn, off, .. } => {504let rn = get_fact_or_default(vcode, rn, 64);505let sum = fail_if_missing(ctx.offset(&rn, 64, off))?;506check(&sum, ty)507}508&AMode::SPOffset { .. }509| &AMode::FPOffset { .. }510| &AMode::IncomingArg { .. }511| &AMode::SlotOffset { .. }512| &AMode::SPPostIndexed { .. }513| &AMode::SPPreIndexed { .. } => {514// We trust ABI code (for now!) and no lowering rules515// lower input value accesses directly to these.516Ok(())517}518}519}520521fn check_load_pair(522_ctx: &FactContext,523_flags: MemFlags,524_addr: &PairAMode,525_vcode: &VCode<Inst>,526_size: u8,527) -> PccResult<()> {528Err(PccError::UnimplementedInst)529}530531fn check_store_pair(532_ctx: &FactContext,533_flags: MemFlags,534_addr: &PairAMode,535_vcode: &VCode<Inst>,536_size: u8,537) -> PccResult<()> {538Err(PccError::UnimplementedInst)539}540541fn check_load_addr(542ctx: &FactContext,543flags: MemFlags,544reg: Reg,545vcode: &VCode<Inst>,546ty: Type,547) -> PccResult<()> {548if !flags.checked() {549return Ok(());550}551let fact = get_fact_or_default(vcode, reg, 64);552let _output_fact = ctx.load(&fact, ty)?;553Ok(())554}555556fn check_store_addr(557ctx: &FactContext,558flags: MemFlags,559reg: Reg,560vcode: &VCode<Inst>,561ty: Type,562) -> PccResult<()> {563if !flags.checked() {564return Ok(());565}566let fact = get_fact_or_default(vcode, reg, 64);567let _output_fact = ctx.store(&fact, ty, None)?;568Ok(())569}570571572