Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/cranelift/codegen/meta/src/pulley.rs
1692 views
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
427