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