Path: blob/main/cranelift/codegen/src/isa/pulley_shared/inst/mod.rs
3088 views
//! This module defines Pulley-specific machine instruction types.12use core::marker::PhantomData;34use crate::binemit::{Addend, CodeOffset, Reloc};5use crate::ir::types::{self, F32, F64, I8, I8X16, I16, I32, I64, I128};6use crate::ir::{self, MemFlags, Type};7use crate::isa::FunctionAlignment;8use crate::isa::pulley_shared::abi::PulleyMachineDeps;9use crate::{CodegenError, CodegenResult, settings};10use crate::{machinst::*, trace};11use alloc::string::{String, ToString};12use alloc::vec;13use alloc::vec::Vec;14use regalloc2::RegClass;15use smallvec::SmallVec;1617pub mod regs;18pub use self::regs::*;19pub mod args;20pub use self::args::*;21pub mod emit;22pub use self::emit::*;2324//=============================================================================25// Instructions (top level): definition2627pub use crate::isa::pulley_shared::lower::isle::generated_code::MInst as Inst;28pub use crate::isa::pulley_shared::lower::isle::generated_code::RawInst;2930impl From<RawInst> for Inst {31fn from(raw: RawInst) -> Inst {32Inst::Raw { raw }33}34}3536use super::PulleyTargetKind;3738mod generated {39use super::*;40use crate::isa::pulley_shared::lower::isle::generated_code::RawInst;4142include!(concat!(env!("OUT_DIR"), "/pulley_inst_gen.rs"));43}4445/// Out-of-line data for return-calls, to keep the size of `Inst` down.46#[derive(Clone, Debug)]47pub struct ReturnCallInfo<T> {48/// Where this call is going.49pub dest: T,5051/// The size of the argument area for this return-call, potentially smaller52/// than that of the caller, but never larger.53pub new_stack_arg_size: u32,5455/// The in-register arguments and their constraints.56pub uses: CallArgList,57}5859impl Inst {60/// Generic constructor for a load (zero-extending where appropriate).61pub fn gen_load(dst: Writable<Reg>, mem: Amode, ty: Type, flags: MemFlags) -> Inst {62if ty.is_vector() {63assert_eq!(ty.bytes(), 16);64Inst::VLoad {65dst: dst.map(|r| VReg::new(r).unwrap()),66mem,67ty,68flags,69}70} else if ty.is_int() {71assert!(ty.bytes() <= 8);72Inst::XLoad {73dst: dst.map(|r| XReg::new(r).unwrap()),74mem,75ty,76flags,77}78} else {79Inst::FLoad {80dst: dst.map(|r| FReg::new(r).unwrap()),81mem,82ty,83flags,84}85}86}8788/// Generic constructor for a store.89pub fn gen_store(mem: Amode, from_reg: Reg, ty: Type, flags: MemFlags) -> Inst {90if ty.is_vector() {91assert_eq!(ty.bytes(), 16);92Inst::VStore {93mem,94src: VReg::new(from_reg).unwrap(),95ty,96flags,97}98} else if ty.is_int() {99assert!(ty.bytes() <= 8);100Inst::XStore {101mem,102src: XReg::new(from_reg).unwrap(),103ty,104flags,105}106} else {107Inst::FStore {108mem,109src: FReg::new(from_reg).unwrap(),110ty,111flags,112}113}114}115}116117fn pulley_get_operands(inst: &mut Inst, collector: &mut impl OperandVisitor) {118match inst {119Inst::Args { args } => {120for ArgPair { vreg, preg } in args {121collector.reg_fixed_def(vreg, *preg);122}123}124Inst::Rets { rets } => {125for RetPair { vreg, preg } in rets {126collector.reg_fixed_use(vreg, *preg);127}128}129130Inst::DummyUse { reg } => {131collector.reg_use(reg);132}133134Inst::Nop => {}135136Inst::TrapIf { cond, code: _ } => {137cond.get_operands(collector);138}139140Inst::GetSpecial { dst, reg } => {141collector.reg_def(dst);142// Note that this is explicitly ignored as this is only used for143// special registers that don't participate in register allocation144// such as the stack pointer, frame pointer, etc.145assert!(reg.is_special());146}147148Inst::LoadExtNameNear { dst, .. } | Inst::LoadExtNameFar { dst, .. } => {149collector.reg_def(dst);150}151152Inst::Call { info } => {153let CallInfo {154uses,155defs,156dest,157try_call_info,158clobbers,159..160} = &mut **info;161162// Pulley supports having the first few integer arguments in any163// register, so flag that with `reg_use` here.164let PulleyCall { args, .. } = dest;165for arg in args {166collector.reg_use(arg);167}168169// Remaining arguments (and return values) are all in fixed170// registers according to Pulley's ABI, however.171for CallArgPair { vreg, preg } in uses {172collector.reg_fixed_use(vreg, *preg);173}174for CallRetPair { vreg, location } in defs {175match location {176RetLocation::Reg(preg, ..) => collector.reg_fixed_def(vreg, *preg),177RetLocation::Stack(..) => collector.any_def(vreg),178}179}180collector.reg_clobbers(*clobbers);181if let Some(try_call_info) = try_call_info {182try_call_info.collect_operands(collector);183}184}185Inst::IndirectCallHost { info } => {186let CallInfo {187uses,188defs,189try_call_info,190clobbers,191..192} = &mut **info;193for CallArgPair { vreg, preg } in uses {194collector.reg_fixed_use(vreg, *preg);195}196for CallRetPair { vreg, location } in defs {197match location {198RetLocation::Reg(preg, ..) => collector.reg_fixed_def(vreg, *preg),199RetLocation::Stack(..) => collector.any_def(vreg),200}201}202collector.reg_clobbers(*clobbers);203if let Some(try_call_info) = try_call_info {204try_call_info.collect_operands(collector);205}206}207Inst::IndirectCall { info } => {208collector.reg_use(&mut info.dest);209let CallInfo {210uses,211defs,212try_call_info,213clobbers,214..215} = &mut **info;216for CallArgPair { vreg, preg } in uses {217collector.reg_fixed_use(vreg, *preg);218}219for CallRetPair { vreg, location } in defs {220match location {221RetLocation::Reg(preg, ..) => collector.reg_fixed_def(vreg, *preg),222RetLocation::Stack(..) => collector.any_def(vreg),223}224}225collector.reg_clobbers(*clobbers);226if let Some(try_call_info) = try_call_info {227try_call_info.collect_operands(collector);228}229}230Inst::ReturnCall { info } => {231for CallArgPair { vreg, preg } in &mut info.uses {232collector.reg_fixed_use(vreg, *preg);233}234}235Inst::ReturnIndirectCall { info } => {236// Use a fixed location of where to store the value to237// return-call-to. Using a fixed location prevents this register238// from being allocated to a callee-saved register which will get239// clobbered during the register restores just before the240// return-call.241//242// Also note that `x15` is specifically the last caller-saved243// register and, at this time, the only non-argument caller-saved244// register. This register allocation constraint is why it's not an245// argument register.246collector.reg_fixed_use(&mut info.dest, regs::x15());247248for CallArgPair { vreg, preg } in &mut info.uses {249collector.reg_fixed_use(vreg, *preg);250}251}252253Inst::Jump { .. } => {}254255Inst::BrIf {256cond,257taken: _,258not_taken: _,259} => {260cond.get_operands(collector);261}262263Inst::LoadAddr { dst, mem } => {264collector.reg_def(dst);265mem.get_operands(collector);266}267268Inst::XLoad {269dst,270mem,271ty: _,272flags: _,273} => {274collector.reg_def(dst);275mem.get_operands(collector);276}277278Inst::XStore {279mem,280src,281ty: _,282flags: _,283} => {284mem.get_operands(collector);285collector.reg_use(src);286}287288Inst::FLoad {289dst,290mem,291ty: _,292flags: _,293} => {294collector.reg_def(dst);295mem.get_operands(collector);296}297298Inst::FStore {299mem,300src,301ty: _,302flags: _,303} => {304mem.get_operands(collector);305collector.reg_use(src);306}307308Inst::VLoad {309dst,310mem,311ty: _,312flags: _,313} => {314collector.reg_def(dst);315mem.get_operands(collector);316}317318Inst::VStore {319mem,320src,321ty: _,322flags: _,323} => {324mem.get_operands(collector);325collector.reg_use(src);326}327328Inst::BrTable { idx, .. } => {329collector.reg_use(idx);330}331332Inst::Raw { raw } => generated::get_operands(raw, collector),333334Inst::EmitIsland { .. } => {}335336Inst::LabelAddress { dst, label: _ } => {337collector.reg_def(dst);338}339340Inst::SequencePoint { .. } => {}341}342}343344/// A newtype over a Pulley instruction that also carries a phantom type345/// parameter describing whether we are targeting 32- or 64-bit Pulley bytecode.346///347/// Implements `Deref`, `DerefMut`, and `From`/`Into` for `Inst` to allow for348/// seamless conversion between `Inst` and `InstAndKind`.349#[derive(Clone, Debug)]350pub struct InstAndKind<P>351where352P: PulleyTargetKind,353{354inst: Inst,355kind: PhantomData<P>,356}357358impl<P> From<Inst> for InstAndKind<P>359where360P: PulleyTargetKind,361{362fn from(inst: Inst) -> Self {363Self {364inst,365kind: PhantomData,366}367}368}369370impl<P> From<RawInst> for InstAndKind<P>371where372P: PulleyTargetKind,373{374fn from(inst: RawInst) -> Self {375Self {376inst: inst.into(),377kind: PhantomData,378}379}380}381382impl<P> From<InstAndKind<P>> for Inst383where384P: PulleyTargetKind,385{386fn from(inst: InstAndKind<P>) -> Self {387inst.inst388}389}390391impl<P> core::ops::Deref for InstAndKind<P>392where393P: PulleyTargetKind,394{395type Target = Inst;396397fn deref(&self) -> &Self::Target {398&self.inst399}400}401402impl<P> core::ops::DerefMut for InstAndKind<P>403where404P: PulleyTargetKind,405{406fn deref_mut(&mut self) -> &mut Self::Target {407&mut self.inst408}409}410411impl<P> MachInst for InstAndKind<P>412where413P: PulleyTargetKind,414{415type LabelUse = LabelUse;416type ABIMachineSpec = PulleyMachineDeps<P>;417418const TRAP_OPCODE: &'static [u8] = TRAP_OPCODE;419420fn gen_dummy_use(reg: Reg) -> Self {421Inst::DummyUse { reg }.into()422}423424fn canonical_type_for_rc(rc: RegClass) -> Type {425match rc {426regalloc2::RegClass::Int => I64,427regalloc2::RegClass::Float => F64,428regalloc2::RegClass::Vector => I8X16,429}430}431432fn is_safepoint(&self) -> bool {433match self.inst {434Inst::Raw {435raw: RawInst::Trap { .. },436}437| Inst::Call { .. }438| Inst::IndirectCall { .. }439| Inst::IndirectCallHost { .. } => true,440_ => false,441}442}443444fn get_operands(&mut self, collector: &mut impl OperandVisitor) {445pulley_get_operands(self, collector);446}447448fn is_move(&self) -> Option<(Writable<Reg>, Reg)> {449match self.inst {450Inst::Raw {451raw: RawInst::Xmov { dst, src },452} => Some((Writable::from_reg(*dst.to_reg()), *src)),453_ => None,454}455}456457fn is_included_in_clobbers(&self) -> bool {458!self.is_args()459}460461fn is_trap(&self) -> bool {462match self.inst {463Inst::Raw {464raw: RawInst::Trap { .. },465} => true,466_ => false,467}468}469470fn is_args(&self) -> bool {471match self.inst {472Inst::Args { .. } => true,473_ => false,474}475}476477fn is_term(&self) -> MachTerminator {478match &self.inst {479Inst::Raw {480raw: RawInst::Ret { .. },481}482| Inst::Rets { .. } => MachTerminator::Ret,483Inst::Jump { .. } => MachTerminator::Branch,484Inst::BrIf { .. } => MachTerminator::Branch,485Inst::BrTable { .. } => MachTerminator::Branch,486Inst::ReturnCall { .. } | Inst::ReturnIndirectCall { .. } => MachTerminator::RetCall,487Inst::Call { info } if info.try_call_info.is_some() => MachTerminator::Branch,488Inst::IndirectCall { info } if info.try_call_info.is_some() => MachTerminator::Branch,489Inst::IndirectCallHost { info } if info.try_call_info.is_some() => {490MachTerminator::Branch491}492_ => MachTerminator::None,493}494}495496fn is_mem_access(&self) -> bool {497todo!()498}499500fn call_type(&self) -> CallType {501match &self.inst {502Inst::Call { .. } | Inst::IndirectCall { .. } | Inst::IndirectCallHost { .. } => {503CallType::Regular504}505506Inst::ReturnCall { .. } | Inst::ReturnIndirectCall { .. } => CallType::TailCall,507508_ => CallType::None,509}510}511512fn gen_move(to_reg: Writable<Reg>, from_reg: Reg, ty: Type) -> Self {513match ty {514ir::types::I8 | ir::types::I16 | ir::types::I32 | ir::types::I64 => RawInst::Xmov {515dst: WritableXReg::try_from(to_reg).unwrap(),516src: XReg::new(from_reg).unwrap(),517}518.into(),519ir::types::F32 | ir::types::F64 => RawInst::Fmov {520dst: WritableFReg::try_from(to_reg).unwrap(),521src: FReg::new(from_reg).unwrap(),522}523.into(),524_ if ty.is_vector() => RawInst::Vmov {525dst: WritableVReg::try_from(to_reg).unwrap(),526src: VReg::new(from_reg).unwrap(),527}528.into(),529_ => panic!("don't know how to generate a move for type {ty}"),530}531}532533fn gen_nop(_preferred_size: usize) -> Self {534todo!()535}536537fn gen_nop_units() -> Vec<Vec<u8>> {538let mut bytes = vec![];539let nop = pulley_interpreter::op::Nop {};540nop.encode(&mut bytes);541// NOP needs to be a 1-byte opcode so it can be used to542// overwrite a callsite of any length.543assert_eq!(bytes.len(), 1);544vec![bytes]545}546547fn rc_for_type(ty: Type) -> CodegenResult<(&'static [RegClass], &'static [Type])> {548match ty {549I8 => Ok((&[RegClass::Int], &[I8])),550I16 => Ok((&[RegClass::Int], &[I16])),551I32 => Ok((&[RegClass::Int], &[I32])),552I64 => Ok((&[RegClass::Int], &[I64])),553F32 => Ok((&[RegClass::Float], &[F32])),554F64 => Ok((&[RegClass::Float], &[F64])),555I128 => Ok((&[RegClass::Int, RegClass::Int], &[I64, I64])),556_ if ty.is_vector() => {557debug_assert!(ty.bits() <= 512);558559// Here we only need to return a SIMD type with the same size as `ty`.560// We use these types for spills and reloads, so prefer types with lanes <= 31561// since that fits in the immediate field of `vsetivli`.562const SIMD_TYPES: [[Type; 1]; 6] = [563[types::I8X2],564[types::I8X4],565[types::I8X8],566[types::I8X16],567[types::I16X16],568[types::I32X16],569];570let idx = (ty.bytes().ilog2() - 1) as usize;571let ty = &SIMD_TYPES[idx][..];572573Ok((&[RegClass::Vector], ty))574}575_ => Err(CodegenError::Unsupported(format!(576"Unexpected SSA-value type: {ty}"577))),578}579}580581fn gen_jump(label: MachLabel) -> Self {582Inst::Jump { label }.into()583}584585fn worst_case_size() -> CodeOffset {586// `VShuffle { dst, src1, src2, imm }` is 22 bytes:587// 3-byte opcode588// dst, src1, src2589// 16-byte immediate59022591}592593fn ref_type_regclass(_settings: &settings::Flags) -> RegClass {594RegClass::Int595}596597fn function_alignment() -> FunctionAlignment {598FunctionAlignment {599minimum: 1,600preferred: 1,601}602}603}604605const TRAP_OPCODE: &'static [u8] = &[606pulley_interpreter::opcode::Opcode::ExtendedOp as u8,607((pulley_interpreter::opcode::ExtendedOpcode::Trap as u16) >> 0) as u8,608((pulley_interpreter::opcode::ExtendedOpcode::Trap as u16) >> 8) as u8,609];610611#[test]612fn test_trap_encoding() {613let mut dst = alloc::vec::Vec::new();614pulley_interpreter::encode::trap(&mut dst);615assert_eq!(dst, TRAP_OPCODE);616}617618//=============================================================================619// Pretty-printing of instructions.620621pub fn reg_name(reg: Reg) -> String {622match reg.to_real_reg() {623Some(real) => {624let n = real.hw_enc();625match (real.class(), n) {626(RegClass::Int, 63) => format!("sp"),627(RegClass::Int, 62) => format!("lr"),628(RegClass::Int, 61) => format!("fp"),629(RegClass::Int, 60) => format!("tmp0"),630(RegClass::Int, 59) => format!("tmp1"),631632(RegClass::Int, _) => format!("x{n}"),633(RegClass::Float, _) => format!("f{n}"),634(RegClass::Vector, _) => format!("v{n}"),635}636}637None => {638format!("{reg:?}")639}640}641}642643fn pretty_print_try_call(info: &TryCallInfo) -> String {644format!(645"; jump {:?}; catch [{}]",646info.continuation,647info.pretty_print_dests()648)649}650651impl Inst {652fn print_with_state<P>(&self, _state: &mut EmitState<P>) -> String653where654P: PulleyTargetKind,655{656use core::fmt::Write;657658let format_reg = |reg: Reg| -> String { reg_name(reg) };659660match self {661Inst::Args { args } => {662let mut s = "args".to_string();663for arg in args {664let preg = format_reg(arg.preg);665let def = format_reg(arg.vreg.to_reg());666write!(&mut s, " {def}={preg}").unwrap();667}668s669}670Inst::Rets { rets } => {671let mut s = "rets".to_string();672for ret in rets {673let preg = format_reg(ret.preg);674let vreg = format_reg(ret.vreg);675write!(&mut s, " {vreg}={preg}").unwrap();676}677s678}679680Inst::DummyUse { reg } => {681let reg = format_reg(*reg);682format!("dummy_use {reg}")683}684685Inst::TrapIf { cond, code } => {686format!("trap_{cond} // code = {code:?}")687}688689Inst::Nop => format!("nop"),690691Inst::GetSpecial { dst, reg } => {692let dst = format_reg(*dst.to_reg());693let reg = format_reg(**reg);694format!("xmov {dst}, {reg}")695}696697Inst::LoadExtNameNear { dst, name, offset } => {698let dst = format_reg(*dst.to_reg());699format!("{dst} = load_ext_name_near {name:?}, {offset}")700}701702Inst::LoadExtNameFar { dst, name, offset } => {703let dst = format_reg(*dst.to_reg());704format!("{dst} = load_ext_name_far {name:?}, {offset}")705}706707Inst::Call { info } => {708let try_call = info709.try_call_info710.as_ref()711.map(|tci| pretty_print_try_call(tci))712.unwrap_or_default();713format!("call {info:?}{try_call}")714}715716Inst::IndirectCall { info } => {717let callee = format_reg(*info.dest);718let try_call = info719.try_call_info720.as_ref()721.map(|tci| pretty_print_try_call(tci))722.unwrap_or_default();723format!("indirect_call {callee}, {info:?}{try_call}")724}725726Inst::ReturnCall { info } => {727format!("return_call {info:?}")728}729730Inst::ReturnIndirectCall { info } => {731let callee = format_reg(*info.dest);732format!("return_indirect_call {callee}, {info:?}")733}734735Inst::IndirectCallHost { info } => {736let try_call = info737.try_call_info738.as_ref()739.map(|tci| pretty_print_try_call(tci))740.unwrap_or_default();741format!("indirect_call_host {info:?}{try_call}")742}743744Inst::Jump { label } => format!("jump {}", label.to_string()),745746Inst::BrIf {747cond,748taken,749not_taken,750} => {751let taken = taken.to_string();752let not_taken = not_taken.to_string();753format!("br_{cond}, {taken}; jump {not_taken}")754}755756Inst::LoadAddr { dst, mem } => {757let dst = format_reg(*dst.to_reg());758let mem = mem.to_string();759format!("{dst} = load_addr {mem}")760}761762Inst::XLoad {763dst,764mem,765ty,766flags,767} => {768let dst = format_reg(*dst.to_reg());769let ty = ty.bits();770let mem = mem.to_string();771format!("{dst} = xload{ty} {mem} // flags ={flags}")772}773774Inst::XStore {775mem,776src,777ty,778flags,779} => {780let ty = ty.bits();781let mem = mem.to_string();782let src = format_reg(**src);783format!("xstore{ty} {mem}, {src} // flags = {flags}")784}785786Inst::FLoad {787dst,788mem,789ty,790flags,791} => {792let dst = format_reg(*dst.to_reg());793let ty = ty.bits();794let mem = mem.to_string();795format!("{dst} = fload{ty} {mem} // flags ={flags}")796}797798Inst::FStore {799mem,800src,801ty,802flags,803} => {804let ty = ty.bits();805let mem = mem.to_string();806let src = format_reg(**src);807format!("fstore{ty} {mem}, {src} // flags = {flags}")808}809810Inst::VLoad {811dst,812mem,813ty,814flags,815} => {816let dst = format_reg(*dst.to_reg());817let ty = ty.bits();818let mem = mem.to_string();819format!("{dst} = vload{ty} {mem} // flags ={flags}")820}821822Inst::VStore {823mem,824src,825ty,826flags,827} => {828let ty = ty.bits();829let mem = mem.to_string();830let src = format_reg(**src);831format!("vstore{ty} {mem}, {src} // flags = {flags}")832}833834Inst::BrTable {835idx,836default,837targets,838} => {839let idx = format_reg(**idx);840format!("br_table {idx} {default:?} {targets:?}")841}842Inst::Raw { raw } => generated::print(raw),843844Inst::EmitIsland { space_needed } => format!("emit_island {space_needed}"),845846Inst::LabelAddress { dst, label } => {847let dst = format_reg(dst.to_reg().to_reg());848format!("label_address {dst}, {label:?}")849}850851Inst::SequencePoint {} => {852format!("sequence_point")853}854}855}856}857858/// Different forms of label references for different instruction formats.859#[derive(Clone, Copy, Debug, PartialEq, Eq)]860pub enum LabelUse {861/// A PC-relative `jump`/`call`/etc... instruction with an `i32` relative862/// target.863///864/// The relative distance to the destination is added to the 4 bytes at the865/// label site.866PcRel,867}868869impl MachInstLabelUse for LabelUse {870/// Alignment for veneer code. Pulley instructions don't require any871/// particular alignment.872const ALIGN: CodeOffset = 1;873874/// Maximum PC-relative range (positive), inclusive.875fn max_pos_range(self) -> CodeOffset {876match self {877Self::PcRel => 0x7fff_ffff,878}879}880881/// Maximum PC-relative range (negative).882fn max_neg_range(self) -> CodeOffset {883match self {884Self::PcRel => 0x8000_0000,885}886}887888/// Size of window into code needed to do the patch.889fn patch_size(self) -> CodeOffset {890match self {891Self::PcRel => 4,892}893}894895/// Perform the patch.896fn patch(self, buffer: &mut [u8], use_offset: CodeOffset, label_offset: CodeOffset) {897let use_relative = (label_offset as i64) - (use_offset as i64);898debug_assert!(use_relative <= self.max_pos_range() as i64);899debug_assert!(use_relative >= -(self.max_neg_range() as i64));900let pc_rel = i32::try_from(use_relative).unwrap() as u32;901match self {902Self::PcRel => {903let buf: &mut [u8; 4] = buffer.try_into().unwrap();904let addend = u32::from_le_bytes(*buf);905trace!(906"patching label use @ {use_offset:#x} \907to label {label_offset:#x} via \908PC-relative offset {pc_rel:#x} \909adding in {addend:#x}"910);911let value = pc_rel.wrapping_add(addend);912*buf = value.to_le_bytes();913}914}915}916917/// Is a veneer supported for this label reference type?918fn supports_veneer(self) -> bool {919match self {920Self::PcRel => false,921}922}923924/// How large is the veneer, if supported?925fn veneer_size(self) -> CodeOffset {926match self {927Self::PcRel => 0,928}929}930931fn worst_case_veneer_size() -> CodeOffset {9320933}934935/// Generate a veneer into the buffer, given that this veneer is at `veneer_offset`, and return936/// an offset and label-use for the veneer's use of the original label.937fn generate_veneer(938self,939_buffer: &mut [u8],940_veneer_offset: CodeOffset,941) -> (CodeOffset, LabelUse) {942match self {943Self::PcRel => panic!("veneer not supported for {self:?}"),944}945}946947fn from_reloc(reloc: Reloc, addend: Addend) -> Option<LabelUse> {948match (reloc, addend) {949(Reloc::PulleyPcRel, 0) => Some(LabelUse::PcRel),950_ => None,951}952}953}954955956