1 //! Generate the Cranelift-specific integration of the x64 assembler. 2 3 use cranelift_assembler_x64_meta::dsl::{Format, Inst, Mutability, Operand, OperandKind}; 4 use cranelift_srcgen::{fmtln, Formatter}; 5 6 /// Returns the Rust type used for the `IsleConstructorRaw` variants. 7 pub fn rust_param_raw(op: &Operand) -> String { 8 match op.location.kind() { 9 OperandKind::Imm(loc) => { 10 let bits = loc.bits(); 11 if op.extension.is_sign_extended() { 12 format!("i{bits}") 13 } else { 14 format!("u{bits}") 15 } 16 } 17 OperandKind::RegMem(rm) => { 18 let reg = match rm.bits() { 19 128 => "Xmm", 20 _ => "Gpr", 21 }; 22 let aligned = if op.align { "Aligned" } else { "" }; 23 format!("&{reg}Mem{aligned}") 24 } 25 OperandKind::Mem(_) => { 26 format!("&Amode") 27 } 28 OperandKind::Reg(r) => match r.bits() { 29 128 => "Xmm".to_string(), 30 _ => "Gpr".to_string(), 31 }, 32 OperandKind::FixedReg(_) => "Gpr".to_string(), 33 } 34 } 35 36 /// Returns the conversion function, if any, when converting the ISLE type for 37 /// this parameter to the assembler type for this parameter. Effectively 38 /// converts `self.rust_param_raw()` to the assembler type. 39 pub fn rust_convert_isle_to_assembler(op: &Operand) -> Option<&'static str> { 40 match op.location.kind() { 41 OperandKind::Reg(r) => Some(match (r.bits(), op.mutability) { 42 (128, Mutability::Read) => "cranelift_assembler_x64::Xmm::new", 43 (128, Mutability::ReadWrite) => "self.convert_xmm_to_assembler_read_write_xmm", 44 (_, Mutability::Read) => "cranelift_assembler_x64::Gpr::new", 45 (_, Mutability::ReadWrite) => "self.convert_gpr_to_assembler_read_write_gpr", 46 }), 47 OperandKind::Mem(_) => Some("self.convert_amode_to_assembler_amode"), 48 OperandKind::RegMem(r) => Some(match (r.bits(), op.mutability) { 49 (128, Mutability::Read) => "self.convert_xmm_mem_to_assembler_read_xmm_mem", 50 (128, Mutability::ReadWrite) => "self.convert_xmm_mem_to_assembler_read_write_xmm_mem", 51 (_, Mutability::Read) => "self.convert_gpr_mem_to_assembler_read_gpr_mem", 52 (_, Mutability::ReadWrite) => "self.convert_gpr_mem_to_assembler_read_write_gpr_mem", 53 }), 54 OperandKind::Imm(loc) => match (op.extension.is_sign_extended(), loc.bits()) { 55 (true, 8) => Some("cranelift_assembler_x64::Simm8::new"), 56 (true, 16) => Some("cranelift_assembler_x64::Simm16::new"), 57 (true, 32) => Some("cranelift_assembler_x64::Simm32::new"), 58 (false, 8) => Some("cranelift_assembler_x64::Imm8::new"), 59 (false, 16) => Some("cranelift_assembler_x64::Imm16::new"), 60 (false, 32) => Some("cranelift_assembler_x64::Imm32::new"), 61 _ => None, 62 }, 63 OperandKind::FixedReg(_) => None, 64 } 65 } 66 67 /// `fn x64_<inst>(&mut self, <params>) -> Inst<R> { ... }` 68 /// 69 /// # Panics 70 /// 71 /// This function panics if the instruction has no operands. 72 pub fn generate_macro_inst_fn(f: &mut Formatter, inst: &Inst) { 73 let struct_name = inst.name(); 74 let params = inst 75 .format 76 .operands 77 .iter() 78 .filter(|o| o.mutability.is_read()) 79 // FIXME(#10238) don't filter out fixed regs here 80 .filter(|o| !matches!(o.location.kind(), OperandKind::FixedReg(_))) 81 .collect::<Vec<_>>(); 82 let results = inst 83 .format 84 .operands 85 .iter() 86 .filter(|o| o.mutability.is_write()) 87 .collect::<Vec<_>>(); 88 let rust_params = params 89 .iter() 90 .map(|o| format!("{}: {}", o.location, rust_param_raw(o))) 91 .collect::<Vec<_>>() 92 .join(", "); 93 f.add_block( 94 &format!("fn x64_{struct_name}_raw(&mut self, {rust_params}) -> AssemblerOutputs"), 95 |f| { 96 for o in params.iter() { 97 let l = o.location; 98 match rust_convert_isle_to_assembler(o) { 99 Some(cvt) => fmtln!(f, "let {l} = {cvt}({l});"), 100 None => fmtln!(f, "let {l} = {l}.clone();"), 101 } 102 } 103 let args = params 104 .iter() 105 .map(|o| format!("{}.clone()", o.location)) 106 .collect::<Vec<_>>(); 107 let args = args.join(", "); 108 fmtln!( 109 f, 110 "let inst = cranelift_assembler_x64::inst::{struct_name}::new({args}).into();" 111 ); 112 if let Some(OperandKind::FixedReg(_)) = results.first().map(|o| o.location.kind()) { 113 fmtln!(f, "#[allow(unused_variables, reason = \"FIXME(#10238): fixed register instructions have TODOs\")]"); 114 } 115 fmtln!(f, "let inst = MInst::External {{ inst }};"); 116 117 use cranelift_assembler_x64_meta::dsl::Mutability::*; 118 match results.as_slice() { 119 [] => fmtln!(f, "SideEffectNoResult::Inst(inst)"), 120 [one] => match one.mutability { 121 Read => unreachable!(), 122 ReadWrite => match one.location.kind() { 123 OperandKind::Imm(_) => unreachable!(), 124 // FIXME(#10238) 125 OperandKind::FixedReg(_) => fmtln!(f, "todo!()"), 126 // One read/write register output? Output the instruction 127 // and that register. 128 OperandKind::Reg(r) => { 129 let (var, ty) = match r.bits() { 130 128 => ("xmm", "Xmm"), 131 _ => ("gpr", "Gpr"), 132 }; 133 fmtln!( 134 f, 135 "let {var} = {r}.as_ref().write.to_reg();", 136 ); 137 fmtln!(f, "AssemblerOutputs::Ret{ty} {{ inst, {var} }}"); 138 }, 139 // One read/write memory operand? Output a side effect. 140 OperandKind::Mem(_) => { 141 fmtln!(f, "AssemblerOutputs::SideEffect {{ inst }}") 142 } 143 // One read/write regmem output? We need to output 144 // everything and it'll internally disambiguate which was 145 // emitted (e.g. the mem variant or the register variant). 146 OperandKind::RegMem(rm) => { 147 assert_eq!(results.len(), 1); 148 let (var, ty) = match rm.bits() { 149 128 => ("xmm", "Xmm"), 150 _ => ("gpr", "Gpr"), 151 }; 152 f.add_block(&format!("match {rm}"), |f| { 153 f.add_block(&format!("asm::{ty}Mem::{ty}(reg) => "), |f| { 154 fmtln!(f, "let {var} = reg.write.to_reg();"); 155 fmtln!(f, "AssemblerOutputs::Ret{ty} {{ inst, {var} }} "); 156 }); 157 f.add_block(&format!("asm::{ty}Mem::Mem(_) => "), |f| { 158 fmtln!(f, "AssemblerOutputs::SideEffect {{ inst }} "); 159 }); 160 }); 161 } 162 }, 163 }, 164 _ => panic!("instruction has more than one result"), 165 } 166 }, 167 ); 168 } 169 170 /// Generate the `isle_assembler_methods!` macro. 171 pub fn generate_rust_macro(f: &mut Formatter, insts: &[Inst]) { 172 fmtln!(f, "#[doc(hidden)]"); 173 fmtln!(f, "macro_rules! isle_assembler_methods {{"); 174 f.indent(|f| { 175 fmtln!(f, "() => {{"); 176 f.indent(|f| { 177 for inst in insts { 178 generate_macro_inst_fn(f, inst); 179 } 180 }); 181 fmtln!(f, "}};"); 182 }); 183 fmtln!(f, "}}"); 184 } 185 186 /// Returns the type of this operand in ISLE as a part of the ISLE "raw" 187 /// constructors. 188 pub fn isle_param_raw(op: &Operand) -> String { 189 match op.location.kind() { 190 OperandKind::Imm(loc) => { 191 let bits = loc.bits(); 192 if op.extension.is_sign_extended() { 193 format!("i{bits}") 194 } else { 195 format!("u{bits}") 196 } 197 } 198 OperandKind::Reg(r) => match r.bits() { 199 128 => "Xmm".to_string(), 200 _ => "Gpr".to_string(), 201 }, 202 OperandKind::FixedReg(_) => "Gpr".to_string(), 203 OperandKind::Mem(_) => { 204 if op.align { 205 unimplemented!("no way yet to mark an Amode as aligned") 206 } else { 207 "Amode".to_string() 208 } 209 } 210 OperandKind::RegMem(rm) => { 211 let reg = match rm.bits() { 212 128 => "Xmm", 213 _ => "Gpr", 214 }; 215 let aligned = if op.align { "Aligned" } else { "" }; 216 format!("{reg}Mem{aligned}") 217 } 218 } 219 } 220 221 /// Different kinds of ISLE constructors generated for a particular instruction. 222 /// 223 /// One instruction may generate a single constructor or multiple constructors. 224 /// For example an instruction that writes its result to a register will 225 /// generate only a single constructor. An instruction where the destination 226 /// read/write operand is `GprMem` will generate two constructors though, one 227 /// for memory and one for in registers. 228 #[derive(Copy, Clone, Debug)] 229 pub enum IsleConstructor { 230 /// This constructor only produces a side effect, meaning that the 231 /// instruction does not produce results in registers. This may produce 232 /// a result in memory, however. 233 RetMemorySideEffect, 234 235 /// This constructor produces a `Gpr` value, meaning that it will write the 236 /// result to a `Gpr`. 237 RetGpr, 238 239 /// This constructor produces an `Xmm` value, meaning that it will write the 240 /// result to an `Xmm`. 241 RetXmm, 242 } 243 244 impl IsleConstructor { 245 /// Returns the result type, in ISLE, that this constructor generates. 246 pub fn result_ty(&self) -> &'static str { 247 match self { 248 IsleConstructor::RetMemorySideEffect => "SideEffectNoResult", 249 IsleConstructor::RetGpr => "Gpr", 250 IsleConstructor::RetXmm => "Xmm", 251 } 252 } 253 254 /// Returns the constructor used to convert an `AssemblerOutput` into the 255 /// type returned by [`Self::result_ty`]. 256 pub fn conversion_constructor(&self) -> &'static str { 257 match self { 258 IsleConstructor::RetMemorySideEffect => "defer_side_effect", 259 IsleConstructor::RetGpr => "emit_ret_gpr", 260 IsleConstructor::RetXmm => "emit_ret_xmm", 261 } 262 } 263 264 /// Returns the suffix used in the ISLE constructor name. 265 pub fn suffix(&self) -> &'static str { 266 match self { 267 IsleConstructor::RetMemorySideEffect => "_mem", 268 IsleConstructor::RetGpr => "", 269 IsleConstructor::RetXmm => "", 270 } 271 } 272 } 273 274 /// Returns the parameter type used for the `IsleConstructor` variant 275 /// provided. 276 pub fn isle_param_for_ctor(op: &Operand, ctor: IsleConstructor) -> String { 277 match op.location.kind() { 278 // Writable `RegMem` operands are special here: in one constructor 279 // it's operating on memory so the argument is `Amode` and in the 280 // other constructor it's operating on registers so the argument is 281 // a `Gpr`. 282 OperandKind::RegMem(_) if op.mutability.is_write() => match ctor { 283 IsleConstructor::RetMemorySideEffect => "Amode".to_string(), 284 IsleConstructor::RetGpr => "Gpr".to_string(), 285 IsleConstructor::RetXmm => "Xmm".to_string(), 286 }, 287 288 // everything else is the same as the "raw" variant 289 _ => isle_param_raw(op), 290 } 291 } 292 293 /// Returns the ISLE constructors that are going to be used when generating 294 /// this instruction. 295 /// 296 /// Note that one instruction might need multiple constructors, such as one 297 /// for operating on memory and one for operating on registers. 298 pub fn isle_constructors(format: &Format) -> Vec<IsleConstructor> { 299 use Mutability::*; 300 use OperandKind::*; 301 302 let write_operands = format 303 .operands 304 .iter() 305 .filter(|o| o.mutability.is_write()) 306 .collect::<Vec<_>>(); 307 match &write_operands[..] { 308 [] => unimplemented!("if you truly need this (and not a `SideEffect*`), add a `NoReturn` variant to `AssemblerOutputs`"), 309 [one] => match one.mutability { 310 Read => unreachable!(), 311 ReadWrite => match one.location.kind() { 312 Imm(_) => unreachable!(), 313 FixedReg(_) => vec![IsleConstructor::RetGpr], 314 // One read/write register output? Output the instruction 315 // and that register. 316 Reg(r) => match r.bits() { 317 128 => vec![IsleConstructor::RetXmm], 318 _ => vec![IsleConstructor::RetGpr], 319 }, 320 // One read/write memory operand? Output a side effect. 321 Mem(_) => vec![IsleConstructor::RetMemorySideEffect], 322 // One read/write reg-mem output? We need constructors for 323 // both variants. 324 RegMem(rm) => match rm.bits() { 325 128 => vec![IsleConstructor::RetXmm, IsleConstructor::RetMemorySideEffect], 326 _ => vec![IsleConstructor::RetGpr, IsleConstructor::RetMemorySideEffect], 327 }, 328 } 329 }, 330 other => panic!("unsupported number of write operands {other:?}"), 331 } 332 } 333 334 /// Generate a "raw" constructor that simply constructs, but does not emit 335 /// the assembly instruction: 336 /// 337 /// ```text 338 /// (decl x64_<inst>_raw (<params>) AssemblerOutputs) 339 /// (extern constructor x64_<inst>_raw x64_<inst>_raw) 340 /// ``` 341 /// 342 /// Using the "raw" constructor, we also generate "emitter" constructors 343 /// (see [`IsleConstructor`]). E.g., instructions that write to a register 344 /// will return the register: 345 /// 346 /// ```text 347 /// (decl x64_<inst> (<params>) Gpr) 348 /// (rule (x64_<inst> <params>) (emit_ret_gpr (x64_<inst>_raw <params>))) 349 /// ``` 350 /// 351 /// For instructions that write to memory, we also generate an "emitter" 352 /// constructor with the `_mem` suffix: 353 /// 354 /// ```text 355 /// (decl x64_<inst>_mem (<params>) SideEffectNoResult) 356 /// (rule (x64_<inst>_mem <params>) (defer_side_effect (x64_<inst>_raw <params>))) 357 /// ``` 358 /// 359 /// # Panics 360 /// 361 /// This function panics if the instruction has no operands. 362 pub fn generate_isle_inst_decls(f: &mut Formatter, inst: &Inst) { 363 // First declare the "raw" constructor which is implemented in Rust 364 // with `generate_isle_macro` above. This is an "extern" constructor 365 // with relatively raw types. This is not intended to be used by 366 // general lowering rules in ISLE. 367 let struct_name = inst.name(); 368 let raw_name = format!("x64_{struct_name}_raw"); 369 let params = inst 370 .format 371 .operands 372 .iter() 373 .filter(|o| o.mutability.is_read()) 374 // FIXME(#10238) don't filter out fixed regs here 375 .filter(|o| !matches!(o.location.kind(), OperandKind::FixedReg(_))) 376 .collect::<Vec<_>>(); 377 let raw_param_tys = params 378 .iter() 379 .map(|o| isle_param_raw(o)) 380 .collect::<Vec<_>>() 381 .join(" "); 382 fmtln!(f, "(decl {raw_name} ({raw_param_tys}) AssemblerOutputs)"); 383 fmtln!(f, "(extern constructor {raw_name} {raw_name})"); 384 385 // Next, for each "emitter" ISLE constructor being generated, synthesize 386 // a pure-ISLE constructor which delegates appropriately to the `*_raw` 387 // constructor above. 388 // 389 // The main purpose of these constructors is to have faithful type 390 // signatures for the SSA nature of VCode/ISLE, effectively translating 391 // x64's type system to ISLE/VCode's type system. 392 for ctor in isle_constructors(&inst.format) { 393 let suffix = ctor.suffix(); 394 let rule_name = format!("x64_{struct_name}{suffix}"); 395 let result_ty = ctor.result_ty(); 396 let param_tys = params 397 .iter() 398 .map(|o| isle_param_for_ctor(o, ctor)) 399 .collect::<Vec<_>>() 400 .join(" "); 401 let param_names = params 402 .iter() 403 .map(|o| o.location.to_string()) 404 .collect::<Vec<_>>() 405 .join(" "); 406 let convert = ctor.conversion_constructor(); 407 408 fmtln!(f, "(decl {rule_name} ({param_tys}) {result_ty})"); 409 fmtln!( 410 f, 411 "(rule ({rule_name} {param_names}) ({convert} ({raw_name} {param_names})))" 412 ); 413 } 414 } 415 416 /// Generate the ISLE definitions that match the `isle_assembler_methods!` macro 417 /// above. 418 pub fn generate_isle(f: &mut Formatter, insts: &[Inst]) { 419 fmtln!(f, "(type AssemblerOutputs (enum"); 420 fmtln!(f, " ;; Used for instructions that have ISLE"); 421 fmtln!(f, " ;; `SideEffect`s (memory stores, traps,"); 422 fmtln!(f, " ;; etc.) and do not return a `Value`."); 423 fmtln!(f, " (SideEffect (inst MInst))"); 424 fmtln!(f, " ;; Used for instructions that return a"); 425 fmtln!(f, " ;; GPR (including `GprMem` variants with"); 426 fmtln!(f, " ;; a GPR as the first argument)."); 427 fmtln!(f, " (RetGpr (inst MInst) (gpr Gpr))"); 428 fmtln!(f, " ;; Used for instructions that return an"); 429 fmtln!(f, " ;; XMM register."); 430 fmtln!(f, " (RetXmm (inst MInst) (xmm Xmm))"); 431 fmtln!(f, " ;; TODO: eventually add more variants for"); 432 fmtln!(f, " ;; multi-return, XMM, etc.; see"); 433 fmtln!( 434 f, 435 " ;; https://github.com/bytecodealliance/wasmtime/pull/10276" 436 ); 437 fmtln!(f, "))"); 438 f.empty_line(); 439 440 fmtln!(f, ";; Directly emit instructions that return a GPR."); 441 fmtln!(f, "(decl emit_ret_gpr (AssemblerOutputs) Gpr)"); 442 fmtln!(f, "(rule (emit_ret_gpr (AssemblerOutputs.RetGpr inst gpr))"); 443 fmtln!(f, " (let ((_ Unit (emit inst))) gpr))"); 444 f.empty_line(); 445 446 fmtln!(f, ";; Directly emit instructions that return an"); 447 fmtln!(f, ";; XMM register."); 448 fmtln!(f, "(decl emit_ret_xmm (AssemblerOutputs) Xmm)"); 449 fmtln!(f, "(rule (emit_ret_xmm (AssemblerOutputs.RetXmm inst xmm))"); 450 fmtln!(f, " (let ((_ Unit (emit inst))) xmm))"); 451 f.empty_line(); 452 453 fmtln!(f, ";; Pass along the side-effecting instruction"); 454 fmtln!(f, ";; for later emission."); 455 fmtln!( 456 f, 457 "(decl defer_side_effect (AssemblerOutputs) SideEffectNoResult)" 458 ); 459 fmtln!( 460 f, 461 "(rule (defer_side_effect (AssemblerOutputs.SideEffect inst))" 462 ); 463 fmtln!(f, " (SideEffectNoResult.Inst inst))"); 464 f.empty_line(); 465 466 for inst in insts { 467 generate_isle_inst_decls(f, inst); 468 f.empty_line(); 469 } 470 } 471