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