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