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<'_> {
operands(&self) -> impl Iterator<Item = Operand<'_>> + use<'_>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 
skip(&self) -> bool88     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 
generate_rust(filename: &str, out_dir: &Path) -> Result<(), Error>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 
generate_isle(filename: &str, out_dir: &Path) -> Result<(), Error>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