cranelift_codegen/isa/x64/
lower.rs1pub(super) mod isle;
5
6use crate::ir::{
7 Endianness, ExternalName, Inst as IRInst, InstructionData, LibCall, Opcode, Type, types,
8};
9use crate::isa::x64::abi::*;
10use crate::isa::x64::inst::args::*;
11use crate::isa::x64::inst::*;
12use crate::isa::{CallConv, x64::X64Backend};
13use crate::machinst::*;
14use crate::result::CodegenResult;
15use crate::settings::Flags;
16use alloc::boxed::Box;
17use target_lexicon::Triple;
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21struct InsnInput {
22 insn: IRInst,
23 input: usize,
24}
25
26impl Lower<'_, Inst> {
30 #[inline]
31 pub fn temp_writable_gpr(&mut self) -> WritableGpr {
32 WritableGpr::from_writable_reg(self.alloc_tmp(types::I64).only_reg().unwrap()).unwrap()
33 }
34
35 #[inline]
36 pub fn temp_writable_xmm(&mut self) -> WritableXmm {
37 WritableXmm::from_writable_reg(self.alloc_tmp(types::F64).only_reg().unwrap()).unwrap()
38 }
39}
40
41fn is_int_or_ref_ty(ty: Type) -> bool {
42 match ty {
43 types::I8 | types::I16 | types::I32 | types::I64 => true,
44 _ => false,
45 }
46}
47
48fn matches_input(ctx: &mut Lower<Inst>, input: InsnInput, op: Opcode) -> Option<IRInst> {
52 let inputs = ctx.get_input_as_source_or_const(input.insn, input.input);
53 inputs.inst.as_inst().and_then(|(src_inst, _)| {
54 let data = ctx.data(src_inst);
55 if data.opcode() == op {
56 return Some(src_inst);
57 }
58 None
59 })
60}
61
62fn put_input_in_regs(ctx: &mut Lower<Inst>, spec: InsnInput) -> ValueRegs<Reg> {
64 let ty = ctx.input_ty(spec.insn, spec.input);
65 let input = ctx.get_input_as_source_or_const(spec.insn, spec.input);
66
67 if let Some(c) = input.constant {
68 let size = if ty_bits(ty) < 64 {
70 OperandSize::Size32
71 } else {
72 OperandSize::Size64
73 };
74 assert!(is_int_or_ref_ty(ty)); let cst_copy = ctx.alloc_tmp(ty);
76 ctx.emit(Inst::imm(size, c, cst_copy.only_reg().unwrap()));
77 non_writable_value_regs(cst_copy)
78 } else {
79 ctx.put_input_in_regs(spec.insn, spec.input)
80 }
81}
82
83fn put_input_in_reg(ctx: &mut Lower<Inst>, spec: InsnInput) -> Reg {
85 put_input_in_regs(ctx, spec)
86 .only_reg()
87 .expect("Multi-register value not expected")
88}
89
90enum MergeableLoadSize {
91 Exact,
94
95 Min32,
102}
103
104fn is_mergeable_load(
109 ctx: &mut Lower<Inst>,
110 src_insn: IRInst,
111 size: MergeableLoadSize,
112) -> Option<(InsnInput, i32)> {
113 let insn_data = ctx.data(src_insn);
114 let inputs = ctx.num_inputs(src_insn);
115 if inputs != 1 {
116 return None;
117 }
118
119 let load_ty = ctx.output_ty(src_insn, 0);
121 if ty_bits(load_ty) < 32 {
122 match size {
123 MergeableLoadSize::Exact => {}
124 MergeableLoadSize::Min32 => return None,
125 }
126 }
127
128 if let Some(flags) = ctx.memflags(src_insn) {
130 if flags.explicit_endianness() == Some(Endianness::Big) {
131 return None;
132 }
133 }
134
135 if let &InstructionData::Load {
139 opcode: Opcode::Load,
140 offset,
141 ..
142 } = insn_data
143 {
144 Some((
145 InsnInput {
146 insn: src_insn,
147 input: 0,
148 },
149 offset.into(),
150 ))
151 } else {
152 None
153 }
154}
155
156fn input_to_imm(ctx: &mut Lower<Inst>, spec: InsnInput) -> Option<u64> {
157 ctx.get_input_as_source_or_const(spec.insn, spec.input)
158 .constant
159}
160
161fn emit_vm_call(
162 ctx: &mut Lower<Inst>,
163 flags: &Flags,
164 triple: &Triple,
165 libcall: LibCall,
166 inputs: &[ValueRegs<Reg>],
167) -> CodegenResult<InstOutput> {
168 let extname = ExternalName::LibCall(libcall);
169
170 let call_conv = CallConv::for_libcall(flags, CallConv::triple_default(triple));
172 let sig = libcall.signature(call_conv, types::I64);
173 let outputs = ctx.gen_call_output(&sig);
174
175 if !ctx.sigs().have_abi_sig_for_signature(&sig) {
176 ctx.sigs_mut()
177 .make_abi_sig_from_ir_signature::<X64ABIMachineSpec>(sig.clone(), flags)?;
178 }
179 let sig = ctx.sigs().abi_sig_for_signature(&sig);
180
181 let uses = ctx.gen_call_args(sig, inputs);
182 let defs = ctx.gen_call_rets(sig, &outputs);
183
184 let stack_ret_space = ctx.sigs()[sig].sized_stack_ret_space();
185 let stack_arg_space = ctx.sigs()[sig].sized_stack_arg_space();
186 ctx.abi_mut()
187 .accumulate_outgoing_args_size(stack_ret_space + stack_arg_space);
188
189 if flags.use_colocated_libcalls() {
190 let call_info = ctx.gen_call_info(sig, extname, uses, defs, None, false);
191 ctx.emit(Inst::call_known(Box::new(call_info)));
192 } else {
193 let tmp = ctx.alloc_tmp(types::I64).only_reg().unwrap();
194 ctx.emit(Inst::LoadExtName {
195 dst: tmp.map(Gpr::unwrap_new),
196 name: Box::new(extname),
197 offset: 0,
198 distance: RelocDistance::Far,
199 });
200 let call_info = ctx.gen_call_info(sig, RegMem::reg(tmp.to_reg()), uses, defs, None, false);
201 ctx.emit(Inst::call_unknown(Box::new(call_info)));
202 }
203 Ok(outputs)
204}
205
206fn matches_small_constant_shift(ctx: &mut Lower<Inst>, spec: InsnInput) -> Option<(InsnInput, u8)> {
209 matches_input(ctx, spec, Opcode::Ishl).and_then(|shift| {
210 match input_to_imm(
211 ctx,
212 InsnInput {
213 insn: shift,
214 input: 1,
215 },
216 ) {
217 Some(shift_amt) if shift_amt <= 3 => Some((
218 InsnInput {
219 insn: shift,
220 input: 0,
221 },
222 shift_amt as u8,
223 )),
224 _ => None,
225 }
226 })
227}
228
229fn lower_to_amode(ctx: &mut Lower<Inst>, spec: InsnInput, offset: i32) -> Amode {
233 let flags = ctx
234 .memflags(spec.insn)
235 .expect("Instruction with amode should have memflags");
236
237 if let Some(add) = matches_input(ctx, spec, Opcode::Iadd) {
240 let output_ty = ctx.output_ty(add, 0);
241 debug_assert_eq!(
242 output_ty,
243 types::I64,
244 "Address width of 64 expected, got {output_ty}"
245 );
246 let add_inputs = &[
247 InsnInput {
248 insn: add,
249 input: 0,
250 },
251 InsnInput {
252 insn: add,
253 input: 1,
254 },
255 ];
256
257 let (base, index, shift) = if let Some((shift_input, shift_amt)) =
260 matches_small_constant_shift(ctx, add_inputs[0])
261 {
262 (
263 put_input_in_reg(ctx, add_inputs[1]),
264 put_input_in_reg(ctx, shift_input),
265 shift_amt,
266 )
267 } else if let Some((shift_input, shift_amt)) =
268 matches_small_constant_shift(ctx, add_inputs[1])
269 {
270 (
271 put_input_in_reg(ctx, add_inputs[0]),
272 put_input_in_reg(ctx, shift_input),
273 shift_amt,
274 )
275 } else {
276 for input in 0..=1 {
277 let (inst, inst_input) = if let Some(uextend) =
279 matches_input(ctx, InsnInput { insn: add, input }, Opcode::Uextend)
280 {
281 (uextend, 0)
282 } else {
283 (add, input)
284 };
285
286 if let Some(cst) = ctx.get_input_as_source_or_const(inst, inst_input).constant {
288 let final_offset = (offset as i64).wrapping_add(cst as i64);
289 if let Ok(final_offset) = i32::try_from(final_offset) {
290 let base = put_input_in_reg(ctx, add_inputs[1 - input]);
291 return Amode::imm_reg(final_offset, base).with_flags(flags.into());
292 }
293 }
294 }
295
296 (
297 put_input_in_reg(ctx, add_inputs[0]),
298 put_input_in_reg(ctx, add_inputs[1]),
299 0,
300 )
301 };
302
303 return Amode::imm_reg_reg_shift(
304 offset,
305 Gpr::unwrap_new(base),
306 Gpr::unwrap_new(index),
307 shift,
308 )
309 .with_flags(flags.into());
310 }
311
312 let input = put_input_in_reg(ctx, spec);
313 Amode::imm_reg(offset, input).with_flags(flags.into())
314}
315
316impl LowerBackend for X64Backend {
320 type MInst = Inst;
321
322 fn lower(&self, ctx: &mut Lower<Inst>, ir_inst: IRInst) -> Option<InstOutput> {
323 isle::lower(ctx, self, ir_inst)
324 }
325
326 fn lower_branch(
327 &self,
328 ctx: &mut Lower<Inst>,
329 ir_inst: IRInst,
330 targets: &[MachLabel],
331 ) -> Option<()> {
332 isle::lower_branch(ctx, self, ir_inst, targets)
333 }
334
335 fn maybe_pinned_reg(&self) -> Option<Reg> {
336 Some(regs::pinned_reg())
337 }
338}