//! Implementation of a standard Pulley ABI. use super::{inst::*, PulleyFlags, PulleyTargetKind}; use crate::isa::pulley_shared::{PointerWidth, PulleyBackend}; use crate::{ ir::{self, types::*, MemFlags, Signature}, isa, machinst::*, settings, CodegenResult, }; use alloc::{boxed::Box, vec::Vec}; use core::marker::PhantomData; use cranelift_bitset::ScalarBitSet; use regalloc2::{MachineEnv, PReg, PRegSet}; use smallvec::{smallvec, SmallVec}; use std::borrow::ToOwned; use std::sync::OnceLock; /// Support for the Pulley ABI from the callee side (within a function body). pub(crate) type PulleyCallee

= Callee>; /// Support for the Pulley ABI from the caller side (at a callsite). pub(crate) type PulleyABICallSite

= CallSite>; /// Pulley-specific ABI behavior. This struct just serves as an implementation /// point for the trait; it is never actually instantiated. pub struct PulleyMachineDeps

where P: PulleyTargetKind, { _phantom: PhantomData

, } impl

ABIMachineSpec for PulleyMachineDeps

where P: PulleyTargetKind, { type I = InstAndKind

; type F = PulleyFlags; /// This is the limit for the size of argument and return-value areas on the /// stack. We place a reasonable limit here to avoid integer overflow issues /// with 32-bit arithmetic: for now, 128 MB. const STACK_ARG_RET_SIZE_LIMIT: u32 = 128 * 1024 * 1024; fn word_bits() -> u32 { P::pointer_width().bits().into() } /// Return required stack alignment in bytes. fn stack_align(_call_conv: isa::CallConv) -> u32 { 16 } fn compute_arg_locs( call_conv: isa::CallConv, flags: &settings::Flags, params: &[ir::AbiParam], args_or_rets: ArgsOrRets, add_ret_area_ptr: bool, mut args: ArgsAccumulator, ) -> CodegenResult<(u32, Option)> { // NB: make sure this method stays in sync with // `cranelift_pulley::interp::Vm::call`. let x_end = 15; let f_end = 15; let v_end = 15; let mut next_x_reg = 0; let mut next_f_reg = 0; let mut next_v_reg = 0; let mut next_stack: u32 = 0; let ret_area_ptr = if add_ret_area_ptr { debug_assert_eq!(args_or_rets, ArgsOrRets::Args); next_x_reg += 1; Some(ABIArg::reg( x_reg(next_x_reg - 1).to_real_reg().unwrap(), I64, ir::ArgumentExtension::None, ir::ArgumentPurpose::Normal, )) } else { None }; for param in params { // Find the regclass(es) of the register(s) used to store a value of // this type. let (rcs, reg_tys) = Self::I::rc_for_type(param.value_type)?; let mut slots = ABIArgSlotVec::new(); for (rc, reg_ty) in rcs.iter().zip(reg_tys.iter()) { let next_reg = if (next_x_reg <= x_end) && *rc == RegClass::Int { let x = Some(x_reg(next_x_reg)); next_x_reg += 1; x } else if (next_f_reg <= f_end) && *rc == RegClass::Float { let f = Some(f_reg(next_f_reg)); next_f_reg += 1; f } else if (next_v_reg <= v_end) && *rc == RegClass::Vector { let v = Some(v_reg(next_v_reg)); next_v_reg += 1; v } else { None }; if let Some(reg) = next_reg { slots.push(ABIArgSlot::Reg { reg: reg.to_real_reg().unwrap(), ty: *reg_ty, extension: param.extension, }); } else { if args_or_rets == ArgsOrRets::Rets && !flags.enable_multi_ret_implicit_sret() { return Err(crate::CodegenError::Unsupported( "Too many return values to fit in registers. \ Use a StructReturn argument instead. (#9510)" .to_owned(), )); } // Compute size and 16-byte stack alignment happens // separately after all args. let size = reg_ty.bits() / 8; let size = std::cmp::max(size, 8); // Align. debug_assert!(size.is_power_of_two()); next_stack = align_to(next_stack, size); slots.push(ABIArgSlot::Stack { offset: i64::from(next_stack), ty: *reg_ty, extension: param.extension, }); next_stack += size; } } args.push(ABIArg::Slots { slots, purpose: param.purpose, }); } let pos = if let Some(ret_area_ptr) = ret_area_ptr { args.push_non_formal(ret_area_ptr); Some(args.args().len() - 1) } else { None }; next_stack = align_to(next_stack, Self::stack_align(call_conv)); Ok((next_stack, pos)) } fn gen_load_stack(mem: StackAMode, into_reg: Writable, ty: Type) -> Self::I { let mut flags = MemFlags::trusted(); // Stack loads/stores of vectors always use little-endianess to avoid // implementing a byte-swap of vectors on big-endian platforms. if ty.is_vector() { flags.set_endianness(ir::Endianness::Little); } Inst::gen_load(into_reg, mem.into(), ty, flags).into() } fn gen_store_stack(mem: StackAMode, from_reg: Reg, ty: Type) -> Self::I { let mut flags = MemFlags::trusted(); // Stack loads/stores of vectors always use little-endianess to avoid // implementing a byte-swap of vectors on big-endian platforms. if ty.is_vector() { flags.set_endianness(ir::Endianness::Little); } Inst::gen_store(mem.into(), from_reg, ty, flags).into() } fn gen_move(to_reg: Writable, from_reg: Reg, ty: Type) -> Self::I { Self::I::gen_move(to_reg, from_reg, ty) } fn gen_extend( dst: Writable, src: Reg, signed: bool, from_bits: u8, to_bits: u8, ) -> Self::I { assert!(from_bits < to_bits); let src = XReg::new(src).unwrap(); let dst = dst.try_into().unwrap(); match (signed, from_bits) { (true, 8) => RawInst::Sext8 { dst, src }.into(), (true, 16) => RawInst::Sext16 { dst, src }.into(), (true, 32) => RawInst::Sext32 { dst, src }.into(), (false, 8) => RawInst::Zext8 { dst, src }.into(), (false, 16) => RawInst::Zext16 { dst, src }.into(), (false, 32) => RawInst::Zext32 { dst, src }.into(), _ => unimplemented!("extend {from_bits} to {to_bits} as signed? {signed}"), } } fn get_ext_mode( _call_conv: isa::CallConv, specified: ir::ArgumentExtension, ) -> ir::ArgumentExtension { specified } fn gen_args(args: Vec) -> Self::I { Inst::Args { args }.into() } fn gen_rets(rets: Vec) -> Self::I { Inst::Rets { rets }.into() } fn get_stacklimit_reg(_call_conv: isa::CallConv) -> Reg { spilltmp_reg() } fn gen_add_imm( _call_conv: isa::CallConv, into_reg: Writable, from_reg: Reg, imm: u32, ) -> SmallInstVec { let dst = into_reg.try_into().unwrap(); let imm = imm as i32; smallvec![ RawInst::Xconst32 { dst, imm }.into(), RawInst::Xadd32 { dst, src1: from_reg.try_into().unwrap(), src2: dst.to_reg(), } .into() ] } fn gen_stack_lower_bound_trap(_limit_reg: Reg) -> SmallInstVec { unimplemented!("pulley shouldn't need stack bound checks") } fn gen_get_stack_addr(mem: StackAMode, dst: Writable) -> Self::I { let dst = dst.to_reg(); let dst = XReg::new(dst).unwrap(); let dst = WritableXReg::from_reg(dst); let mem = mem.into(); Inst::LoadAddr { dst, mem }.into() } fn gen_load_base_offset(into_reg: Writable, base: Reg, offset: i32, ty: Type) -> Self::I { let base = XReg::try_from(base).unwrap(); let mem = Amode::RegOffset { base, offset }; Inst::gen_load(into_reg, mem, ty, MemFlags::trusted()).into() } fn gen_store_base_offset(base: Reg, offset: i32, from_reg: Reg, ty: Type) -> Self::I { let base = XReg::try_from(base).unwrap(); let mem = Amode::RegOffset { base, offset }; Inst::gen_store(mem, from_reg, ty, MemFlags::trusted()).into() } fn gen_sp_reg_adjust(amount: i32) -> SmallInstVec { if amount == 0 { return smallvec![]; } let inst = if amount < 0 { let amount = amount.checked_neg().unwrap(); if let Ok(amt) = u32::try_from(amount) { RawInst::StackAlloc32 { amt } } else { unreachable!() } } else { if let Ok(amt) = u32::try_from(amount) { RawInst::StackFree32 { amt } } else { unreachable!() } }; smallvec![inst.into()] } /// Generates the entire prologue for the function. /// /// Note that this is different from other backends where it's not spread /// out among a few individual functions. That's because the goal here is to /// generate a single macro-instruction for the entire prologue in the most /// common cases and we don't want to spread the logic over multiple /// functions. /// /// The general machinst methods are split to accommodate stack checks and /// things like stack probes, all of which are empty on Pulley because /// Pulley has its own stack check mechanism. fn gen_prologue_frame_setup( _call_conv: isa::CallConv, _flags: &settings::Flags, _isa_flags: &PulleyFlags, frame_layout: &FrameLayout, ) -> SmallInstVec { let mut insts = SmallVec::new(); let incoming_args_diff = frame_layout.tail_args_size - frame_layout.incoming_args_size; if incoming_args_diff > 0 { // Decrement SP by the amount of additional incoming argument space // we need insts.extend(Self::gen_sp_reg_adjust(-(incoming_args_diff as i32))); } let style = frame_layout.pulley_frame_style(); match &style { FrameStyle::None => {} FrameStyle::PulleySetupNoClobbers => insts.push(RawInst::PushFrame.into()), FrameStyle::PulleySetupAndSaveClobbers { frame_size, saved_by_pulley, } => insts.push( RawInst::PushFrameSave { amt: *frame_size, regs: pulley_interpreter::RegSet::from_bitset(*saved_by_pulley), } .into(), ), FrameStyle::Manual { frame_size } => insts.extend(Self::gen_sp_reg_adjust( -i32::try_from(*frame_size).unwrap(), )), } for (offset, ty, reg) in frame_layout.manually_managed_clobbers(&style) { insts.push( Inst::gen_store(Amode::SpOffset { offset }, reg, ty, MemFlags::trusted()).into(), ); } insts } /// Reverse of `gen_prologue_frame_setup`. fn gen_epilogue_frame_restore( _call_conv: isa::CallConv, _flags: &settings::Flags, _isa_flags: &PulleyFlags, _frame_layout: &FrameLayout, ) -> SmallInstVec { // Note that this is intentionally empty as `gen_return` does // everything. SmallVec::new() } fn gen_return( _call_conv: isa::CallConv, _isa_flags: &PulleyFlags, frame_layout: &FrameLayout, ) -> SmallInstVec { let mut insts = SmallVec::new(); let style = frame_layout.pulley_frame_style(); // Restore clobbered registers that are manually managed in Cranelift. for (offset, ty, reg) in frame_layout.manually_managed_clobbers(&style) { insts.push( Inst::gen_load( Writable::from_reg(reg), Amode::SpOffset { offset }, ty, MemFlags::trusted(), ) .into(), ); } // Perform the inverse of `gen_prologue_frame_setup`. match &style { FrameStyle::None => {} FrameStyle::PulleySetupNoClobbers => insts.push(RawInst::PopFrame.into()), FrameStyle::PulleySetupAndSaveClobbers { frame_size, saved_by_pulley, } => insts.push( RawInst::PopFrameRestore { amt: *frame_size, regs: pulley_interpreter::RegSet::from_bitset(*saved_by_pulley), } .into(), ), FrameStyle::Manual { frame_size } => { insts.extend(Self::gen_sp_reg_adjust(i32::try_from(*frame_size).unwrap())) } } // Handle final stack adjustments for the tail-call ABI. if frame_layout.tail_args_size > 0 { insts.extend(Self::gen_sp_reg_adjust( frame_layout.tail_args_size.try_into().unwrap(), )); } // And finally, return. // // FIXME: if `frame_layout.tail_args_size` is zero this instruction // should get folded into the macro-instructions above. No need to have // all functions do `pop_frame; ret`, that could be `pop_frame_and_ret`. // Should benchmark whether this is worth it though. insts.push(RawInst::Ret {}.into()); insts } fn gen_probestack(_insts: &mut SmallInstVec, _frame_size: u32) { // Pulley doesn't implement stack probes since all stack pointer // decrements are checked already. } fn gen_clobber_save( _call_conv: isa::CallConv, _flags: &settings::Flags, _frame_layout: &FrameLayout, ) -> SmallVec<[Self::I; 16]> { // Note that this is intentionally empty because everything necessary // was already done in `gen_prologue_frame_setup`. SmallVec::new() } fn gen_clobber_restore( _call_conv: isa::CallConv, _flags: &settings::Flags, _frame_layout: &FrameLayout, ) -> SmallVec<[Self::I; 16]> { // Intentionally empty as restores happen for Pulley in `gen_return`. SmallVec::new() } fn gen_call( dest: &CallDest, _tmp: Writable, mut info: CallInfo<()>, ) -> SmallVec<[Self::I; 2]> { match dest { // "near" calls are pulley->pulley calls so they use a normal "call" // opcode CallDest::ExtName(name, RelocDistance::Near) => { // The first four integer arguments to a call can be handled via // special pulley call instructions. Assert here that // `info.uses` is sorted in order and then take out x0-x3 if // they're present and move them from `info.uses` to // `info.dest.args` to be handled differently during register // allocation. let mut args = SmallVec::new(); if cfg!(debug_assertions) { let xargs = info .uses .iter() .filter_map(|a| XReg::new(a.preg)) .collect::>(); for window in xargs.windows(2) { assert!(window[0] < window[1]); } } info.uses.retain(|arg| { if arg.preg != x0() && arg.preg != x1() && arg.preg != x2() && arg.preg != x3() { return true; } args.push(XReg::new(arg.vreg).unwrap()); false }); smallvec![Inst::Call { info: Box::new(info.map(|()| PulleyCall { name: name.clone(), args, })) } .into()] } // "far" calls are pulley->host calls so they use a different opcode // which is lowered with a special relocation in the backend. CallDest::ExtName(name, RelocDistance::Far) => smallvec![Inst::IndirectCallHost { info: Box::new(info.map(|()| name.clone())) } .into()], // Indirect calls are all assumed to be pulley->pulley calls CallDest::Reg(reg) => smallvec![Inst::IndirectCall { info: Box::new(info.map(|()| XReg::new(*reg).unwrap())) } .into()], } } fn gen_memcpy Writable>( _call_conv: isa::CallConv, _dst: Reg, _src: Reg, _size: usize, _alloc_tmp: F, ) -> SmallVec<[Self::I; 8]> { todo!() } fn get_number_of_spillslots_for_value( rc: RegClass, _target_vector_bytes: u32, _isa_flags: &PulleyFlags, ) -> u32 { // Spill slots are the size of a "word" or a pointer, but Pulley // registers are 8-byte for integers/floats regardless of pointer size. // Calculate the number of slots necessary to store 8 bytes. let slots_for_8bytes = match P::pointer_width() { PointerWidth::PointerWidth32 => 2, PointerWidth::PointerWidth64 => 1, }; match rc { // Int/float registers are 8-bytes RegClass::Int | RegClass::Float => slots_for_8bytes, // Vector registers are 16 bytes RegClass::Vector => 2 * slots_for_8bytes, } } fn get_machine_env(_flags: &settings::Flags, _call_conv: isa::CallConv) -> &MachineEnv { static MACHINE_ENV: OnceLock = OnceLock::new(); MACHINE_ENV.get_or_init(create_reg_environment) } fn get_regs_clobbered_by_call(_call_conv_of_callee: isa::CallConv) -> PRegSet { DEFAULT_CLOBBERS } fn compute_frame_layout( _call_conv: isa::CallConv, flags: &settings::Flags, _sig: &Signature, regs: &[Writable], is_leaf: bool, incoming_args_size: u32, tail_args_size: u32, fixed_frame_storage_size: u32, outgoing_args_size: u32, ) -> FrameLayout { let mut regs: Vec> = regs .iter() .cloned() .filter(|r| DEFAULT_CALLEE_SAVES.contains(r.to_reg().into())) .collect(); regs.sort_unstable(); // Compute clobber size. let clobber_size = compute_clobber_size(®s); // Compute linkage frame size. let setup_area_size = if flags.preserve_frame_pointers() || !is_leaf // The function arguments that are passed on the stack are addressed // relative to the Frame Pointer. || incoming_args_size > 0 || clobber_size > 0 || fixed_frame_storage_size > 0 { P::pointer_width().bytes() * 2 // FP, LR } else { 0 }; FrameLayout { incoming_args_size, tail_args_size, setup_area_size: setup_area_size.into(), clobber_size, fixed_frame_storage_size, outgoing_args_size, clobbered_callee_saves: regs, } } fn gen_inline_probestack( _insts: &mut SmallInstVec, _call_conv: isa::CallConv, _frame_size: u32, _guard_size: u32, ) { // Pulley doesn't need inline probestacks because it always checks stack // decrements. } } /// Different styles of management of fp/lr and clobbered registers. /// /// This helps decide, depending on Cranelift settings and frame layout, what /// macro instruction is used to setup the pulley frame. enum FrameStyle { /// No management is happening, fp/lr aren't saved by Pulley or Cranelift. /// No stack is being allocated either. None, /// No stack is being allocated and nothing is clobbered, but Pulley should /// save the fp/lr combo. PulleySetupNoClobbers, /// Pulley is managing the fp/lr combo, the stack size, and clobbered /// X-class registers. /// /// Note that `saved_by_pulley` is not the exhaustive set of clobbered /// registers. It's only those that are part of the `PushFrameSave` /// instruction. PulleySetupAndSaveClobbers { /// The size of the frame, including clobbers, that's being allocated. frame_size: u32, /// Registers that pulley is saving/restoring. saved_by_pulley: ScalarBitSet, }, /// Cranelift is manually managing everything, both clobbers and stack /// increments/decrements. /// /// Note that fp/lr are not saved in this mode. Manual { /// The size of the stack being allocated. frame_size: u32, }, } /// Pulley-specific helpers when dealing with ABI code. impl FrameLayout { /// Whether or not this frame saves fp/lr. fn setup_frame(&self) -> bool { self.setup_area_size > 0 } /// Returns the stack size allocated by this function, excluding incoming /// tail args or the optional "setup area" of fp/lr. fn stack_size(&self) -> u32 { self.clobber_size + self.fixed_frame_storage_size + self.outgoing_args_size } /// Returns the style of frame being used for this function. /// /// See `FrameStyle` for more information. fn pulley_frame_style(&self) -> FrameStyle { let saved_by_pulley = self.clobbered_xregs_saved_by_pulley(); match ( self.stack_size(), self.setup_frame(), saved_by_pulley.is_empty(), ) { // No stack allocated, not saving fp/lr, no clobbers, nothing to do (0, false, true) => FrameStyle::None, // No stack allocated, saving fp/lr, no clobbers, so this is // pulley-managed via push/pop_frame. (0, true, true) => FrameStyle::PulleySetupNoClobbers, // Some stack is being allocated and pulley is managing fp/lr. Let // pulley manage clobbered registers as well, regardless if they're // present or not. (frame_size, true, _) => FrameStyle::PulleySetupAndSaveClobbers { frame_size, saved_by_pulley, }, // Some stack is being allocated, but pulley isn't managing fp/lr, // so we're manually doing everything. (frame_size, false, true) => FrameStyle::Manual { frame_size }, // If there's no frame setup and there's clobbered registers this // technically should have already hit a case above, so panic here. (_, false, false) => unreachable!(), } } /// Returns the set of clobbered registers that Pulley is managing via its /// macro instructions rather than the generated code. fn clobbered_xregs_saved_by_pulley(&self) -> ScalarBitSet { let mut clobbered: ScalarBitSet = ScalarBitSet::new(); // Pulley only manages clobbers if it's also managing fp/lr. if !self.setup_frame() { return clobbered; } let mut found_manual_clobber = false; for reg in self.clobbered_callee_saves.iter() { let r_reg = reg.to_reg(); // Pulley can only manage clobbers of integer registers at this // time, float registers are managed manually. // // Also assert that all pulley-managed clobbers come first, // otherwise the loop below in `manually_managed_clobbers` is // incorrect. if r_reg.class() == RegClass::Int { assert!(!found_manual_clobber); clobbered.insert(r_reg.hw_enc()); } else { found_manual_clobber = true; } } clobbered } /// Returns an iterator over the clobbers that Cranelift is managing, not /// Pulley. /// /// If this frame has clobbers then they're either saved by Pulley with /// `FrameStyle::PulleySetupAndSaveClobbers`. Cranelift might need to manage /// these registers depending on Cranelift settings. Cranelift also always /// manages floating-point registers. fn manually_managed_clobbers<'a>( &'a self, style: &'a FrameStyle, ) -> impl Iterator + 'a { let mut offset = self.stack_size(); self.clobbered_callee_saves.iter().filter_map(move |reg| { // Allocate space for this clobber no matter what. If pulley is // managing this then we're just accounting for the pulley-saved // registers as well. Note that all pulley-managed registers come // first in the list here. offset -= 8; let r_reg = reg.to_reg(); let ty = match r_reg.class() { RegClass::Int => { // If this register is saved by pulley, skip this clobber. if let FrameStyle::PulleySetupAndSaveClobbers { saved_by_pulley, .. } = style { if saved_by_pulley.contains(r_reg.hw_enc()) { return None; } } I64 } RegClass::Float => F64, RegClass::Vector => unreachable!("no vector registers are callee-save"), }; let offset = i32::try_from(offset).unwrap(); Some((offset, ty, Reg::from(reg.to_reg()))) }) } } impl

PulleyABICallSite

where P: PulleyTargetKind, { pub fn emit_return_call( mut self, ctx: &mut Lower>, args: isle::ValueSlice, _backend: &PulleyBackend

, ) { let new_stack_arg_size = u32::try_from(self.sig(ctx.sigs()).sized_stack_arg_space()).unwrap(); ctx.abi_mut().accumulate_tail_args_size(new_stack_arg_size); // Put all arguments in registers and stack slots (within that newly // allocated stack space). self.emit_args(ctx, args); self.emit_stack_ret_arg_for_tail_call(ctx); let dest = self.dest().clone(); let uses = self.take_uses(); match dest { CallDest::ExtName(name, RelocDistance::Near) => { let info = Box::new(ReturnCallInfo { dest: name, uses, new_stack_arg_size, }); ctx.emit(Inst::ReturnCall { info }.into()); } CallDest::ExtName(_name, RelocDistance::Far) => { unimplemented!("return-call of a host function") } CallDest::Reg(callee) => { let info = Box::new(ReturnCallInfo { dest: XReg::new(callee).unwrap(), uses, new_stack_arg_size, }); ctx.emit(Inst::ReturnIndirectCall { info }.into()); } } } } const DEFAULT_CALLEE_SAVES: PRegSet = PRegSet::empty() // Integer registers. .with(px_reg(16)) .with(px_reg(17)) .with(px_reg(18)) .with(px_reg(19)) .with(px_reg(20)) .with(px_reg(21)) .with(px_reg(22)) .with(px_reg(23)) .with(px_reg(24)) .with(px_reg(25)) .with(px_reg(26)) .with(px_reg(27)) .with(px_reg(28)) .with(px_reg(29)) .with(px_reg(30)) .with(px_reg(31)) // Float registers. .with(pf_reg(16)) .with(pf_reg(17)) .with(pf_reg(18)) .with(pf_reg(19)) .with(pf_reg(20)) .with(pf_reg(21)) .with(pf_reg(22)) .with(pf_reg(23)) .with(pf_reg(24)) .with(pf_reg(25)) .with(pf_reg(26)) .with(pf_reg(27)) .with(pf_reg(28)) .with(pf_reg(29)) .with(pf_reg(30)) .with(pf_reg(31)) // Note: no vector registers are callee-saved. ; fn compute_clobber_size(clobbers: &[Writable]) -> u32 { let mut clobbered_size = 0; for reg in clobbers { match reg.to_reg().class() { RegClass::Int => { clobbered_size += 8; } RegClass::Float => { clobbered_size += 8; } RegClass::Vector => unimplemented!("Vector Size Clobbered"), } } align_to(clobbered_size, 16) } const DEFAULT_CLOBBERS: PRegSet = PRegSet::empty() // Integer registers: the first 16 get clobbered. .with(px_reg(0)) .with(px_reg(1)) .with(px_reg(2)) .with(px_reg(3)) .with(px_reg(4)) .with(px_reg(5)) .with(px_reg(6)) .with(px_reg(7)) .with(px_reg(8)) .with(px_reg(9)) .with(px_reg(10)) .with(px_reg(11)) .with(px_reg(12)) .with(px_reg(13)) .with(px_reg(14)) .with(px_reg(15)) // Float registers: the first 16 get clobbered. .with(pf_reg(0)) .with(pf_reg(1)) .with(pf_reg(2)) .with(pf_reg(3)) .with(pf_reg(4)) .with(pf_reg(5)) .with(pf_reg(6)) .with(pf_reg(7)) .with(pf_reg(8)) .with(pf_reg(9)) .with(pf_reg(10)) .with(pf_reg(11)) .with(pf_reg(12)) .with(pf_reg(13)) .with(pf_reg(14)) .with(pf_reg(15)) // All vector registers get clobbered. .with(pv_reg(0)) .with(pv_reg(1)) .with(pv_reg(2)) .with(pv_reg(3)) .with(pv_reg(4)) .with(pv_reg(5)) .with(pv_reg(6)) .with(pv_reg(7)) .with(pv_reg(8)) .with(pv_reg(9)) .with(pv_reg(10)) .with(pv_reg(11)) .with(pv_reg(12)) .with(pv_reg(13)) .with(pv_reg(14)) .with(pv_reg(15)) .with(pv_reg(16)) .with(pv_reg(17)) .with(pv_reg(18)) .with(pv_reg(19)) .with(pv_reg(20)) .with(pv_reg(21)) .with(pv_reg(22)) .with(pv_reg(23)) .with(pv_reg(24)) .with(pv_reg(25)) .with(pv_reg(26)) .with(pv_reg(27)) .with(pv_reg(28)) .with(pv_reg(29)) .with(pv_reg(30)) .with(pv_reg(31)); fn create_reg_environment() -> MachineEnv { // Prefer caller-saved registers over callee-saved registers, because that // way we don't need to emit code to save and restore them if we don't // mutate them. let preferred_regs_by_class: [Vec; 3] = { let x_registers: Vec = (0..16).map(|x| px_reg(x)).collect(); let f_registers: Vec = (0..16).map(|x| pf_reg(x)).collect(); let v_registers: Vec = (0..32).map(|x| pv_reg(x)).collect(); [x_registers, f_registers, v_registers] }; let non_preferred_regs_by_class: [Vec; 3] = { let x_registers: Vec = (16..XReg::SPECIAL_START) .map(|x| px_reg(x.into())) .collect(); let f_registers: Vec = (16..32).map(|x| pf_reg(x)).collect(); let v_registers: Vec = vec![]; [x_registers, f_registers, v_registers] }; MachineEnv { preferred_regs_by_class, non_preferred_regs_by_class, fixed_stack_slots: vec![], scratch_by_class: [None, None, None], } }