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