1 use cranelift_srcgen::error::Error; 2 use std::path::Path; 3 4 struct Inst<'a> { 5 snake_name: &'a str, 6 name: &'a str, 7 fields: &'a [(&'a str, &'a str)], 8 } 9 10 macro_rules! define { 11 ( 12 $( 13 $( #[$attr:meta] )* 14 $snake_name:ident = $name:ident $( { $( $field:ident : $field_ty:ty ),* } )? ; 15 )* 16 ) => { 17 &[$(Inst { 18 snake_name: stringify!($snake_name), 19 name: stringify!($name), 20 fields: &[$($( (stringify!($field), stringify!($field_ty)), )*)?], 21 }),*] 22 // helpers.push_str(concat!("(define pulley_", stringify!($snake_name), " (")); 23 }; 24 } 25 26 const OPS: &[Inst<'_>] = pulley_interpreter::for_each_op!(define); 27 const EXTENDED_OPS: &[Inst<'_>] = pulley_interpreter::for_each_extended_op!(define); 28 29 enum Operand<'a> { 30 Normal { 31 name: &'a str, 32 ty: &'a str, 33 }, 34 Writable { 35 name: &'a str, 36 ty: &'a str, 37 }, 38 TrapCode { 39 name: &'a str, 40 ty: &'a str, 41 }, 42 Binop { 43 dst: &'a str, 44 src1: &'a str, 45 src2: &'a str, 46 }, 47 } 48 49 impl Inst<'_> { 50 fn operands(&self) -> impl Iterator<Item = Operand<'_>> + use<'_> { 51 self.fields 52 .iter() 53 .map(|(name, ty)| match (*name, *ty) { 54 ("operands", binop) => { 55 // Parse "BinaryOperands < A >"` as A/A/A 56 // Parse "BinaryOperands < A, B >"` as A/B/A 57 // Parse "BinaryOperands < A, B, C >"` as A/B/C 58 let mut parts = binop 59 .strip_prefix("BinaryOperands <") 60 .unwrap() 61 .strip_suffix(">") 62 .unwrap() 63 .trim() 64 .split(',') 65 .map(|x| x.trim()); 66 let dst = parts.next().unwrap(); 67 let src1 = parts.next().unwrap_or(dst); 68 let src2 = parts.next().unwrap_or(dst); 69 Operand::Binop { dst, src1, src2 } 70 } 71 (name, ty) if name.starts_with("dst") => Operand::Writable { name, ty }, 72 (name, "UpperRegSet < XReg >") => Operand::Normal { 73 name, 74 ty: "UpperXRegSet", 75 }, 76 (name, ty) => Operand::Normal { name, ty }, 77 }) 78 .chain(if self.name.contains("Trap") { 79 Some(Operand::TrapCode { 80 name: "code", 81 ty: "TrapCode", 82 }) 83 } else { 84 None 85 }) 86 } 87 88 fn skip(&self) -> bool { 89 match self.name { 90 // Skip instructions related to control-flow as those require 91 // special handling with `MachBuffer`. 92 "Jump" => true, 93 n if n.starts_with("Call") => true, 94 95 // Skip special instructions not used in Cranelift. 96 "XPush32Many" | "XPush64Many" | "XPop32Many" | "XPop64Many" => true, 97 98 // Skip more branching-related instructions. 99 n => n.starts_with("Br"), 100 } 101 } 102 } 103 104 pub fn generate_rust(filename: &str, out_dir: &Path) -> Result<(), Error> { 105 let mut rust = String::new(); 106 107 // Generate a pretty-printing method for debugging. 108 rust.push_str("pub fn print(inst: &RawInst) -> String {\n"); 109 rust.push_str("match inst {\n"); 110 for inst @ Inst { name, .. } in OPS.iter().chain(EXTENDED_OPS) { 111 if inst.skip() { 112 continue; 113 } 114 115 let mut pat = String::new(); 116 let mut locals = String::new(); 117 let mut format_string = String::new(); 118 format_string.push_str(inst.snake_name); 119 for (i, op) in inst.operands().enumerate() { 120 match op { 121 Operand::Normal { name, ty } | Operand::Writable { name, ty } => { 122 pat.push_str(name); 123 pat.push_str(","); 124 125 if i > 0 { 126 format_string.push_str(","); 127 } 128 129 if ty == "UpperXRegSet" { 130 format_string.push_str(" {"); 131 format_string.push_str(name); 132 format_string.push_str(":?}"); 133 continue; 134 } 135 136 format_string.push_str(" {"); 137 format_string.push_str(name); 138 format_string.push_str("}"); 139 if ty.contains("Reg") { 140 if matches!(op, Operand::Writable { .. }) { 141 locals.push_str(&format!("let {name} = reg_name(*{name}.to_reg());\n")); 142 } else { 143 locals.push_str(&format!("let {name} = reg_name(**{name});\n")); 144 } 145 } 146 } 147 Operand::TrapCode { name, ty: _ } => { 148 pat.push_str(name); 149 pat.push_str(","); 150 format_string.push_str(&format!(" // trap={{{name}:?}}")); 151 } 152 Operand::Binop { src2, .. } => { 153 pat.push_str("dst, src1, src2,"); 154 format_string.push_str(" {dst}, {src1}, {src2}"); 155 locals.push_str(&format!("let dst = reg_name(*dst.to_reg());\n")); 156 locals.push_str(&format!("let src1 = reg_name(**src1);\n")); 157 if src2.contains("Reg") { 158 locals.push_str(&format!("let src2 = reg_name(**src2);\n")); 159 } 160 } 161 } 162 } 163 164 rust.push_str(&format!( 165 " 166 RawInst::{name} {{ {pat} }} => {{ 167 {locals} 168 format!(\"{format_string}\") 169 }} 170 " 171 )); 172 } 173 rust.push_str("}\n"); 174 rust.push_str("}\n"); 175 176 // Generate `get_operands` to feed information to regalloc 177 rust.push_str( 178 "pub fn get_operands(inst: &mut RawInst, collector: &mut impl OperandVisitor) {\n", 179 ); 180 rust.push_str("match inst {\n"); 181 for inst @ Inst { name, .. } in OPS.iter().chain(EXTENDED_OPS) { 182 if inst.skip() { 183 continue; 184 } 185 186 let mut pat = String::new(); 187 let mut uses = Vec::new(); 188 let mut defs = Vec::new(); 189 let mut addrs = Vec::new(); 190 for op in inst.operands() { 191 match op { 192 // `{Push,Pop}Frame{Save,Restore}` doesn't participate in 193 // register allocation. 194 Operand::Normal { 195 name: _, 196 ty: "UpperXRegSet", 197 } if *name == "PushFrameSave" || *name == "PopFrameRestore" => {} 198 199 Operand::Normal { name, ty } => { 200 if ty.contains("Reg") { 201 uses.push(name); 202 pat.push_str(name); 203 pat.push_str(","); 204 } else if ty.starts_with("Addr") { 205 addrs.push(name); 206 pat.push_str(name); 207 pat.push_str(","); 208 } 209 } 210 Operand::Writable { name, ty } => { 211 if ty.contains("Reg") { 212 defs.push(name); 213 pat.push_str(name); 214 pat.push_str(","); 215 } 216 } 217 Operand::TrapCode { .. } => {} 218 Operand::Binop { src2, .. } => { 219 pat.push_str("dst, src1,"); 220 uses.push("src1"); 221 defs.push("dst"); 222 if src2.contains("Reg") { 223 pat.push_str("src2,"); 224 uses.push("src2"); 225 } 226 } 227 } 228 } 229 230 let uses = uses 231 .iter() 232 .map(|u| format!("collector.reg_use({u});\n")) 233 .collect::<String>(); 234 let defs = defs 235 .iter() 236 .map(|u| format!("collector.reg_def({u});\n")) 237 .collect::<String>(); 238 let addrs = addrs 239 .iter() 240 .map(|u| format!("{u}.collect_operands(collector);\n")) 241 .collect::<String>(); 242 243 rust.push_str(&format!( 244 " 245 RawInst::{name} {{ {pat} .. }} => {{ 246 {uses} 247 {defs} 248 {addrs} 249 }} 250 " 251 )); 252 } 253 rust.push_str("}\n"); 254 rust.push_str("}\n"); 255 256 // Generate an emission method 257 rust.push_str("pub fn emit<P>(inst: &RawInst, sink: &mut MachBuffer<InstAndKind<P>>)\n"); 258 rust.push_str(" where P: PulleyTargetKind,\n"); 259 rust.push_str("{\n"); 260 rust.push_str("match *inst {\n"); 261 for inst @ Inst { 262 name, snake_name, .. 263 } in OPS.iter().chain(EXTENDED_OPS) 264 { 265 if inst.skip() { 266 continue; 267 } 268 269 let mut pat = String::new(); 270 let mut args = String::new(); 271 let mut trap = String::new(); 272 for op in inst.operands() { 273 match op { 274 Operand::Normal { name, ty: _ } | Operand::Writable { name, ty: _ } => { 275 pat.push_str(name); 276 pat.push_str(","); 277 278 args.push_str(name); 279 args.push_str(","); 280 } 281 Operand::TrapCode { name, ty: _ } => { 282 pat.push_str(name); 283 pat.push_str(","); 284 trap.push_str(&format!("sink.add_trap({name});\n")); 285 } 286 Operand::Binop { .. } => { 287 pat.push_str("dst, src1, src2,"); 288 args.push_str( 289 "pulley_interpreter::regs::BinaryOperands::new(dst, src1, src2),", 290 ); 291 } 292 } 293 } 294 295 rust.push_str(&format!( 296 " 297 RawInst::{name} {{ {pat} }} => {{ 298 {trap} 299 pulley_interpreter::encode::{snake_name}(sink, {args}) 300 }} 301 " 302 )); 303 } 304 rust.push_str("}\n"); 305 rust.push_str("}\n"); 306 307 std::fs::write(out_dir.join(filename), rust)?; 308 Ok(()) 309 } 310 311 pub fn generate_isle(filename: &str, out_dir: &Path) -> Result<(), Error> { 312 let mut isle = String::new(); 313 314 // Generate the `RawInst` enum 315 isle.push_str("(type RawInst (enum\n"); 316 for inst in OPS.iter().chain(EXTENDED_OPS) { 317 if inst.skip() { 318 continue; 319 } 320 isle.push_str(" ("); 321 isle.push_str(inst.name); 322 for op in inst.operands() { 323 match op { 324 Operand::Normal { name, ty } | Operand::TrapCode { name, ty } => { 325 isle.push_str(&format!("\n ({name} {ty})")); 326 } 327 Operand::Writable { name, ty } => { 328 isle.push_str(&format!("\n ({name} Writable{ty})")); 329 } 330 Operand::Binop { dst, src1, src2 } => { 331 isle.push_str(&format!("\n (dst Writable{dst})")); 332 isle.push_str(&format!("\n (src1 {src1})")); 333 isle.push_str(&format!("\n (src2 {src2})")); 334 } 335 } 336 } 337 isle.push_str(")\n"); 338 } 339 isle.push_str("))\n"); 340 341 // Generate the `pulley_*` constructors with a `decl` and a `rule`. 342 for inst @ Inst { 343 name, snake_name, .. 344 } in OPS.iter().chain(EXTENDED_OPS) 345 { 346 if inst.skip() { 347 continue; 348 } 349 // generate `decl` and `rule` at the same time, placing the `rule` in 350 // temporary storage on the side. Makes generation a bit easier to read 351 // as opposed to doing the decl first then the rule. 352 let mut rule = String::new(); 353 isle.push_str(&format!("(decl pulley_{snake_name} (")); 354 rule.push_str(&format!("(rule (pulley_{snake_name} ")); 355 let mut results = Vec::new(); 356 let mut ops = Vec::new(); 357 for op in inst.operands() { 358 match op { 359 Operand::Normal { name, ty } | Operand::TrapCode { name, ty } => { 360 isle.push_str(ty); 361 rule.push_str(name); 362 ops.push(name); 363 } 364 Operand::Writable { name: _, ty } => { 365 results.push(ty); 366 } 367 Operand::Binop { dst, src1, src2 } => { 368 isle.push_str(&format!("{src1} {src2}")); 369 rule.push_str("src1 src2"); 370 ops.push("src1"); 371 ops.push("src2"); 372 results.push(dst); 373 } 374 } 375 isle.push_str(" "); 376 rule.push_str(" "); 377 } 378 isle.push_str(") "); 379 rule.push_str(")"); 380 let ops = ops.join(" "); 381 match &results[..] { 382 [result] => { 383 isle.push_str(result); 384 rule.push_str(&format!( 385 " 386 (let ( 387 (dst Writable{result} (temp_writable_{})) 388 (_ Unit (emit (RawInst.{name} dst {ops}))) 389 ) 390 dst))\ 391 \n", 392 result.to_lowercase() 393 )); 394 } 395 [a, b] => { 396 isle.push_str("ValueRegs"); 397 rule.push_str(&format!( 398 " 399 (let ( 400 (dst1 Writable{a} (temp_writable_{})) 401 (dst2 Writable{b} (temp_writable_{})) 402 (_ Unit (emit (RawInst.{name} dst1 dst2 {ops}))) 403 ) 404 (value_regs dst1 dst2)))\ 405 \n", 406 a.to_lowercase(), 407 b.to_lowercase(), 408 )); 409 } 410 [] => { 411 isle.push_str("SideEffectNoResult"); 412 rule.push_str(&format!( 413 " (SideEffectNoResult.Inst (RawInst.{name} {ops})))\n", 414 )); 415 } 416 other => panic!("cannot codegen results {other:?}"), 417 } 418 isle.push_str(")\n"); 419 420 isle.push_str(&rule); 421 } 422 423 std::fs::write(out_dir.join(filename), isle)?; 424 Ok(()) 425 } 426