Skip to content

Commit 0747529

Browse files
committed
asm: generate ISLE for multi-return constructors--AssemblerOutputs::RetValueRegs
Certain `mul*` instructions write to multiple registers. For register allocation, Cranelift needs to know about all of these registers. This change uses the pre-existing pattern of returning a `ValueRegs` type to indicate this. This change is limited to what is needed now: the only multi-return needed now uses two fixed registers.
1 parent cbc65cb commit 0747529

File tree

2 files changed

+90
-59
lines changed

2 files changed

+90
-59
lines changed

cranelift/assembler-x64/meta/src/dsl.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ pub use encoding::{
1313
};
1414
pub use encoding::{rex, vex};
1515
pub use features::{ALL_FEATURES, Feature, Features};
16-
pub use format::{Extension, Format, Location, Mutability, Operand, OperandKind};
16+
pub use format::{Extension, Format, Location, Mutability, Operand, OperandKind, RegClass};
1717
pub use format::{align, fmt, implicit, r, rw, sxl, sxq, sxw, w};
1818

1919
/// Abbreviated constructor for an x64 instruction.

cranelift/codegen/meta/src/gen_asm.rs

Lines changed: 89 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Generate the Cranelift-specific integration of the x64 assembler.
22
33
use cranelift_assembler_x64_meta::dsl::{
4-
Format, Inst, Mutability, Operand, OperandKind, format::RegClass,
4+
Format, Inst, Location, Mutability, Operand, OperandKind, RegClass,
55
};
66
use cranelift_srcgen::{Formatter, fmtln};
77

@@ -89,10 +89,8 @@ pub fn rust_convert_isle_to_assembler(op: &Operand) -> String {
8989
/// This function panics if the instruction has no operands.
9090
pub fn generate_macro_inst_fn(f: &mut Formatter, inst: &Inst) {
9191
let struct_name = inst.name();
92-
let operands = inst.format.operands.iter().collect::<Vec<_>>();
93-
let results = inst
94-
.format
95-
.operands
92+
let operands = inst.format.operands.iter().cloned().collect::<Vec<_>>();
93+
let results = operands
9694
.iter()
9795
.filter(|o| o.mutability.is_write())
9896
.collect::<Vec<_>>();
@@ -105,6 +103,7 @@ pub fn generate_macro_inst_fn(f: &mut Formatter, inst: &Inst) {
105103
f.add_block(
106104
&format!("fn x64_{struct_name}_raw(&mut self, {rust_params}) -> AssemblerOutputs"),
107105
|f| {
106+
f.comment("Convert ISLE types to assembler types.");
108107
for op in operands.iter() {
109108
let loc = op.location;
110109
let cvt = rust_convert_isle_to_assembler(op);
@@ -115,60 +114,69 @@ pub fn generate_macro_inst_fn(f: &mut Formatter, inst: &Inst) {
115114
.map(|o| format!("{}.clone()", o.location))
116115
.collect::<Vec<_>>();
117116
let args = args.join(", ");
117+
f.empty_line();
118+
119+
f.comment("Build the instruction.");
118120
fmtln!(
119121
f,
120122
"let inst = {ASM}::inst::{struct_name}::new({args}).into();"
121123
);
122124
fmtln!(f, "let inst = MInst::External {{ inst }};");
125+
f.empty_line();
123126

124-
use cranelift_assembler_x64_meta::dsl::Mutability::*;
127+
// When an instruction writes to an operand, Cranelift expects a
128+
// returned value to use in other instructions: we return this
129+
// information in the `AssemblerOutputs` struct defined in ISLE
130+
// (below). The general rule here is that memory stores will create
131+
// a `SideEffect` whereas for write or read-write registers we will
132+
// return some form of `Ret*`.
133+
f.comment("Return a type ISLE can work with.");
134+
let access_reg = |op: &Operand| match op.mutability {
135+
Mutability::Read => unreachable!(),
136+
Mutability::Write => "to_reg()",
137+
Mutability::ReadWrite => "write.to_reg()",
138+
};
139+
let ty_var_of_reg = |loc: Location| {
140+
let ty = loc.reg_class().unwrap().to_string();
141+
let var = ty.to_lowercase();
142+
(ty, var)
143+
};
125144
match results.as_slice() {
126145
[] => fmtln!(f, "SideEffectNoResult::Inst(inst)"),
127-
[one] => match one.mutability {
128-
Read => unreachable!(),
129-
Write => match one.location.kind() {
130-
// One write-only register output? Output the
131-
// instruction and that register.
132-
OperandKind::Reg(r) => {
133-
let ty = r.reg_class().unwrap().to_string();
134-
let var = ty.to_lowercase();
135-
fmtln!(f, "let {var} = {r}.as_ref().to_reg();");
136-
fmtln!(f, "AssemblerOutputs::Ret{ty} {{ inst, {var} }}");
137-
}
138-
_ => unimplemented!(),
139-
},
140-
ReadWrite => match one.location.kind() {
141-
OperandKind::Imm(_) => unreachable!(),
142-
// One read/write register output? Output the instruction
143-
// and that register.
144-
OperandKind::Reg(r) | OperandKind::FixedReg(r) => {
145-
let ty = r.reg_class().unwrap().to_string();
146-
let var = ty.to_lowercase();
147-
fmtln!(f, "let {var} = {r}.as_ref().write.to_reg();",);
148-
fmtln!(f, "AssemblerOutputs::Ret{ty} {{ inst, {var} }}");
149-
}
150-
// One read/write memory operand? Output a side effect.
151-
OperandKind::Mem(_) => {
152-
fmtln!(f, "AssemblerOutputs::SideEffect {{ inst }}")
153-
}
154-
// One read/write regmem output? We need to output
155-
// everything and it'll internally disambiguate which was
156-
// emitted (e.g. the mem variant or the register variant).
157-
OperandKind::RegMem(rm) => {
158-
assert_eq!(results.len(), 1);
159-
let ty = rm.reg_class().unwrap().to_string();
160-
let var = ty.to_lowercase();
161-
f.add_block(&format!("match {rm}"), |f| {
162-
f.add_block(&format!("asm::{ty}Mem::{ty}(reg) => "), |f| {
163-
fmtln!(f, "let {var} = reg.write.to_reg();");
164-
fmtln!(f, "AssemblerOutputs::Ret{ty} {{ inst, {var} }} ");
165-
});
166-
f.add_block(&format!("asm::{ty}Mem::Mem(_) => "), |f| {
167-
fmtln!(f, "AssemblerOutputs::SideEffect {{ inst }} ");
168-
});
146+
[op] => match op.location.kind() {
147+
OperandKind::Imm(_) => unreachable!(),
148+
OperandKind::Reg(r) | OperandKind::FixedReg(r) => {
149+
let (ty, var) = ty_var_of_reg(r);
150+
fmtln!(f, "let {var} = {r}.as_ref().{};", access_reg(op));
151+
fmtln!(f, "AssemblerOutputs::Ret{ty} {{ inst, {var} }}");
152+
}
153+
OperandKind::Mem(_) => {
154+
fmtln!(f, "AssemblerOutputs::SideEffect {{ inst }}")
155+
}
156+
OperandKind::RegMem(rm) => {
157+
let (ty, var) = ty_var_of_reg(rm);
158+
f.add_block(&format!("match {rm}"), |f| {
159+
f.add_block(&format!("{ASM}::{ty}Mem::{ty}(reg) => "), |f| {
160+
fmtln!(f, "let {var} = reg.{};", access_reg(op));
161+
fmtln!(f, "AssemblerOutputs::Ret{ty} {{ inst, {var} }} ");
162+
});
163+
f.add_block(&format!("{ASM}::{ty}Mem::Mem(_) => "), |f| {
164+
fmtln!(f, "AssemblerOutputs::SideEffect {{ inst }} ");
169165
});
170-
}
171-
},
166+
});
167+
}
168+
},
169+
// For now, we assume that if there are two results, they are
170+
// coming from a register-writing instruction like `mul`. The
171+
// `match` below can be expanded as needed.
172+
[op1, op2] => match (op1.location.kind(), op2.location.kind()) {
173+
(OperandKind::FixedReg(loc1), OperandKind::FixedReg(loc2)) => {
174+
fmtln!(f, "let one = {loc1}.as_ref().{}.to_reg();", access_reg(op1));
175+
fmtln!(f, "let two = {loc2}.as_ref().{}.to_reg();", access_reg(op2));
176+
fmtln!(f, "let regs = ValueRegs::two(one, two);");
177+
fmtln!(f, "AssemblerOutputs::RetValueRegs {{ inst, regs }}");
178+
}
179+
_ => unimplemented!("unhandled results: {results:?}"),
172180
},
173181
_ => panic!("instruction has more than one result"),
174182
}
@@ -234,13 +242,16 @@ pub enum IsleConstructor {
234242
/// a result in memory, however.
235243
RetMemorySideEffect,
236244

237-
/// This constructor produces a `Gpr` value, meaning that it will write the
238-
/// result to a `Gpr`.
245+
/// This constructor produces a `Gpr` value, meaning that the instruction
246+
/// will write its result to a single GPR register.
239247
RetGpr,
240248

241-
/// This constructor produces an `Xmm` value, meaning that it will write the
242-
/// result to an `Xmm`.
249+
/// This is similar to `RetGpr`, but for XMM registers.
243250
RetXmm,
251+
252+
/// This "special" constructor captures multiple written-to registers (e.g.
253+
/// `mul`).
254+
RetValueRegs,
244255
}
245256

246257
impl IsleConstructor {
@@ -250,6 +261,7 @@ impl IsleConstructor {
250261
IsleConstructor::RetMemorySideEffect => "SideEffectNoResult",
251262
IsleConstructor::RetGpr => "Gpr",
252263
IsleConstructor::RetXmm => "Xmm",
264+
IsleConstructor::RetValueRegs => "ValueRegs",
253265
}
254266
}
255267

@@ -260,15 +272,15 @@ impl IsleConstructor {
260272
IsleConstructor::RetMemorySideEffect => "defer_side_effect",
261273
IsleConstructor::RetGpr => "emit_ret_gpr",
262274
IsleConstructor::RetXmm => "emit_ret_xmm",
275+
IsleConstructor::RetValueRegs => "emit_ret_value_regs",
263276
}
264277
}
265278

266279
/// Returns the suffix used in the ISLE constructor name.
267280
pub fn suffix(&self) -> &'static str {
268281
match self {
269282
IsleConstructor::RetMemorySideEffect => "_mem",
270-
IsleConstructor::RetGpr => "",
271-
IsleConstructor::RetXmm => "",
283+
IsleConstructor::RetGpr | IsleConstructor::RetXmm | IsleConstructor::RetValueRegs => "",
272284
}
273285
}
274286
}
@@ -285,6 +297,7 @@ pub fn isle_param_for_ctor(op: &Operand, ctor: IsleConstructor) -> String {
285297
IsleConstructor::RetMemorySideEffect => "Amode".to_string(),
286298
IsleConstructor::RetGpr => "Gpr".to_string(),
287299
IsleConstructor::RetXmm => "Xmm".to_string(),
300+
IsleConstructor::RetValueRegs => "ValueRegs".to_string(),
288301
},
289302

290303
// everything else is the same as the "raw" variant
@@ -336,6 +349,14 @@ pub fn isle_constructors(format: &Format) -> Vec<IsleConstructor> {
336349
},
337350
},
338351
},
352+
[one, two] => {
353+
// For now, we assume that if there are two results, they are coming
354+
// from a register-writing instruction like `mul`. This can be
355+
// expanded as needed.
356+
assert!(matches!(one.location.kind(), FixedReg(_)));
357+
assert!(matches!(two.location.kind(), FixedReg(_)));
358+
vec![IsleConstructor::RetValueRegs]
359+
}
339360
other => panic!("unsupported number of write operands {other:?}"),
340361
}
341362
}
@@ -435,8 +456,8 @@ pub fn generate_isle(f: &mut Formatter, insts: &[Inst]) {
435456
fmtln!(f, " ;; Used for instructions that return an");
436457
fmtln!(f, " ;; XMM register.");
437458
fmtln!(f, " (RetXmm (inst MInst) (xmm Xmm))");
438-
fmtln!(f, " ;; TODO: eventually add more variants for");
439-
fmtln!(f, " ;; multi-return, XMM, etc.; see");
459+
fmtln!(f, " ;; Used for multi-return instructions.");
460+
fmtln!(f, " (RetValueRegs (inst MInst) (regs ValueRegs))");
440461
fmtln!(
441462
f,
442463
" ;; https://github.com/bytecodealliance/wasmtime/pull/10276"
@@ -457,6 +478,16 @@ pub fn generate_isle(f: &mut Formatter, insts: &[Inst]) {
457478
fmtln!(f, " (let ((_ Unit (emit inst))) xmm))");
458479
f.empty_line();
459480

481+
fmtln!(f, ";; Directly emit instructions that return multiple");
482+
fmtln!(f, ";; registers (e.g. `mul`).");
483+
fmtln!(f, "(decl emit_ret_value_regs (AssemblerOutputs) ValueRegs)");
484+
fmtln!(
485+
f,
486+
"(rule (emit_ret_value_regs (AssemblerOutputs.RetValueRegs inst regs))"
487+
);
488+
fmtln!(f, " (let ((_ Unit (emit inst))) regs))");
489+
f.empty_line();
490+
460491
fmtln!(f, ";; Pass along the side-effecting instruction");
461492
fmtln!(f, ";; for later emission.");
462493
fmtln!(

0 commit comments

Comments
 (0)