1 //! Lowering rules for X64.
2 
3 // ISLE integration glue.
4 pub(super) mod isle;
5 
6 use crate::ir::pcc::{FactContext, PccResult};
7 use crate::ir::{
8     Endianness, ExternalName, Inst as IRInst, InstructionData, LibCall, Opcode, Type, types,
9 };
10 use crate::isa::x64::abi::*;
11 use crate::isa::x64::inst::args::*;
12 use crate::isa::x64::inst::*;
13 use crate::isa::x64::pcc;
14 use crate::isa::{CallConv, x64::X64Backend};
15 use crate::machinst::lower::*;
16 use crate::machinst::*;
17 use crate::result::CodegenResult;
18 use crate::settings::Flags;
19 use std::boxed::Box;
20 use target_lexicon::Triple;
21 
22 /// Identifier for a particular input of an instruction.
23 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
24 struct InsnInput {
25     insn: IRInst,
26     input: usize,
27 }
28 
29 //=============================================================================
30 // Helpers for instruction lowering.
31 
32 impl Lower<'_, Inst> {
33     #[inline]
34     pub fn temp_writable_gpr(&mut self) -> WritableGpr {
35         WritableGpr::from_writable_reg(self.alloc_tmp(types::I64).only_reg().unwrap()).unwrap()
36     }
37 
38     #[inline]
39     pub fn temp_writable_xmm(&mut self) -> WritableXmm {
40         WritableXmm::from_writable_reg(self.alloc_tmp(types::F64).only_reg().unwrap()).unwrap()
41     }
42 }
43 
44 fn is_int_or_ref_ty(ty: Type) -> bool {
45     match ty {
46         types::I8 | types::I16 | types::I32 | types::I64 => true,
47         _ => false,
48     }
49 }
50 
51 /// Returns whether the given specified `input` is a result produced by an instruction with Opcode
52 /// `op`.
53 // TODO investigate failures with checking against the result index.
54 fn matches_input(ctx: &mut Lower<Inst>, input: InsnInput, op: Opcode) -> Option<IRInst> {
55     let inputs = ctx.get_input_as_source_or_const(input.insn, input.input);
56     inputs.inst.as_inst().and_then(|(src_inst, _)| {
57         let data = ctx.data(src_inst);
58         if data.opcode() == op {
59             return Some(src_inst);
60         }
61         None
62     })
63 }
64 
65 /// Put the given input into possibly multiple registers, and mark it as used (side-effect).
66 fn put_input_in_regs(ctx: &mut Lower<Inst>, spec: InsnInput) -> ValueRegs<Reg> {
67     let ty = ctx.input_ty(spec.insn, spec.input);
68     let input = ctx.get_input_as_source_or_const(spec.insn, spec.input);
69 
70     if let Some(c) = input.constant {
71         // Generate constants fresh at each use to minimize long-range register pressure.
72         let size = if ty_bits(ty) < 64 {
73             OperandSize::Size32
74         } else {
75             OperandSize::Size64
76         };
77         assert!(is_int_or_ref_ty(ty)); // Only used for addresses.
78         let cst_copy = ctx.alloc_tmp(ty);
79         ctx.emit(Inst::imm(size, c, cst_copy.only_reg().unwrap()));
80         non_writable_value_regs(cst_copy)
81     } else {
82         ctx.put_input_in_regs(spec.insn, spec.input)
83     }
84 }
85 
86 /// Put the given input into a register, and mark it as used (side-effect).
87 fn put_input_in_reg(ctx: &mut Lower<Inst>, spec: InsnInput) -> Reg {
88     put_input_in_regs(ctx, spec)
89         .only_reg()
90         .expect("Multi-register value not expected")
91 }
92 
93 enum MergeableLoadSize {
94     /// The load size performed by a sinkable load merging operation is
95     /// precisely the size necessary for the type in question.
96     Exact,
97 
98     /// Narrower-than-32-bit values are handled by ALU insts that are at least
99     /// 32 bits wide, which is normally OK as we ignore upper buts; but, if we
100     /// generate, e.g., a direct-from-memory 32-bit add for a byte value and
101     /// the byte is the last byte in a page, the extra data that we load is
102     /// incorrectly accessed. So we only allow loads to merge for
103     /// 32-bit-and-above widths.
104     Min32,
105 }
106 
107 /// Determines whether a load operation (indicated by `src_insn`) can be merged
108 /// into the current lowering point. If so, returns the address-base source (as
109 /// an `InsnInput`) and an offset from that address from which to perform the
110 /// load.
111 fn is_mergeable_load(
112     ctx: &mut Lower<Inst>,
113     src_insn: IRInst,
114     size: MergeableLoadSize,
115 ) -> Option<(InsnInput, i32)> {
116     let insn_data = ctx.data(src_insn);
117     let inputs = ctx.num_inputs(src_insn);
118     if inputs != 1 {
119         return None;
120     }
121 
122     // If this type is too small to get a merged load, don't merge the load.
123     let load_ty = ctx.output_ty(src_insn, 0);
124     if ty_bits(load_ty) < 32 {
125         match size {
126             MergeableLoadSize::Exact => {}
127             MergeableLoadSize::Min32 => return None,
128         }
129     }
130 
131     // If the load's flags specify big-endian, we can't merge.
132     if let Some(flags) = ctx.memflags(src_insn) {
133         if flags.explicit_endianness() == Some(Endianness::Big) {
134             return None;
135         }
136     }
137 
138     // Just testing the opcode is enough, because the width will always match if
139     // the type does (and the type should match if the CLIF is properly
140     // constructed).
141     if let &InstructionData::Load {
142         opcode: Opcode::Load,
143         offset,
144         ..
145     } = insn_data
146     {
147         Some((
148             InsnInput {
149                 insn: src_insn,
150                 input: 0,
151             },
152             offset.into(),
153         ))
154     } else {
155         None
156     }
157 }
158 
159 fn input_to_imm(ctx: &mut Lower<Inst>, spec: InsnInput) -> Option<u64> {
160     ctx.get_input_as_source_or_const(spec.insn, spec.input)
161         .constant
162 }
163 
164 fn emit_vm_call(
165     ctx: &mut Lower<Inst>,
166     flags: &Flags,
167     triple: &Triple,
168     libcall: LibCall,
169     inputs: &[ValueRegs<Reg>],
170 ) -> CodegenResult<InstOutput> {
171     let extname = ExternalName::LibCall(libcall);
172 
173     // TODO avoid recreating signatures for every single Libcall function.
174     let call_conv = CallConv::for_libcall(flags, CallConv::triple_default(triple));
175     let sig = libcall.signature(call_conv, types::I64);
176     let outputs = ctx.gen_call_output(&sig);
177 
178     if !ctx.sigs().have_abi_sig_for_signature(&sig) {
179         ctx.sigs_mut()
180             .make_abi_sig_from_ir_signature::<X64ABIMachineSpec>(sig.clone(), flags)?;
181     }
182     let sig = ctx.sigs().abi_sig_for_signature(&sig);
183 
184     let uses = ctx.gen_call_args(sig, inputs);
185     let defs = ctx.gen_call_rets(sig, &outputs);
186 
187     let stack_ret_space = ctx.sigs()[sig].sized_stack_ret_space();
188     let stack_arg_space = ctx.sigs()[sig].sized_stack_arg_space();
189     ctx.abi_mut()
190         .accumulate_outgoing_args_size(stack_ret_space + stack_arg_space);
191 
192     if flags.use_colocated_libcalls() {
193         let call_info = ctx.gen_call_info(sig, extname, uses, defs, None);
194         ctx.emit(Inst::call_known(Box::new(call_info)));
195     } else {
196         let tmp = ctx.alloc_tmp(types::I64).only_reg().unwrap();
197         ctx.emit(Inst::LoadExtName {
198             dst: tmp.map(Gpr::unwrap_new),
199             name: Box::new(extname),
200             offset: 0,
201             distance: RelocDistance::Far,
202         });
203         let call_info = ctx.gen_call_info(sig, RegMem::reg(tmp.to_reg()), uses, defs, None);
204         ctx.emit(Inst::call_unknown(Box::new(call_info)));
205     }
206     Ok(outputs)
207 }
208 
209 /// Returns whether the given input is a shift by a constant value less or equal than 3.
210 /// The goal is to embed it within an address mode.
211 fn matches_small_constant_shift(ctx: &mut Lower<Inst>, spec: InsnInput) -> Option<(InsnInput, u8)> {
212     matches_input(ctx, spec, Opcode::Ishl).and_then(|shift| {
213         match input_to_imm(
214             ctx,
215             InsnInput {
216                 insn: shift,
217                 input: 1,
218             },
219         ) {
220             Some(shift_amt) if shift_amt <= 3 => Some((
221                 InsnInput {
222                     insn: shift,
223                     input: 0,
224                 },
225                 shift_amt as u8,
226             )),
227             _ => None,
228         }
229     })
230 }
231 
232 /// Lowers an instruction to one of the x86 addressing modes.
233 ///
234 /// Note: the 32-bit offset in Cranelift has to be sign-extended, which maps x86's behavior.
235 fn lower_to_amode(ctx: &mut Lower<Inst>, spec: InsnInput, offset: i32) -> Amode {
236     let flags = ctx
237         .memflags(spec.insn)
238         .expect("Instruction with amode should have memflags");
239 
240     // We now either have an add that we must materialize, or some other input; as well as the
241     // final offset.
242     if let Some(add) = matches_input(ctx, spec, Opcode::Iadd) {
243         let output_ty = ctx.output_ty(add, 0);
244         debug_assert_eq!(
245             output_ty,
246             types::I64,
247             "Address width of 64 expected, got {output_ty}"
248         );
249         let add_inputs = &[
250             InsnInput {
251                 insn: add,
252                 input: 0,
253             },
254             InsnInput {
255                 insn: add,
256                 input: 1,
257             },
258         ];
259 
260         // TODO heap_addr legalization generates a uext64 *after* the shift, so these optimizations
261         // aren't happening in the wasm case. We could do better, given some range analysis.
262         let (base, index, shift) = if let Some((shift_input, shift_amt)) =
263             matches_small_constant_shift(ctx, add_inputs[0])
264         {
265             (
266                 put_input_in_reg(ctx, add_inputs[1]),
267                 put_input_in_reg(ctx, shift_input),
268                 shift_amt,
269             )
270         } else if let Some((shift_input, shift_amt)) =
271             matches_small_constant_shift(ctx, add_inputs[1])
272         {
273             (
274                 put_input_in_reg(ctx, add_inputs[0]),
275                 put_input_in_reg(ctx, shift_input),
276                 shift_amt,
277             )
278         } else {
279             for input in 0..=1 {
280                 // Try to pierce through uextend.
281                 let (inst, inst_input) = if let Some(uextend) =
282                     matches_input(ctx, InsnInput { insn: add, input }, Opcode::Uextend)
283                 {
284                     (uextend, 0)
285                 } else {
286                     (add, input)
287                 };
288 
289                 // If it's a constant, add it directly!
290                 if let Some(cst) = ctx.get_input_as_source_or_const(inst, inst_input).constant {
291                     let final_offset = (offset as i64).wrapping_add(cst as i64);
292                     if let Ok(final_offset) = i32::try_from(final_offset) {
293                         let base = put_input_in_reg(ctx, add_inputs[1 - input]);
294                         return Amode::imm_reg(final_offset, base).with_flags(flags);
295                     }
296                 }
297             }
298 
299             (
300                 put_input_in_reg(ctx, add_inputs[0]),
301                 put_input_in_reg(ctx, add_inputs[1]),
302                 0,
303             )
304         };
305 
306         return Amode::imm_reg_reg_shift(
307             offset,
308             Gpr::unwrap_new(base),
309             Gpr::unwrap_new(index),
310             shift,
311         )
312         .with_flags(flags);
313     }
314 
315     let input = put_input_in_reg(ctx, spec);
316     Amode::imm_reg(offset, input).with_flags(flags)
317 }
318 
319 //=============================================================================
320 // Lowering-backend trait implementation.
321 
322 impl LowerBackend for X64Backend {
323     type MInst = Inst;
324 
325     fn lower(&self, ctx: &mut Lower<Inst>, ir_inst: IRInst) -> Option<InstOutput> {
326         isle::lower(ctx, self, ir_inst)
327     }
328 
329     fn lower_branch(
330         &self,
331         ctx: &mut Lower<Inst>,
332         ir_inst: IRInst,
333         targets: &[MachLabel],
334     ) -> Option<()> {
335         isle::lower_branch(ctx, self, ir_inst, targets)
336     }
337 
338     fn maybe_pinned_reg(&self) -> Option<Reg> {
339         Some(regs::pinned_reg())
340     }
341 
342     fn check_fact(
343         &self,
344         ctx: &FactContext<'_>,
345         vcode: &mut VCode<Self::MInst>,
346         inst: InsnIndex,
347         state: &mut pcc::FactFlowState,
348     ) -> PccResult<()> {
349         pcc::check(ctx, vcode, inst, state)
350     }
351 
352     type FactFlowState = pcc::FactFlowState;
353 }
354