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 302 impl IsleConstructor { 303 /// Returns the result type, in ISLE, that this constructor generates. 304 fn result_ty(&self) -> &'static str { 305 match self { 306 IsleConstructor::RetGpr => "Gpr", 307 IsleConstructor::RetXmm => "Xmm", 308 IsleConstructor::RetValueRegs => "ValueRegs", 309 IsleConstructor::NoReturnSideEffect | IsleConstructor::RetMemorySideEffect => { 310 "SideEffectNoResult" 311 } 312 } 313 } 314 315 /// Returns the constructor used to convert an `AssemblerOutput` into the 316 /// type returned by [`Self::result_ty`]. 317 fn conversion_constructor(&self) -> &'static str { 318 match self { 319 IsleConstructor::NoReturnSideEffect | IsleConstructor::RetMemorySideEffect => { 320 "defer_side_effect" 321 } 322 IsleConstructor::RetGpr => "emit_ret_gpr", 323 IsleConstructor::RetXmm => "emit_ret_xmm", 324 IsleConstructor::RetValueRegs => "emit_ret_value_regs", 325 } 326 } 327 328 /// Returns the suffix used in the ISLE constructor name. 329 fn suffix(&self) -> &'static str { 330 match self { 331 IsleConstructor::RetMemorySideEffect => "_mem", 332 IsleConstructor::RetGpr 333 | IsleConstructor::RetXmm 334 | IsleConstructor::RetValueRegs 335 | IsleConstructor::NoReturnSideEffect => "", 336 } 337 } 338 339 /// Returns whether this constructor will include a write-only `RegMem` 340 /// operand as an argument to the constructor. 341 /// 342 /// Memory-based ctors take an `Amode`, but register-based ctors don't take 343 /// the result as an argument and instead manufacture it internally. 344 fn includes_write_only_reg_mem(&self) -> bool { 345 match self { 346 IsleConstructor::RetMemorySideEffect => true, 347 IsleConstructor::RetGpr 348 | IsleConstructor::RetXmm 349 | IsleConstructor::RetValueRegs 350 | IsleConstructor::NoReturnSideEffect => false, 351 } 352 } 353 } 354 355 /// Returns the parameter type used for the `IsleConstructor` variant 356 /// provided. 357 fn isle_param_for_ctor(op: &Operand, ctor: IsleConstructor) -> String { 358 match op.location.kind() { 359 // Writable `RegMem` operands are special here: in one constructor 360 // it's operating on memory so the argument is `Amode` and in the 361 // other constructor it's operating on registers so the argument is 362 // a `Gpr`. 363 OperandKind::RegMem(_) if op.mutability.is_write() => match ctor { 364 IsleConstructor::RetMemorySideEffect => "SyntheticAmode".to_string(), 365 IsleConstructor::NoReturnSideEffect => "".to_string(), 366 IsleConstructor::RetGpr => "Gpr".to_string(), 367 IsleConstructor::RetXmm => "Xmm".to_string(), 368 IsleConstructor::RetValueRegs => "ValueRegs".to_string(), 369 }, 370 371 // everything else is the same as the "raw" variant 372 _ => isle_param_raw(op), 373 } 374 } 375 376 /// Returns the ISLE constructors that are going to be used when generating 377 /// this instruction. 378 /// 379 /// Note that one instruction might need multiple constructors, such as one 380 /// for operating on memory and one for operating on registers. 381 fn isle_constructors(format: &Format) -> Vec<IsleConstructor> { 382 use Mutability::*; 383 use OperandKind::*; 384 385 let write_operands = format 386 .operands 387 .iter() 388 .filter(|o| o.mutability.is_write()) 389 .collect::<Vec<_>>(); 390 match &write_operands[..] { 391 [] => vec![IsleConstructor::NoReturnSideEffect], 392 [one] => match one.mutability { 393 Read => unreachable!(), 394 ReadWrite | Write => match one.location.kind() { 395 Imm(_) => unreachable!(), 396 // One read/write register output? Output the instruction 397 // and that register. 398 Reg(r) | FixedReg(r) => match r.reg_class().unwrap() { 399 RegClass::Xmm => vec![IsleConstructor::RetXmm], 400 RegClass::Gpr => vec![IsleConstructor::RetGpr], 401 }, 402 // One read/write memory operand? Output a side effect. 403 Mem(_) => vec![IsleConstructor::RetMemorySideEffect], 404 // One read/write reg-mem output? We need constructors for 405 // both variants. 406 RegMem(rm) => match rm.reg_class().unwrap() { 407 RegClass::Xmm => vec![ 408 IsleConstructor::RetXmm, 409 IsleConstructor::RetMemorySideEffect, 410 ], 411 RegClass::Gpr => vec![ 412 IsleConstructor::RetGpr, 413 IsleConstructor::RetMemorySideEffect, 414 ], 415 }, 416 }, 417 }, 418 [one, two] => match (one.location.kind(), two.location.kind()) { 419 (FixedReg(_) | Reg(_), FixedReg(_) | Reg(_)) => { 420 vec![IsleConstructor::RetValueRegs] 421 } 422 (Reg(r), Mem(_)) | (Mem(_) | RegMem(_), Reg(r) | FixedReg(r)) => { 423 assert!(matches!(r.reg_class().unwrap(), RegClass::Gpr)); 424 vec![IsleConstructor::RetGpr] 425 } 426 other => panic!("unsupported number of write operands {other:?}"), 427 }, 428 [one, two, three] => match ( 429 one.location.kind(), 430 two.location.kind(), 431 three.location.kind(), 432 ) { 433 (FixedReg(_), FixedReg(_), Mem(_)) => { 434 vec![IsleConstructor::RetValueRegs] 435 } 436 other => panic!("unsupported number of write operands {other:?}"), 437 }, 438 439 other => panic!("unsupported number of write operands {other:?}"), 440 } 441 } 442 443 /// Generate a "raw" constructor that simply constructs, but does not emit 444 /// the assembly instruction: 445 /// 446 /// ```text 447 /// (decl x64_<inst>_raw (<params>) AssemblerOutputs) 448 /// (extern constructor x64_<inst>_raw x64_<inst>_raw) 449 /// ``` 450 /// 451 /// Using the "raw" constructor, we also generate "emitter" constructors 452 /// (see [`IsleConstructor`]). E.g., instructions that write to a register 453 /// will return the register: 454 /// 455 /// ```text 456 /// (decl x64_<inst> (<params>) Gpr) 457 /// (rule (x64_<inst> <params>) (emit_ret_gpr (x64_<inst>_raw <params>))) 458 /// ``` 459 /// 460 /// For instructions that write to memory, we also generate an "emitter" 461 /// constructor with the `_mem` suffix: 462 /// 463 /// ```text 464 /// (decl x64_<inst>_mem (<params>) SideEffectNoResult) 465 /// (rule (x64_<inst>_mem <params>) (defer_side_effect (x64_<inst>_raw <params>))) 466 /// ``` 467 /// 468 /// # Panics 469 /// 470 /// This function panics if the instruction has no operands. 471 fn generate_isle_inst_decls(f: &mut Formatter, inst: &Inst) { 472 let (trap_type, trap_name) = if inst.has_trap { 473 (Some("TrapCode".to_string()), Some("trap".to_string())) 474 } else { 475 (None, None) 476 }; 477 478 // First declare the "raw" constructor which is implemented in Rust 479 // with `generate_isle_macro` above. This is an "extern" constructor 480 // with relatively raw types. This is not intended to be used by 481 // general lowering rules in ISLE. 482 let struct_name = inst.name(); 483 let raw_name = format!("x64_{struct_name}_raw"); 484 let params = inst 485 .format 486 .operands 487 .iter() 488 .filter(|o| is_raw_operand_param(o)) 489 .collect::<Vec<_>>(); 490 let raw_param_tys = params 491 .iter() 492 .map(|o| isle_param_raw(o)) 493 .chain(trap_type.clone()) 494 .collect::<Vec<_>>() 495 .join(" "); 496 fmtln!(f, "(decl {raw_name} ({raw_param_tys}) AssemblerOutputs)"); 497 fmtln!(f, "(extern constructor {raw_name} {raw_name})"); 498 499 // Next, for each "emitter" ISLE constructor being generated, synthesize 500 // a pure-ISLE constructor which delegates appropriately to the `*_raw` 501 // constructor above. 502 // 503 // The main purpose of these constructors is to have faithful type 504 // signatures for the SSA nature of VCode/ISLE, effectively translating 505 // x64's type system to ISLE/VCode's type system. 506 // 507 // Note that the `params` from above are partitioned into explicit/implicit 508 // parameters based on the `ctor` we're generating here. That means, for 509 // example, that a write-only `RegMem` will have one ctor which produces a 510 // register that takes no argument, but one ctors will take an `Amode` which 511 // is the address to write to. 512 for ctor in isle_constructors(&inst.format) { 513 let suffix = ctor.suffix(); 514 let rule_name = format!("x64_{struct_name}{suffix}"); 515 let result_ty = ctor.result_ty(); 516 let mut explicit_params = Vec::new(); 517 let mut implicit_params = Vec::new(); 518 for param in params.iter() { 519 if param.mutability.is_read() || ctor.includes_write_only_reg_mem() { 520 explicit_params.push(param); 521 } else { 522 implicit_params.push(param); 523 } 524 } 525 assert!(implicit_params.len() <= 1); 526 let param_tys = explicit_params 527 .iter() 528 .map(|o| isle_param_for_ctor(o, ctor)) 529 .chain(trap_type.clone()) 530 .collect::<Vec<_>>() 531 .join(" "); 532 let param_names = explicit_params 533 .iter() 534 .map(|o| o.location.to_string()) 535 .chain(trap_name.clone()) 536 .collect::<Vec<_>>() 537 .join(" "); 538 let convert = ctor.conversion_constructor(); 539 540 // Generate implicit parameters to the `*_raw` constructor. Currently 541 // this is only destination gpr/xmm temps if the result of this entire 542 // constructor is a gpr/xmm register. 543 let implicit_params = implicit_params 544 .iter() 545 .map(|o| { 546 assert!(matches!(o.location.kind(), OperandKind::RegMem(_))); 547 match ctor { 548 IsleConstructor::RetMemorySideEffect | IsleConstructor::NoReturnSideEffect => { 549 unreachable!() 550 } 551 IsleConstructor::RetGpr => "(temp_writable_gpr)", 552 IsleConstructor::RetXmm => "(temp_writable_xmm)", 553 IsleConstructor::RetValueRegs => todo!(), 554 } 555 }) 556 .collect::<Vec<_>>() 557 .join(" "); 558 559 fmtln!(f, "(decl {rule_name} ({param_tys}) {result_ty})"); 560 fmtln!( 561 f, 562 "(rule ({rule_name} {param_names}) ({convert} ({raw_name} {implicit_params} {param_names})))" 563 ); 564 565 if let Some(alternate) = &inst.alternate { 566 // We currently plan to use alternate instructions for SSE/AVX 567 // pairs, so we expect the destination register to be an XMM 568 // register. In the future we could relax this, but would need to 569 // handle more cases below. 570 assert!(matches!( 571 inst.format.operands.first().unwrap().location.reg_class(), 572 Some(RegClass::Xmm) 573 )); 574 let param_tys = if alternate.feature == Feature::avx { 575 param_tys.replace("Aligned", "") 576 } else { 577 param_tys 578 }; 579 let name = inst.name(); 580 let alt_feature = alternate.feature.to_string(); 581 let alt_name = &alternate.name; 582 let rule_name = format!("x64_{name}_or_{alt_feature}"); 583 fmtln!(f, "(decl {rule_name} ({param_tys}) {result_ty})"); 584 fmtln!(f, "(rule 1 ({rule_name} {param_names})"); 585 f.indent(|f| { 586 fmtln!(f, "(if-let true (use_{alt_feature}))"); 587 fmtln!(f, "(x64_{alt_name} {param_names}))"); 588 }); 589 fmtln!( 590 f, 591 "(rule 0 ({rule_name} {param_names}) (x64_{name} {param_names}))" 592 ); 593 } 594 } 595 } 596 597 /// Generate the ISLE definitions that match the `isle_assembler_methods!` macro 598 /// above. 599 pub fn generate_isle(f: &mut Formatter, insts: &[Inst]) { 600 fmtln!(f, "(type AssemblerOutputs (enum"); 601 fmtln!(f, " ;; Used for instructions that have ISLE"); 602 fmtln!(f, " ;; `SideEffect`s (memory stores, traps,"); 603 fmtln!(f, " ;; etc.) and do not return a `Value`."); 604 fmtln!(f, " (SideEffect (inst MInst))"); 605 fmtln!(f, " ;; Used for instructions that return a"); 606 fmtln!(f, " ;; GPR (including `GprMem` variants with"); 607 fmtln!(f, " ;; a GPR as the first argument)."); 608 fmtln!(f, " (RetGpr (inst MInst) (gpr Gpr))"); 609 fmtln!(f, " ;; Used for instructions that return an"); 610 fmtln!(f, " ;; XMM register."); 611 fmtln!(f, " (RetXmm (inst MInst) (xmm Xmm))"); 612 fmtln!(f, " ;; Used for multi-return instructions."); 613 fmtln!(f, " (RetValueRegs (inst MInst) (regs ValueRegs))"); 614 fmtln!( 615 f, 616 " ;; https://github.com/bytecodealliance/wasmtime/pull/10276" 617 ); 618 fmtln!(f, "))"); 619 f.empty_line(); 620 621 fmtln!(f, ";; Directly emit instructions that return a GPR."); 622 fmtln!(f, "(decl emit_ret_gpr (AssemblerOutputs) Gpr)"); 623 fmtln!(f, "(rule (emit_ret_gpr (AssemblerOutputs.RetGpr inst gpr))"); 624 fmtln!(f, " (let ((_ Unit (emit inst))) gpr))"); 625 f.empty_line(); 626 627 fmtln!(f, ";; Directly emit instructions that return an"); 628 fmtln!(f, ";; XMM register."); 629 fmtln!(f, "(decl emit_ret_xmm (AssemblerOutputs) Xmm)"); 630 fmtln!(f, "(rule (emit_ret_xmm (AssemblerOutputs.RetXmm inst xmm))"); 631 fmtln!(f, " (let ((_ Unit (emit inst))) xmm))"); 632 f.empty_line(); 633 634 fmtln!(f, ";; Directly emit instructions that return multiple"); 635 fmtln!(f, ";; registers (e.g. `mul`)."); 636 fmtln!(f, "(decl emit_ret_value_regs (AssemblerOutputs) ValueRegs)"); 637 fmtln!( 638 f, 639 "(rule (emit_ret_value_regs (AssemblerOutputs.RetValueRegs inst regs))" 640 ); 641 fmtln!(f, " (let ((_ Unit (emit inst))) regs))"); 642 f.empty_line(); 643 644 fmtln!(f, ";; Pass along the side-effecting instruction"); 645 fmtln!(f, ";; for later emission."); 646 fmtln!( 647 f, 648 "(decl defer_side_effect (AssemblerOutputs) SideEffectNoResult)" 649 ); 650 fmtln!( 651 f, 652 "(rule (defer_side_effect (AssemblerOutputs.SideEffect inst))" 653 ); 654 fmtln!(f, " (SideEffectNoResult.Inst inst))"); 655 f.empty_line(); 656 657 for inst in insts { 658 if include_inst(inst) { 659 generate_isle_inst_decls(f, inst); 660 f.empty_line(); 661 } 662 } 663 } 664 665 /// Returns whether `o` is included in the `*_raw` constructor generated in 666 /// ISLE/Rust. 667 /// 668 /// This notably includes all operands that are read as those are the 669 /// data-dependencies of an instruction. This additionally includes, though, 670 /// write-only `RegMem` operands. In this situation the `RegMem` operand is 671 /// dynamically a `RegMem::Reg`, a temp register synthesized in ISLE, or a 672 /// `RegMem::Mem`, an operand from the constructor of the original entrypoint 673 /// itself. 674 fn is_raw_operand_param(o: &Operand) -> bool { 675 o.mutability.is_read() 676 || matches!( 677 o.location.kind(), 678 OperandKind::RegMem(_) | OperandKind::Mem(_) 679 ) 680 } 681