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