1 //! Generate the Cranelift-specific integration of the x64 assembler. 2 3 use cranelift_assembler_x64_meta::dsl::{ 4 Feature, Format, Inst, Location, Mutability, Operand, OperandKind, RegClass, 5 }; 6 use cranelift_srcgen::{Formatter, fmtln}; 7 8 /// This factors out use of the assembler crate name. 9 const ASM: &str = "cranelift_assembler_x64"; 10 11 fn include_inst(inst: &Inst) -> bool { 12 // No need to worry about this instruction shape in ISLE as it's generated 13 // in ABI code, not ISLE. 14 if inst.mnemonic.starts_with("push") { 15 return false; 16 } 17 18 true 19 } 20 21 /// Returns the Rust type used for the `IsleConstructorRaw` variants. 22 fn rust_param_raw(op: &Operand) -> String { 23 match op.location.kind() { 24 OperandKind::Imm(loc) => { 25 let bits = loc.bits(); 26 if op.extension.is_sign_extended() { 27 format!("i{bits}") 28 } else { 29 format!("u{bits}") 30 } 31 } 32 OperandKind::RegMem(rm) => { 33 let reg = rm.reg_class().unwrap(); 34 let aligned = if op.align { "Aligned" } else { "" }; 35 format!("&{reg}Mem{aligned}") 36 } 37 OperandKind::Mem(_) => { 38 format!("&SyntheticAmode") 39 } 40 OperandKind::Reg(r) | OperandKind::FixedReg(r) => r.reg_class().unwrap().to_string(), 41 } 42 } 43 44 /// Returns the conversion function, if any, when converting the ISLE type for 45 /// this parameter to the assembler type for this parameter. Effectively 46 /// converts `self.rust_param_raw()` to the assembler type. 47 fn rust_convert_isle_to_assembler(op: &Operand) -> String { 48 match op.location.kind() { 49 OperandKind::Imm(loc) => { 50 let bits = loc.bits(); 51 let ty = if op.extension.is_sign_extended() { 52 "Simm" 53 } else { 54 "Imm" 55 }; 56 format!("{ASM}::{ty}{bits}::new({loc})") 57 } 58 OperandKind::FixedReg(r) => { 59 let reg = r.reg_class().unwrap().to_string().to_lowercase(); 60 match op.mutability { 61 Mutability::Read => format!("{ASM}::Fixed({r})"), 62 Mutability::Write => { 63 format!("{ASM}::Fixed(self.temp_writable_{reg}())") 64 } 65 Mutability::ReadWrite => { 66 format!("self.convert_{reg}_to_assembler_fixed_read_write_{reg}({r})") 67 } 68 } 69 } 70 OperandKind::Reg(r) => { 71 let reg = r.reg_class().unwrap(); 72 let reg_lower = reg.to_string().to_lowercase(); 73 match op.mutability { 74 Mutability::Read => { 75 format!("{ASM}::{reg}::new({r})") 76 } 77 Mutability::Write => { 78 format!("{ASM}::{reg}::new(self.temp_writable_{reg_lower}())") 79 } 80 Mutability::ReadWrite => { 81 format!("self.convert_{reg_lower}_to_assembler_read_write_{reg_lower}({r})") 82 } 83 } 84 } 85 OperandKind::RegMem(rm) => { 86 let reg = rm.reg_class().unwrap().to_string().to_lowercase(); 87 let mut_ = op.mutability.generate_snake_case(); 88 let align = if op.align { "_aligned" } else { "" }; 89 format!("self.convert_{reg}_mem_to_assembler_{mut_}_{reg}_mem{align}({rm})") 90 } 91 OperandKind::Mem(mem) => format!("self.convert_amode_to_assembler_amode({mem})"), 92 } 93 } 94 95 /// `fn x64_<inst>(&mut self, <params>) -> Inst<R> { ... }` 96 /// 97 /// # Panics 98 /// 99 /// This function panics if the instruction has no operands. 100 fn generate_macro_inst_fn(f: &mut Formatter, inst: &Inst) { 101 use OperandKind::*; 102 103 let struct_name = inst.name(); 104 let operands = inst.format.operands.iter().cloned().collect::<Vec<_>>(); 105 let results = operands 106 .iter() 107 .filter(|o| o.mutability.is_write()) 108 .collect::<Vec<_>>(); 109 let rust_params = operands 110 .iter() 111 .filter(|o| is_raw_operand_param(o)) 112 .map(|o| format!("{}: {}", o.location, rust_param_raw(o))) 113 .chain(if inst.has_trap { 114 Some(format!("trap: &TrapCode")) 115 } else { 116 None 117 }) 118 .collect::<Vec<_>>() 119 .join(", "); 120 f.add_block( 121 &format!("fn x64_{struct_name}_raw(&mut self, {rust_params}) -> AssemblerOutputs"), 122 |f| { 123 f.comment("Convert ISLE types to assembler types."); 124 for op in operands.iter() { 125 let loc = op.location; 126 let cvt = rust_convert_isle_to_assembler(op); 127 fmtln!(f, "let {loc} = {cvt};"); 128 } 129 let mut args = operands 130 .iter() 131 .map(|o| format!("{}.clone()", o.location)) 132 .collect::<Vec<_>>(); 133 if inst.has_trap { 134 args.push(format!("{ASM}::TrapCode(trap.as_raw())")); 135 } 136 let args = args.join(", "); 137 f.empty_line(); 138 139 f.comment("Build the instruction."); 140 fmtln!( 141 f, 142 "let inst = {ASM}::inst::{struct_name}::new({args}).into();" 143 ); 144 fmtln!(f, "let inst = MInst::External {{ inst }};"); 145 f.empty_line(); 146 147 // When an instruction writes to an operand, Cranelift expects a 148 // returned value to use in other instructions: we return this 149 // information in the `AssemblerOutputs` struct defined in ISLE 150 // (below). The general rule here is that memory stores will create 151 // a `SideEffect` whereas for write or read-write registers we will 152 // return some form of `Ret*`. 153 f.comment("Return a type ISLE can work with."); 154 let access_reg = |op: &Operand| match op.mutability { 155 Mutability::Read => unreachable!(), 156 Mutability::Write => "to_reg()", 157 Mutability::ReadWrite => "write.to_reg()", 158 }; 159 let ty_var_of_reg = |loc: Location| { 160 let ty = loc.reg_class().unwrap().to_string(); 161 let var = ty.to_lowercase(); 162 (ty, var) 163 }; 164 match results.as_slice() { 165 [] => fmtln!(f, "AssemblerOutputs::SideEffect {{ inst }}"), 166 [op] => match op.location.kind() { 167 Imm(_) => unreachable!(), 168 Reg(r) | FixedReg(r) => { 169 let (ty, var) = ty_var_of_reg(r); 170 fmtln!(f, "let {var} = {r}.as_ref().{};", access_reg(op)); 171 fmtln!(f, "AssemblerOutputs::Ret{ty} {{ inst, {var} }}"); 172 } 173 Mem(_) => { 174 fmtln!(f, "AssemblerOutputs::SideEffect {{ inst }}") 175 } 176 RegMem(rm) => { 177 let (ty, var) = ty_var_of_reg(rm); 178 f.add_block(&format!("match {rm}"), |f| { 179 f.add_block(&format!("{ASM}::{ty}Mem::{ty}(reg) => "), |f| { 180 fmtln!(f, "let {var} = reg.{};", access_reg(op)); 181 fmtln!(f, "AssemblerOutputs::Ret{ty} {{ inst, {var} }} "); 182 }); 183 f.add_block(&format!("{ASM}::{ty}Mem::Mem(_) => "), |f| { 184 fmtln!(f, "AssemblerOutputs::SideEffect {{ inst }} "); 185 }); 186 }); 187 } 188 }, 189 // For now, we assume that if there are two results, they are 190 // coming from a register-writing instruction like `mul`. The 191 // `match` below can be expanded as needed. 192 [op1, op2] => match (op1.location.kind(), op2.location.kind()) { 193 (FixedReg(loc1) | Reg(loc1), FixedReg(loc2) | Reg(loc2)) => { 194 fmtln!(f, "let one = {loc1}.as_ref().{}.to_reg();", access_reg(op1)); 195 fmtln!(f, "let two = {loc2}.as_ref().{}.to_reg();", access_reg(op2)); 196 fmtln!(f, "let regs = ValueRegs::two(one, two);"); 197 fmtln!(f, "AssemblerOutputs::RetValueRegs {{ inst, regs }}"); 198 } 199 (Reg(reg), Mem(_)) | (Mem(_) | RegMem(_), Reg(reg) | FixedReg(reg)) => { 200 let (ty, var) = ty_var_of_reg(reg); 201 fmtln!(f, "let {var} = {reg}.as_ref().{};", access_reg(op2)); 202 fmtln!(f, "AssemblerOutputs::Ret{ty} {{ inst, {var} }}"); 203 } 204 _ => unimplemented!("unhandled results: {results:?}"), 205 }, 206 207 [op1, op2, op3] => match ( 208 op1.location.kind(), 209 op2.location.kind(), 210 op3.location.kind(), 211 ) { 212 (FixedReg(loc1), FixedReg(loc2), Mem(_)) => { 213 fmtln!(f, "let one = {loc1}.as_ref().{}.to_reg();", access_reg(op1)); 214 fmtln!(f, "let two = {loc2}.as_ref().{}.to_reg();", access_reg(op2)); 215 fmtln!(f, "let regs = ValueRegs::two(one, two);"); 216 fmtln!(f, "AssemblerOutputs::RetValueRegs {{ inst, regs }}"); 217 } 218 _ => unimplemented!("unhandled results: {results:?}"), 219 }, 220 221 _ => panic!("instruction has more than one result"), 222 } 223 }, 224 ); 225 } 226 227 /// Generate the `isle_assembler_methods!` macro. 228 pub fn generate_rust_macro(f: &mut Formatter, insts: &[Inst]) { 229 fmtln!(f, "#[doc(hidden)]"); 230 fmtln!(f, "macro_rules! isle_assembler_methods {{"); 231 f.indent(|f| { 232 fmtln!(f, "() => {{"); 233 f.indent(|f| { 234 for inst in insts { 235 if include_inst(inst) { 236 generate_macro_inst_fn(f, inst); 237 } 238 } 239 }); 240 fmtln!(f, "}};"); 241 }); 242 fmtln!(f, "}}"); 243 } 244 245 /// Returns the type of this operand in ISLE as a part of the ISLE "raw" 246 /// constructors. 247 fn isle_param_raw(op: &Operand) -> String { 248 match op.location.kind() { 249 OperandKind::Imm(loc) => { 250 let bits = loc.bits(); 251 if op.extension.is_sign_extended() { 252 format!("i{bits}") 253 } else { 254 format!("u{bits}") 255 } 256 } 257 OperandKind::Reg(r) | OperandKind::FixedReg(r) => r.reg_class().unwrap().to_string(), 258 OperandKind::Mem(_) => { 259 if op.align { 260 unimplemented!("no way yet to mark an SyntheticAmode as aligned") 261 } else { 262 "SyntheticAmode".to_string() 263 } 264 } 265 OperandKind::RegMem(rm) => { 266 let reg = rm.reg_class().unwrap(); 267 let aligned = if op.align { "Aligned" } else { "" }; 268 format!("{reg}Mem{aligned}") 269 } 270 } 271 } 272 273 /// Different kinds of ISLE constructors generated for a particular instruction. 274 /// 275 /// One instruction may generate a single constructor or multiple constructors. 276 /// For example an instruction that writes its result to a register will 277 /// generate only a single constructor. An instruction where the destination 278 /// read/write operand is `GprMem` will generate two constructors though, one 279 /// for memory and one for in registers. 280 #[derive(Copy, Clone, Debug)] 281 enum IsleConstructor { 282 /// This constructor only produces a side effect, meaning that the 283 /// instruction does not produce results in registers. This may produce 284 /// a result in memory, however. 285 RetMemorySideEffect, 286 287 /// This constructor produces a `Gpr` value, meaning that the instruction 288 /// will write its result to a single GPR register. 289 RetGpr, 290 291 /// This is similar to `RetGpr`, but for XMM registers. 292 RetXmm, 293 294 /// This "special" constructor captures multiple written-to registers (e.g. 295 /// `mul`). 296 RetValueRegs, 297 298 /// This constructor does not return any results, but produces a side effect affecting EFLAGs. 299 NoReturnSideEffect, 300 301 /// This constructor produces no results, but the flags register is written, 302 /// so a `ProducesFlags` value is returned with a side effect. 303 ProducesFlagsSideEffect, 304 305 /// This instructions reads EFLAGS, and returns a single gpr, so this 306 /// creates `ConsumesFlags.ConsumesFlagsReturnsReg`. 307 ConsumesFlagsReturnsGpr, 308 } 309 310 impl IsleConstructor { 311 /// Returns the result type, in ISLE, that this constructor generates. 312 fn result_ty(&self) -> &'static str { 313 match self { 314 IsleConstructor::RetGpr => "Gpr", 315 IsleConstructor::RetXmm => "Xmm", 316 IsleConstructor::RetValueRegs => "ValueRegs", 317 IsleConstructor::NoReturnSideEffect | IsleConstructor::RetMemorySideEffect => { 318 "SideEffectNoResult" 319 } 320 IsleConstructor::ProducesFlagsSideEffect => "ProducesFlags", 321 IsleConstructor::ConsumesFlagsReturnsGpr => "ConsumesFlags", 322 } 323 } 324 325 /// Returns the constructor used to convert an `AssemblerOutput` into the 326 /// type returned by [`Self::result_ty`]. 327 fn conversion_constructor(&self) -> &'static str { 328 match self { 329 IsleConstructor::NoReturnSideEffect | IsleConstructor::RetMemorySideEffect => { 330 "defer_side_effect" 331 } 332 IsleConstructor::RetGpr => "emit_ret_gpr", 333 IsleConstructor::RetXmm => "emit_ret_xmm", 334 IsleConstructor::RetValueRegs => "emit_ret_value_regs", 335 IsleConstructor::ProducesFlagsSideEffect => "asm_produce_flags_side_effect", 336 IsleConstructor::ConsumesFlagsReturnsGpr => "asm_consumes_flags_returns_gpr", 337 } 338 } 339 340 /// Returns the suffix used in the ISLE constructor name. 341 fn suffix(&self) -> &'static str { 342 match self { 343 IsleConstructor::RetMemorySideEffect => "_mem", 344 IsleConstructor::RetGpr 345 | IsleConstructor::RetXmm 346 | IsleConstructor::RetValueRegs 347 | IsleConstructor::NoReturnSideEffect 348 | IsleConstructor::ProducesFlagsSideEffect 349 | IsleConstructor::ConsumesFlagsReturnsGpr => "", 350 } 351 } 352 353 /// Returns whether this constructor will include a write-only `RegMem` 354 /// operand as an argument to the constructor. 355 /// 356 /// Memory-based ctors take an `Amode`, but register-based ctors don't take 357 /// the result as an argument and instead manufacture it internally. 358 fn includes_write_only_reg_mem(&self) -> bool { 359 match self { 360 IsleConstructor::RetMemorySideEffect => true, 361 IsleConstructor::RetGpr 362 | IsleConstructor::RetXmm 363 | IsleConstructor::RetValueRegs 364 | IsleConstructor::NoReturnSideEffect 365 | IsleConstructor::ProducesFlagsSideEffect 366 | IsleConstructor::ConsumesFlagsReturnsGpr => false, 367 } 368 } 369 } 370 371 /// Returns the parameter type used for the `IsleConstructor` variant 372 /// provided. 373 fn isle_param_for_ctor(op: &Operand, ctor: IsleConstructor) -> String { 374 match op.location.kind() { 375 // Writable `RegMem` operands are special here: in one constructor 376 // it's operating on memory so the argument is `Amode` and in the 377 // other constructor it's operating on registers so the argument is 378 // a `Gpr`. 379 OperandKind::RegMem(_) if op.mutability.is_write() => match ctor { 380 IsleConstructor::RetMemorySideEffect => "SyntheticAmode".to_string(), 381 IsleConstructor::NoReturnSideEffect => "".to_string(), 382 IsleConstructor::RetGpr | IsleConstructor::ConsumesFlagsReturnsGpr => "Gpr".to_string(), 383 IsleConstructor::RetXmm => "Xmm".to_string(), 384 IsleConstructor::RetValueRegs => "ValueRegs".to_string(), 385 IsleConstructor::ProducesFlagsSideEffect => todo!(), 386 }, 387 388 // everything else is the same as the "raw" variant 389 _ => isle_param_raw(op), 390 } 391 } 392 393 /// Returns the ISLE constructors that are going to be used when generating 394 /// this instruction. 395 /// 396 /// Note that one instruction might need multiple constructors, such as one 397 /// for operating on memory and one for operating on registers. 398 fn isle_constructors(format: &Format) -> Vec<IsleConstructor> { 399 use Mutability::*; 400 use OperandKind::*; 401 402 let write_operands = format 403 .operands 404 .iter() 405 .filter(|o| o.mutability.is_write()) 406 .collect::<Vec<_>>(); 407 match &write_operands[..] { 408 [] => { 409 if format.eflags.is_write() { 410 vec![IsleConstructor::ProducesFlagsSideEffect] 411 } else { 412 vec![IsleConstructor::NoReturnSideEffect] 413 } 414 } 415 [one] => match one.mutability { 416 Read => unreachable!(), 417 ReadWrite | Write => match one.location.kind() { 418 Imm(_) => unreachable!(), 419 // One read/write register output? Output the instruction 420 // and that register. 421 Reg(r) | FixedReg(r) => match r.reg_class().unwrap() { 422 RegClass::Xmm => { 423 assert!(!format.eflags.is_read()); 424 vec![IsleConstructor::RetXmm] 425 } 426 RegClass::Gpr => { 427 if format.eflags.is_read() { 428 vec![IsleConstructor::ConsumesFlagsReturnsGpr] 429 } else { 430 vec![IsleConstructor::RetGpr] 431 } 432 } 433 }, 434 // One read/write memory operand? Output a side effect. 435 Mem(_) => { 436 assert!(!format.eflags.is_read()); 437 vec![IsleConstructor::RetMemorySideEffect] 438 } 439 // One read/write reg-mem output? We need constructors for 440 // both variants. 441 RegMem(rm) => match rm.reg_class().unwrap() { 442 RegClass::Xmm => { 443 assert!(!format.eflags.is_read()); 444 vec![ 445 IsleConstructor::RetXmm, 446 IsleConstructor::RetMemorySideEffect, 447 ] 448 } 449 RegClass::Gpr => { 450 if format.eflags.is_read() { 451 // FIXME: should expand this to include "consumes 452 // flags plus side effect" to model the 453 // memory-writing variant too. For example this 454 // means there's no memory-writing variant of 455 // `setcc` instructions generated. 456 vec![IsleConstructor::ConsumesFlagsReturnsGpr] 457 } else { 458 vec![ 459 IsleConstructor::RetGpr, 460 IsleConstructor::RetMemorySideEffect, 461 ] 462 } 463 } 464 }, 465 }, 466 }, 467 [one, two] => { 468 assert!(!format.eflags.is_read()); 469 match (one.location.kind(), two.location.kind()) { 470 (FixedReg(_) | Reg(_), FixedReg(_) | Reg(_)) => { 471 vec![IsleConstructor::RetValueRegs] 472 } 473 (Reg(r), Mem(_)) | (Mem(_) | RegMem(_), Reg(r) | FixedReg(r)) => { 474 assert!(matches!(r.reg_class().unwrap(), RegClass::Gpr)); 475 vec![IsleConstructor::RetGpr] 476 } 477 other => panic!("unsupported number of write operands {other:?}"), 478 } 479 } 480 [one, two, three] => { 481 assert!(!format.eflags.is_read()); 482 match ( 483 one.location.kind(), 484 two.location.kind(), 485 three.location.kind(), 486 ) { 487 (FixedReg(_), FixedReg(_), Mem(_)) => { 488 vec![IsleConstructor::RetValueRegs] 489 } 490 other => panic!("unsupported number of write operands {other:?}"), 491 } 492 } 493 494 other => panic!("unsupported number of write operands {other:?}"), 495 } 496 } 497 498 /// Generate a "raw" constructor that simply constructs, but does not emit 499 /// the assembly instruction: 500 /// 501 /// ```text 502 /// (decl x64_<inst>_raw (<params>) AssemblerOutputs) 503 /// (extern constructor x64_<inst>_raw x64_<inst>_raw) 504 /// ``` 505 /// 506 /// Using the "raw" constructor, we also generate "emitter" constructors 507 /// (see [`IsleConstructor`]). E.g., instructions that write to a register 508 /// will return the register: 509 /// 510 /// ```text 511 /// (decl x64_<inst> (<params>) Gpr) 512 /// (rule (x64_<inst> <params>) (emit_ret_gpr (x64_<inst>_raw <params>))) 513 /// ``` 514 /// 515 /// For instructions that write to memory, we also generate an "emitter" 516 /// constructor with the `_mem` suffix: 517 /// 518 /// ```text 519 /// (decl x64_<inst>_mem (<params>) SideEffectNoResult) 520 /// (rule (x64_<inst>_mem <params>) (defer_side_effect (x64_<inst>_raw <params>))) 521 /// ``` 522 /// 523 /// # Panics 524 /// 525 /// This function panics if the instruction has no operands. 526 fn generate_isle_inst_decls(f: &mut Formatter, inst: &Inst) { 527 let (trap_type, trap_name) = if inst.has_trap { 528 (Some("TrapCode".to_string()), Some("trap".to_string())) 529 } else { 530 (None, None) 531 }; 532 533 // First declare the "raw" constructor which is implemented in Rust 534 // with `generate_isle_macro` above. This is an "extern" constructor 535 // with relatively raw types. This is not intended to be used by 536 // general lowering rules in ISLE. 537 let struct_name = inst.name(); 538 let raw_name = format!("x64_{struct_name}_raw"); 539 let params = inst 540 .format 541 .operands 542 .iter() 543 .filter(|o| is_raw_operand_param(o)) 544 .collect::<Vec<_>>(); 545 let raw_param_tys = params 546 .iter() 547 .map(|o| isle_param_raw(o)) 548 .chain(trap_type.clone()) 549 .collect::<Vec<_>>() 550 .join(" "); 551 fmtln!(f, "(decl {raw_name} ({raw_param_tys}) AssemblerOutputs)"); 552 fmtln!(f, "(extern constructor {raw_name} {raw_name})"); 553 554 // Next, for each "emitter" ISLE constructor being generated, synthesize 555 // a pure-ISLE constructor which delegates appropriately to the `*_raw` 556 // constructor above. 557 // 558 // The main purpose of these constructors is to have faithful type 559 // signatures for the SSA nature of VCode/ISLE, effectively translating 560 // x64's type system to ISLE/VCode's type system. 561 // 562 // Note that the `params` from above are partitioned into explicit/implicit 563 // parameters based on the `ctor` we're generating here. That means, for 564 // example, that a write-only `RegMem` will have one ctor which produces a 565 // register that takes no argument, but one ctors will take an `Amode` which 566 // is the address to write to. 567 for ctor in isle_constructors(&inst.format) { 568 let suffix = ctor.suffix(); 569 let rule_name = format!("x64_{struct_name}{suffix}"); 570 let result_ty = ctor.result_ty(); 571 let mut explicit_params = Vec::new(); 572 let mut implicit_params = Vec::new(); 573 for param in params.iter() { 574 if param.mutability.is_read() || ctor.includes_write_only_reg_mem() { 575 explicit_params.push(param); 576 } else { 577 implicit_params.push(param); 578 } 579 } 580 assert!(implicit_params.len() <= 1); 581 let param_tys = explicit_params 582 .iter() 583 .map(|o| isle_param_for_ctor(o, ctor)) 584 .chain(trap_type.clone()) 585 .collect::<Vec<_>>() 586 .join(" "); 587 let param_names = explicit_params 588 .iter() 589 .map(|o| o.location.to_string()) 590 .chain(trap_name.clone()) 591 .collect::<Vec<_>>() 592 .join(" "); 593 let convert = ctor.conversion_constructor(); 594 595 // Generate implicit parameters to the `*_raw` constructor. Currently 596 // this is only destination gpr/xmm temps if the result of this entire 597 // constructor is a gpr/xmm register. 598 let implicit_params = implicit_params 599 .iter() 600 .map(|o| { 601 assert!(matches!(o.location.kind(), OperandKind::RegMem(_))); 602 match ctor { 603 IsleConstructor::RetMemorySideEffect | IsleConstructor::NoReturnSideEffect => { 604 unreachable!() 605 } 606 IsleConstructor::RetGpr | IsleConstructor::ConsumesFlagsReturnsGpr => { 607 "(temp_writable_gpr)" 608 } 609 IsleConstructor::RetXmm => "(temp_writable_xmm)", 610 IsleConstructor::RetValueRegs | IsleConstructor::ProducesFlagsSideEffect => { 611 todo!() 612 } 613 } 614 }) 615 .collect::<Vec<_>>() 616 .join(" "); 617 618 fmtln!(f, "(decl {rule_name} ({param_tys}) {result_ty})"); 619 fmtln!( 620 f, 621 "(rule ({rule_name} {param_names}) ({convert} ({raw_name} {implicit_params} {param_names})))" 622 ); 623 624 if let Some(alternate) = &inst.alternate { 625 // We currently plan to use alternate instructions for SSE/AVX 626 // pairs, so we expect the one of the registers to be an XMM 627 // register. In the future we could relax this, but would need to 628 // handle more cases below. 629 assert!( 630 inst.format 631 .operands 632 .iter() 633 .any(|o| matches!(o.location.reg_class(), Some(RegClass::Xmm))) 634 ); 635 let param_tys = if alternate.feature == Feature::avx { 636 param_tys.replace("Aligned", "") 637 } else { 638 param_tys 639 }; 640 let alt_feature = alternate.feature.to_string(); 641 let alt_name = &alternate.name; 642 let rule_name_or_feat = format!("{rule_name}_or_{alt_feature}"); 643 fmtln!(f, "(decl {rule_name_or_feat} ({param_tys}) {result_ty})"); 644 fmtln!(f, "(rule 1 ({rule_name_or_feat} {param_names})"); 645 f.indent(|f| { 646 fmtln!(f, "(if-let true (has_{alt_feature}))"); 647 fmtln!(f, "(x64_{alt_name}{suffix} {param_names}))"); 648 }); 649 fmtln!( 650 f, 651 "(rule 0 ({rule_name_or_feat} {param_names}) ({rule_name} {param_names}))" 652 ); 653 } 654 } 655 } 656 657 /// Generate the ISLE definitions that match the `isle_assembler_methods!` macro 658 /// above. 659 pub fn generate_isle(f: &mut Formatter, insts: &[Inst]) { 660 fmtln!(f, "(type AssemblerOutputs (enum"); 661 fmtln!(f, " ;; Used for instructions that have ISLE"); 662 fmtln!(f, " ;; `SideEffect`s (memory stores, traps,"); 663 fmtln!(f, " ;; etc.) and do not return a `Value`."); 664 fmtln!(f, " (SideEffect (inst MInst))"); 665 fmtln!(f, " ;; Used for instructions that return a"); 666 fmtln!(f, " ;; GPR (including `GprMem` variants with"); 667 fmtln!(f, " ;; a GPR as the first argument)."); 668 fmtln!(f, " (RetGpr (inst MInst) (gpr Gpr))"); 669 fmtln!(f, " ;; Used for instructions that return an"); 670 fmtln!(f, " ;; XMM register."); 671 fmtln!(f, " (RetXmm (inst MInst) (xmm Xmm))"); 672 fmtln!(f, " ;; Used for multi-return instructions."); 673 fmtln!(f, " (RetValueRegs (inst MInst) (regs ValueRegs))"); 674 fmtln!( 675 f, 676 " ;; https://github.com/bytecodealliance/wasmtime/pull/10276" 677 ); 678 fmtln!(f, "))"); 679 f.empty_line(); 680 681 fmtln!(f, ";; Directly emit instructions that return a GPR."); 682 fmtln!(f, "(decl emit_ret_gpr (AssemblerOutputs) Gpr)"); 683 fmtln!(f, "(rule (emit_ret_gpr (AssemblerOutputs.RetGpr inst gpr))"); 684 fmtln!(f, " (let ((_ Unit (emit inst))) gpr))"); 685 f.empty_line(); 686 687 fmtln!(f, ";; Directly emit instructions that return an"); 688 fmtln!(f, ";; XMM register."); 689 fmtln!(f, "(decl emit_ret_xmm (AssemblerOutputs) Xmm)"); 690 fmtln!(f, "(rule (emit_ret_xmm (AssemblerOutputs.RetXmm inst xmm))"); 691 fmtln!(f, " (let ((_ Unit (emit inst))) xmm))"); 692 f.empty_line(); 693 694 fmtln!(f, ";; Directly emit instructions that return multiple"); 695 fmtln!(f, ";; registers (e.g. `mul`)."); 696 fmtln!(f, "(decl emit_ret_value_regs (AssemblerOutputs) ValueRegs)"); 697 fmtln!( 698 f, 699 "(rule (emit_ret_value_regs (AssemblerOutputs.RetValueRegs inst regs))" 700 ); 701 fmtln!(f, " (let ((_ Unit (emit inst))) regs))"); 702 f.empty_line(); 703 704 fmtln!(f, ";; Pass along the side-effecting instruction"); 705 fmtln!(f, ";; for later emission."); 706 fmtln!( 707 f, 708 "(decl defer_side_effect (AssemblerOutputs) SideEffectNoResult)" 709 ); 710 fmtln!( 711 f, 712 "(rule (defer_side_effect (AssemblerOutputs.SideEffect inst))" 713 ); 714 fmtln!(f, " (SideEffectNoResult.Inst inst))"); 715 f.empty_line(); 716 717 for inst in insts { 718 if include_inst(inst) { 719 generate_isle_inst_decls(f, inst); 720 f.empty_line(); 721 } 722 } 723 } 724 725 /// Returns whether `o` is included in the `*_raw` constructor generated in 726 /// ISLE/Rust. 727 /// 728 /// This notably includes all operands that are read as those are the 729 /// data-dependencies of an instruction. This additionally includes, though, 730 /// write-only `RegMem` operands. In this situation the `RegMem` operand is 731 /// dynamically a `RegMem::Reg`, a temp register synthesized in ISLE, or a 732 /// `RegMem::Mem`, an operand from the constructor of the original entrypoint 733 /// itself. 734 fn is_raw_operand_param(o: &Operand) -> bool { 735 o.mutability.is_read() 736 || matches!( 737 o.location.kind(), 738 OperandKind::RegMem(_) | OperandKind::Mem(_) 739 ) 740 } 741