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