use crate::ir::{BlockCall, Value, ValueList}; use alloc::boxed::Box; use alloc::vec::Vec; use smallvec::SmallVec; use std::cell::Cell; pub use super::MachLabel; use super::RetPair; pub use crate::ir::{condcodes::CondCode, *}; pub use crate::isa::{unwind::UnwindInst, TargetIsa}; pub use crate::machinst::{ ABIArg, ABIArgSlot, ABIMachineSpec, CallSite, InputSourceInst, Lower, LowerBackend, RealReg, Reg, RelocDistance, Sig, VCodeInst, Writable, }; pub use crate::settings::{StackSwitchModel, TlsModel}; pub type Unit = (); pub type ValueSlice = (ValueList, usize); pub type ValueArray2 = [Value; 2]; pub type ValueArray3 = [Value; 3]; pub type BlockArray2 = [BlockCall; 2]; pub type WritableReg = Writable; pub type VecRetPair = Vec; pub type VecMask = Vec; pub type ValueRegs = crate::machinst::ValueRegs; pub type WritableValueRegs = crate::machinst::ValueRegs; pub type InstOutput = SmallVec<[ValueRegs; 2]>; pub type InstOutputBuilder = Cell; pub type BoxExternalName = Box; pub type Range = (usize, usize); pub type MachLabelSlice = [MachLabel]; pub type BoxVecMachLabel = Box>; pub enum RangeView { Empty, NonEmpty { index: usize, rest: Range }, } /// Helper macro to define methods in `prelude.isle` within `impl Context for /// ...` for each backend. These methods are shared amongst all backends. #[macro_export] #[doc(hidden)] macro_rules! isle_lower_prelude_methods { () => { crate::isle_lower_prelude_methods!(MInst); }; ($inst:ty) => { crate::isle_common_prelude_methods!(); #[inline] fn value_type(&mut self, val: Value) -> Type { self.lower_ctx.dfg().value_type(val) } #[inline] fn value_reg(&mut self, reg: Reg) -> ValueRegs { ValueRegs::one(reg) } #[inline] fn value_regs(&mut self, r1: Reg, r2: Reg) -> ValueRegs { ValueRegs::two(r1, r2) } #[inline] fn writable_value_regs(&mut self, r1: WritableReg, r2: WritableReg) -> WritableValueRegs { WritableValueRegs::two(r1, r2) } #[inline] fn writable_value_reg(&mut self, r: WritableReg) -> WritableValueRegs { WritableValueRegs::one(r) } #[inline] fn value_regs_invalid(&mut self) -> ValueRegs { ValueRegs::invalid() } #[inline] fn output_none(&mut self) -> InstOutput { smallvec::smallvec![] } #[inline] fn output(&mut self, regs: ValueRegs) -> InstOutput { smallvec::smallvec![regs] } #[inline] fn output_pair(&mut self, r1: ValueRegs, r2: ValueRegs) -> InstOutput { smallvec::smallvec![r1, r2] } #[inline] fn output_builder_new(&mut self) -> InstOutputBuilder { std::cell::Cell::new(InstOutput::new()) } #[inline] fn output_builder_push(&mut self, builder: &InstOutputBuilder, regs: ValueRegs) -> Unit { let mut vec = builder.take(); vec.push(regs); builder.set(vec); } #[inline] fn output_builder_finish(&mut self, builder: &InstOutputBuilder) -> InstOutput { builder.take() } #[inline] fn temp_writable_reg(&mut self, ty: Type) -> WritableReg { let value_regs = self.lower_ctx.alloc_tmp(ty); value_regs.only_reg().unwrap() } #[inline] fn is_valid_reg(&mut self, reg: Reg) -> bool { use crate::machinst::valueregs::InvalidSentinel; !reg.is_invalid_sentinel() } #[inline] fn invalid_reg(&mut self) -> Reg { use crate::machinst::valueregs::InvalidSentinel; Reg::invalid_sentinel() } #[inline] fn mark_value_used(&mut self, val: Value) { self.lower_ctx.increment_lowered_uses(val); } #[inline] fn put_in_reg(&mut self, val: Value) -> Reg { self.put_in_regs(val).only_reg().unwrap() } #[inline] fn put_in_regs(&mut self, val: Value) -> ValueRegs { self.lower_ctx.put_value_in_regs(val) } #[inline] fn ensure_in_vreg(&mut self, reg: Reg, ty: Type) -> Reg { self.lower_ctx.ensure_in_vreg(reg, ty) } #[inline] fn value_regs_get(&mut self, regs: ValueRegs, i: usize) -> Reg { regs.regs()[i] } #[inline] fn value_regs_len(&mut self, regs: ValueRegs) -> usize { regs.regs().len() } #[inline] fn value_list_slice(&mut self, list: ValueList) -> ValueSlice { (list, 0) } #[inline] fn value_slice_empty(&mut self, slice: ValueSlice) -> Option<()> { let (list, off) = slice; if off >= list.len(&self.lower_ctx.dfg().value_lists) { Some(()) } else { None } } #[inline] fn value_slice_unwrap(&mut self, slice: ValueSlice) -> Option<(Value, ValueSlice)> { let (list, off) = slice; if let Some(val) = list.get(off, &self.lower_ctx.dfg().value_lists) { Some((val, (list, off + 1))) } else { None } } #[inline] fn value_slice_len(&mut self, slice: ValueSlice) -> usize { let (list, off) = slice; list.len(&self.lower_ctx.dfg().value_lists) - off } #[inline] fn value_slice_get(&mut self, slice: ValueSlice, idx: usize) -> Value { let (list, off) = slice; list.get(off + idx, &self.lower_ctx.dfg().value_lists) .unwrap() } #[inline] fn writable_reg_to_reg(&mut self, r: WritableReg) -> Reg { r.to_reg() } #[inline] fn inst_results(&mut self, inst: Inst) -> ValueSlice { (self.lower_ctx.dfg().inst_results_list(inst), 0) } #[inline] fn first_result(&mut self, inst: Inst) -> Option { self.lower_ctx.dfg().inst_results(inst).first().copied() } #[inline] fn inst_data(&mut self, inst: Inst) -> InstructionData { self.lower_ctx.dfg().insts[inst] } #[inline] fn def_inst(&mut self, val: Value) -> Option { self.lower_ctx.dfg().value_def(val).inst() } #[inline] fn i64_from_iconst(&mut self, val: Value) -> Option { let inst = self.def_inst(val)?; let constant = match self.lower_ctx.data(inst) { InstructionData::UnaryImm { opcode: Opcode::Iconst, imm, } => imm.bits(), _ => return None, }; let ty = self.lower_ctx.output_ty(inst, 0); let shift_amt = std::cmp::max(0, 64 - self.ty_bits(ty)); Some((constant << shift_amt) >> shift_amt) } fn i32_from_iconst(&mut self, val: Value) -> Option { self.i64_from_iconst(val)?.try_into().ok() } fn zero_value(&mut self, value: Value) -> Option { let insn = self.def_inst(value); if insn.is_some() { let insn = insn.unwrap(); let inst_data = self.lower_ctx.data(insn); match inst_data { InstructionData::Unary { opcode: Opcode::Splat, arg, } => { let arg = arg.clone(); return self.zero_value(arg); } InstructionData::UnaryConst { opcode: Opcode::Vconst | Opcode::F128const, constant_handle, } => { let constant_data = self.lower_ctx.get_constant_data(*constant_handle).clone(); if constant_data.into_vec().iter().any(|&x| x != 0) { return None; } else { return Some(value); } } InstructionData::UnaryImm { imm, .. } => { if imm.bits() == 0 { return Some(value); } else { return None; } } InstructionData::UnaryIeee16 { imm, .. } => { if imm.bits() == 0 { return Some(value); } else { return None; } } InstructionData::UnaryIeee32 { imm, .. } => { if imm.bits() == 0 { return Some(value); } else { return None; } } InstructionData::UnaryIeee64 { imm, .. } => { if imm.bits() == 0 { return Some(value); } else { return None; } } _ => None, } } else { None } } #[inline] fn tls_model(&mut self, _: Type) -> TlsModel { self.backend.flags().tls_model() } #[inline] fn tls_model_is_elf_gd(&mut self) -> Option<()> { if self.backend.flags().tls_model() == TlsModel::ElfGd { Some(()) } else { None } } #[inline] fn tls_model_is_macho(&mut self) -> Option<()> { if self.backend.flags().tls_model() == TlsModel::Macho { Some(()) } else { None } } #[inline] fn tls_model_is_coff(&mut self) -> Option<()> { if self.backend.flags().tls_model() == TlsModel::Coff { Some(()) } else { None } } #[inline] fn preserve_frame_pointers(&mut self) -> Option<()> { if self.backend.flags().preserve_frame_pointers() { Some(()) } else { None } } #[inline] fn stack_switch_model(&mut self) -> Option { Some(self.backend.flags().stack_switch_model()) } #[inline] fn func_ref_data(&mut self, func_ref: FuncRef) -> (SigRef, ExternalName, RelocDistance) { let funcdata = &self.lower_ctx.dfg().ext_funcs[func_ref]; let reloc_distance = if funcdata.colocated { RelocDistance::Near } else { RelocDistance::Far }; (funcdata.signature, funcdata.name.clone(), reloc_distance) } #[inline] fn box_external_name(&mut self, extname: ExternalName) -> BoxExternalName { Box::new(extname) } #[inline] fn symbol_value_data( &mut self, global_value: GlobalValue, ) -> Option<(ExternalName, RelocDistance, i64)> { let (name, reloc, offset) = self.lower_ctx.symbol_value_data(global_value)?; Some((name.clone(), reloc, offset)) } #[inline] fn reloc_distance_near(&mut self, dist: RelocDistance) -> Option<()> { if dist == RelocDistance::Near { Some(()) } else { None } } #[inline] fn u128_from_immediate(&mut self, imm: Immediate) -> Option { let bytes = self.lower_ctx.get_immediate_data(imm).as_slice(); Some(u128::from_le_bytes(bytes.try_into().ok()?)) } #[inline] fn vconst_from_immediate(&mut self, imm: Immediate) -> Option { Some(self.lower_ctx.use_constant(VCodeConstantData::Generated( self.lower_ctx.get_immediate_data(imm).clone(), ))) } #[inline] fn vec_mask_from_immediate(&mut self, imm: Immediate) -> Option { let data = self.lower_ctx.get_immediate_data(imm); if data.len() == 16 { Some(Vec::from(data.as_slice())) } else { None } } #[inline] fn u64_from_constant(&mut self, constant: Constant) -> Option { let bytes = self.lower_ctx.get_constant_data(constant).as_slice(); Some(u64::from_le_bytes(bytes.try_into().ok()?)) } #[inline] fn u128_from_constant(&mut self, constant: Constant) -> Option { let bytes = self.lower_ctx.get_constant_data(constant).as_slice(); Some(u128::from_le_bytes(bytes.try_into().ok()?)) } #[inline] fn emit_u64_le_const(&mut self, value: u64) -> VCodeConstant { let data = VCodeConstantData::U64(value.to_le_bytes()); self.lower_ctx.use_constant(data) } #[inline] fn emit_u128_le_const(&mut self, value: u128) -> VCodeConstant { let data = VCodeConstantData::Generated(value.to_le_bytes().as_slice().into()); self.lower_ctx.use_constant(data) } #[inline] fn const_to_vconst(&mut self, constant: Constant) -> VCodeConstant { self.lower_ctx.use_constant(VCodeConstantData::Pool( constant, self.lower_ctx.get_constant_data(constant).clone(), )) } fn only_writable_reg(&mut self, regs: WritableValueRegs) -> Option { regs.only_reg() } fn writable_regs_get(&mut self, regs: WritableValueRegs, idx: usize) -> WritableReg { regs.regs()[idx] } fn abi_num_args(&mut self, abi: Sig) -> usize { self.lower_ctx.sigs().num_args(abi) } fn abi_get_arg(&mut self, abi: Sig, idx: usize) -> ABIArg { self.lower_ctx.sigs().get_arg(abi, idx) } fn abi_num_rets(&mut self, abi: Sig) -> usize { self.lower_ctx.sigs().num_rets(abi) } fn abi_get_ret(&mut self, abi: Sig, idx: usize) -> ABIArg { self.lower_ctx.sigs().get_ret(abi, idx) } fn abi_ret_arg(&mut self, abi: Sig) -> Option { self.lower_ctx.sigs().get_ret_arg(abi) } fn abi_no_ret_arg(&mut self, abi: Sig) -> Option<()> { if let Some(_) = self.lower_ctx.sigs().get_ret_arg(abi) { None } else { Some(()) } } fn abi_arg_only_slot(&mut self, arg: &ABIArg) -> Option { match arg { &ABIArg::Slots { ref slots, .. } => { if slots.len() == 1 { Some(slots[0]) } else { None } } _ => None, } } fn abi_arg_implicit_pointer(&mut self, arg: &ABIArg) -> Option<(ABIArgSlot, i64, Type)> { match arg { &ABIArg::ImplicitPtrArg { pointer, offset, ty, .. } => Some((pointer, offset, ty)), _ => None, } } fn abi_unwrap_ret_area_ptr(&mut self) -> Reg { self.lower_ctx.abi().ret_area_ptr().unwrap() } fn abi_stackslot_addr( &mut self, dst: WritableReg, stack_slot: StackSlot, offset: Offset32, ) -> MInst { let offset = u32::try_from(i32::from(offset)).unwrap(); self.lower_ctx .abi() .sized_stackslot_addr(stack_slot, offset, dst) .into() } fn abi_dynamic_stackslot_addr( &mut self, dst: WritableReg, stack_slot: DynamicStackSlot, ) -> MInst { assert!(self .lower_ctx .abi() .dynamic_stackslot_offsets() .is_valid(stack_slot)); self.lower_ctx .abi() .dynamic_stackslot_addr(stack_slot, dst) .into() } fn real_reg_to_reg(&mut self, reg: RealReg) -> Reg { Reg::from(reg) } fn real_reg_to_writable_reg(&mut self, reg: RealReg) -> WritableReg { Writable::from_reg(Reg::from(reg)) } fn is_sinkable_inst(&mut self, val: Value) -> Option { let input = self.lower_ctx.get_value_as_source_or_const(val); if let InputSourceInst::UniqueUse(inst, _) = input.inst { Some(inst) } else { None } } #[inline] fn sink_inst(&mut self, inst: Inst) { self.lower_ctx.sink_inst(inst); } #[inline] fn maybe_uextend(&mut self, value: Value) -> Option { if let Some(def_inst) = self.def_inst(value) { if let InstructionData::Unary { opcode: Opcode::Uextend, arg, } = self.lower_ctx.data(def_inst) { return Some(*arg); } } Some(value) } #[inline] fn uimm8(&mut self, x: Imm64) -> Option { let x64: i64 = x.into(); let x8: u8 = x64.try_into().ok()?; Some(x8) } #[inline] fn preg_to_reg(&mut self, preg: PReg) -> Reg { preg.into() } #[inline] fn gen_move(&mut self, ty: Type, dst: WritableReg, src: Reg) -> MInst { <$inst>::gen_move(dst, src, ty).into() } /// Generate the return instruction. fn gen_return(&mut self, (list, off): ValueSlice) { let rets = (off..list.len(&self.lower_ctx.dfg().value_lists)) .map(|ix| { let val = list.get(ix, &self.lower_ctx.dfg().value_lists).unwrap(); self.put_in_regs(val) }) .collect(); self.lower_ctx.gen_return(rets); } /// Same as `shuffle32_from_imm`, but for 64-bit lane shuffles. fn shuffle64_from_imm(&mut self, imm: Immediate) -> Option<(u8, u8)> { use crate::machinst::isle::shuffle_imm_as_le_lane_idx; let bytes = self.lower_ctx.get_immediate_data(imm).as_slice(); Some(( shuffle_imm_as_le_lane_idx(8, &bytes[0..8])?, shuffle_imm_as_le_lane_idx(8, &bytes[8..16])?, )) } /// Attempts to interpret the shuffle immediate `imm` as a shuffle of /// 32-bit lanes, returning four integers, each of which is less than 8, /// which represents a permutation of 32-bit lanes as specified by /// `imm`. /// /// For example the shuffle immediate /// /// `0 1 2 3 8 9 10 11 16 17 18 19 24 25 26 27` /// /// would return `Some((0, 2, 4, 6))`. fn shuffle32_from_imm(&mut self, imm: Immediate) -> Option<(u8, u8, u8, u8)> { use crate::machinst::isle::shuffle_imm_as_le_lane_idx; let bytes = self.lower_ctx.get_immediate_data(imm).as_slice(); Some(( shuffle_imm_as_le_lane_idx(4, &bytes[0..4])?, shuffle_imm_as_le_lane_idx(4, &bytes[4..8])?, shuffle_imm_as_le_lane_idx(4, &bytes[8..12])?, shuffle_imm_as_le_lane_idx(4, &bytes[12..16])?, )) } /// Same as `shuffle32_from_imm`, but for 16-bit lane shuffles. fn shuffle16_from_imm( &mut self, imm: Immediate, ) -> Option<(u8, u8, u8, u8, u8, u8, u8, u8)> { use crate::machinst::isle::shuffle_imm_as_le_lane_idx; let bytes = self.lower_ctx.get_immediate_data(imm).as_slice(); Some(( shuffle_imm_as_le_lane_idx(2, &bytes[0..2])?, shuffle_imm_as_le_lane_idx(2, &bytes[2..4])?, shuffle_imm_as_le_lane_idx(2, &bytes[4..6])?, shuffle_imm_as_le_lane_idx(2, &bytes[6..8])?, shuffle_imm_as_le_lane_idx(2, &bytes[8..10])?, shuffle_imm_as_le_lane_idx(2, &bytes[10..12])?, shuffle_imm_as_le_lane_idx(2, &bytes[12..14])?, shuffle_imm_as_le_lane_idx(2, &bytes[14..16])?, )) } fn safe_divisor_from_imm64(&mut self, ty: Type, val: Imm64) -> Option { let minus_one = if ty.bytes() == 8 { -1 } else { (1 << (ty.bytes() * 8)) - 1 }; let bits = val.bits() & minus_one; if bits == 0 || bits == minus_one { None } else { Some(bits as u64) } } fn single_target(&mut self, targets: &MachLabelSlice) -> Option { if targets.len() == 1 { Some(targets[0]) } else { None } } fn two_targets(&mut self, targets: &MachLabelSlice) -> Option<(MachLabel, MachLabel)> { if targets.len() == 2 { Some((targets[0], targets[1])) } else { None } } fn jump_table_targets( &mut self, targets: &MachLabelSlice, ) -> Option<(MachLabel, BoxVecMachLabel)> { use std::boxed::Box; if targets.is_empty() { return None; } let default_label = targets[0]; let jt_targets = Box::new(targets[1..].to_vec()); Some((default_label, jt_targets)) } fn jump_table_size(&mut self, targets: &BoxVecMachLabel) -> u32 { targets.len() as u32 } fn add_range_fact(&mut self, reg: Reg, bits: u16, min: u64, max: u64) -> Reg { self.lower_ctx.add_range_fact(reg, bits, min, max); reg } fn value_is_unused(&mut self, val: Value) -> bool { self.lower_ctx.value_is_unused(val) } }; } /// Returns the `size`-byte lane referred to by the shuffle immediate specified /// in `bytes`. /// /// This helper is used by `shuffleNN_from_imm` above and is used to interpret a /// byte-based shuffle as a higher-level shuffle of bigger lanes. This will see /// if the `bytes` specified, which must have `size` length, specifies a lane in /// vectors aligned to a `size`-byte boundary. /// /// Returns `None` if `bytes` doesn't specify a `size`-byte lane aligned /// appropriately, or returns `Some(n)` where `n` is the index of the lane being /// shuffled. pub fn shuffle_imm_as_le_lane_idx(size: u8, bytes: &[u8]) -> Option { assert_eq!(bytes.len(), usize::from(size)); // The first index in `bytes` must be aligned to a `size` boundary for the // bytes to be a valid specifier for a lane of `size` bytes. if bytes[0] % size != 0 { return None; } // Afterwards the bytes must all be one larger than the prior to specify a // contiguous sequence of bytes that's being shuffled. Basically `bytes` // must refer to the entire `size`-byte lane, in little-endian order. for i in 0..size - 1 { let idx = usize::from(i); if bytes[idx] + 1 != bytes[idx + 1] { return None; } } // All of the `bytes` are in-order, meaning that this is a valid shuffle // immediate to specify a lane of `size` bytes. The index, when viewed as // `size`-byte immediates, will be the first byte divided by the byte size. Some(bytes[0] / size) } /// Helpers specifically for machines that use `abi::CallSite`. #[macro_export] #[doc(hidden)] macro_rules! isle_prelude_caller_methods { ($abicaller:ty) => { fn gen_call( &mut self, sig_ref: SigRef, extname: ExternalName, dist: RelocDistance, args @ (inputs, off): ValueSlice, ) -> InstOutput { let caller_conv = self.lower_ctx.abi().call_conv(self.lower_ctx.sigs()); let sig = &self.lower_ctx.dfg().signatures[sig_ref]; let num_rets = sig.returns.len(); let caller = <$abicaller>::from_func( self.lower_ctx.sigs(), sig_ref, &extname, IsTailCall::No, dist, caller_conv, self.backend.flags().clone(), ); assert_eq!( inputs.len(&self.lower_ctx.dfg().value_lists) - off, sig.params.len() ); crate::machinst::isle::gen_call_common(&mut self.lower_ctx, num_rets, caller, args) } fn gen_call_indirect( &mut self, sig_ref: SigRef, val: Value, args @ (inputs, off): ValueSlice, ) -> InstOutput { let caller_conv = self.lower_ctx.abi().call_conv(self.lower_ctx.sigs()); let ptr = self.put_in_reg(val); let sig = &self.lower_ctx.dfg().signatures[sig_ref]; let num_rets = sig.returns.len(); let caller = <$abicaller>::from_ptr( self.lower_ctx.sigs(), sig_ref, ptr, IsTailCall::No, caller_conv, self.backend.flags().clone(), ); assert_eq!( inputs.len(&self.lower_ctx.dfg().value_lists) - off, sig.params.len() ); crate::machinst::isle::gen_call_common(&mut self.lower_ctx, num_rets, caller, args) } fn gen_return_call( &mut self, callee_sig: SigRef, callee: ExternalName, distance: RelocDistance, args: ValueSlice, ) -> InstOutput { let caller_conv = isa::CallConv::Tail; debug_assert_eq!( self.lower_ctx.abi().call_conv(self.lower_ctx.sigs()), caller_conv, "Can only do `return_call`s from within a `tail` calling convention function" ); let call_site = <$abicaller>::from_func( self.lower_ctx.sigs(), callee_sig, &callee, IsTailCall::Yes, distance, caller_conv, self.backend.flags().clone(), ); call_site.emit_return_call(self.lower_ctx, args, self.backend); InstOutput::new() } fn gen_return_call_indirect( &mut self, callee_sig: SigRef, callee: Value, args: ValueSlice, ) -> InstOutput { let caller_conv = isa::CallConv::Tail; debug_assert_eq!( self.lower_ctx.abi().call_conv(self.lower_ctx.sigs()), caller_conv, "Can only do `return_call`s from within a `tail` calling convention function" ); let callee = self.put_in_reg(callee); let call_site = <$abicaller>::from_ptr( self.lower_ctx.sigs(), callee_sig, callee, IsTailCall::Yes, caller_conv, self.backend.flags().clone(), ); call_site.emit_return_call(self.lower_ctx, args, self.backend); InstOutput::new() } }; } fn gen_call_common_args( ctx: &mut Lower<'_, M::I>, call_site: &mut CallSite, (inputs, off): ValueSlice, ) { let num_args = call_site.num_args(ctx.sigs()); assert_eq!(inputs.len(&ctx.dfg().value_lists) - off, num_args); let mut arg_regs = vec![]; for i in 0..num_args { let input = inputs.get(off + i, &ctx.dfg().value_lists).unwrap(); arg_regs.push(ctx.put_value_in_regs(input)); } for (i, arg_regs) in arg_regs.iter().enumerate() { call_site.emit_copy_regs_to_buffer(ctx, i, *arg_regs); } for (i, arg_regs) in arg_regs.iter().enumerate() { call_site.gen_arg(ctx, i, *arg_regs); } } pub fn gen_call_common( ctx: &mut Lower<'_, M::I>, num_rets: usize, mut caller: CallSite, args: ValueSlice, ) -> InstOutput { gen_call_common_args(ctx, &mut caller, args); // Handle retvals prior to emitting call, so the // constraints are on the call instruction; but buffer the // instructions till after the call. let mut outputs = InstOutput::new(); let mut retval_insts = crate::machinst::abi::SmallInstVec::new(); // We take the *last* `num_rets` returns of the sig: // this skips a StructReturn, if any, that is present. let sigdata_num_rets = caller.num_rets(ctx.sigs()); debug_assert!(num_rets <= sigdata_num_rets); for i in (sigdata_num_rets - num_rets)..sigdata_num_rets { let (retval_inst, retval_regs) = caller.gen_retval(ctx, i); retval_insts.extend(retval_inst.into_iter()); outputs.push(retval_regs); } caller.emit_call(ctx); for inst in retval_insts { ctx.emit(inst); } outputs } /// This structure is used to implement the ISLE-generated `Context` trait and /// internally has a temporary reference to a machinst `LowerCtx`. pub(crate) struct IsleContext<'a, 'b, I, B> where I: VCodeInst, B: LowerBackend, { pub lower_ctx: &'a mut Lower<'b, I>, pub backend: &'a B, }