use cranelift_assembler_x64_meta::dsl::{
Format, Inst, Location, Mutability, Operand, OperandKind, RegClass,
};
use cranelift_srcgen::{Formatter, fmtln};
const ASM: &str = "cranelift_assembler_x64";
pub fn rust_param_raw(op: &Operand) -> String {
match op.location.kind() {
OperandKind::Imm(loc) => {
let bits = loc.bits();
if op.extension.is_sign_extended() {
format!("i{bits}")
} else {
format!("u{bits}")
}
}
OperandKind::RegMem(rm) => {
let reg = rm.reg_class().unwrap();
let aligned = if op.align { "Aligned" } else { "" };
format!("&{reg}Mem{aligned}")
}
OperandKind::Mem(_) => {
format!("&SyntheticAmode")
}
OperandKind::Reg(r) | OperandKind::FixedReg(r) => r.reg_class().unwrap().to_string(),
}
}
pub fn rust_convert_isle_to_assembler(op: &Operand) -> String {
match op.location.kind() {
OperandKind::Imm(loc) => {
let bits = loc.bits();
let ty = if op.extension.is_sign_extended() {
"Simm"
} else {
"Imm"
};
format!("{ASM}::{ty}{bits}::new({loc})")
}
OperandKind::FixedReg(r) => {
let reg = r.reg_class().unwrap().to_string().to_lowercase();
match op.mutability {
Mutability::Read => format!("{ASM}::Fixed({r})"),
Mutability::Write => {
format!("{ASM}::Fixed(self.temp_writable_{reg}())")
}
Mutability::ReadWrite => {
format!("self.convert_{reg}_to_assembler_fixed_read_write_{reg}({r})")
}
}
}
OperandKind::Reg(r) => {
let reg = r.reg_class().unwrap();
let reg_lower = reg.to_string().to_lowercase();
match op.mutability {
Mutability::Read => {
format!("{ASM}::{reg}::new({r})")
}
Mutability::Write => {
format!("{ASM}::{reg}::new(self.temp_writable_{reg_lower}())")
}
Mutability::ReadWrite => {
format!("self.convert_{reg_lower}_to_assembler_read_write_{reg_lower}({r})")
}
}
}
OperandKind::RegMem(rm) => {
let reg = rm.reg_class().unwrap().to_string().to_lowercase();
let mut_ = op.mutability.generate_snake_case();
let align = if op.align { "_aligned" } else { "" };
format!("self.convert_{reg}_mem_to_assembler_{mut_}_{reg}_mem{align}({rm})")
}
OperandKind::Mem(mem) => format!("self.convert_amode_to_assembler_amode({mem})"),
}
}
pub fn generate_macro_inst_fn(f: &mut Formatter, inst: &Inst) {
use OperandKind::*;
let struct_name = inst.name();
let operands = inst.format.operands.iter().cloned().collect::<Vec<_>>();
let results = operands
.iter()
.filter(|o| o.mutability.is_write())
.collect::<Vec<_>>();
let rust_params = operands
.iter()
.filter(|o| is_raw_operand_param(o))
.map(|o| format!("{}: {}", o.location, rust_param_raw(o)))
.chain(if inst.has_trap {
Some(format!("trap: &TrapCode"))
} else {
None
})
.collect::<Vec<_>>()
.join(", ");
f.add_block(
&format!("fn x64_{struct_name}_raw(&mut self, {rust_params}) -> AssemblerOutputs"),
|f| {
f.comment("Convert ISLE types to assembler types.");
for op in operands.iter() {
let loc = op.location;
let cvt = rust_convert_isle_to_assembler(op);
fmtln!(f, "let {loc} = {cvt};");
}
let mut args = operands
.iter()
.map(|o| format!("{}.clone()", o.location))
.collect::<Vec<_>>();
if inst.has_trap {
args.push(format!("{ASM}::TrapCode(trap.as_raw())"));
}
let args = args.join(", ");
f.empty_line();
f.comment("Build the instruction.");
fmtln!(
f,
"let inst = {ASM}::inst::{struct_name}::new({args}).into();"
);
fmtln!(f, "let inst = MInst::External {{ inst }};");
f.empty_line();
f.comment("Return a type ISLE can work with.");
let access_reg = |op: &Operand| match op.mutability {
Mutability::Read => unreachable!(),
Mutability::Write => "to_reg()",
Mutability::ReadWrite => "write.to_reg()",
};
let ty_var_of_reg = |loc: Location| {
let ty = loc.reg_class().unwrap().to_string();
let var = ty.to_lowercase();
(ty, var)
};
match results.as_slice() {
[] => fmtln!(f, "SideEffectNoResult::Inst(inst)"),
[op] => match op.location.kind() {
Imm(_) => unreachable!(),
Reg(r) | FixedReg(r) => {
let (ty, var) = ty_var_of_reg(r);
fmtln!(f, "let {var} = {r}.as_ref().{};", access_reg(op));
fmtln!(f, "AssemblerOutputs::Ret{ty} {{ inst, {var} }}");
}
Mem(_) => {
fmtln!(f, "AssemblerOutputs::SideEffect {{ inst }}")
}
RegMem(rm) => {
let (ty, var) = ty_var_of_reg(rm);
f.add_block(&format!("match {rm}"), |f| {
f.add_block(&format!("{ASM}::{ty}Mem::{ty}(reg) => "), |f| {
fmtln!(f, "let {var} = reg.{};", access_reg(op));
fmtln!(f, "AssemblerOutputs::Ret{ty} {{ inst, {var} }} ");
});
f.add_block(&format!("{ASM}::{ty}Mem::Mem(_) => "), |f| {
fmtln!(f, "AssemblerOutputs::SideEffect {{ inst }} ");
});
});
}
},
[op1, op2] => match (op1.location.kind(), op2.location.kind()) {
(FixedReg(loc1) | Reg(loc1), FixedReg(loc2) | Reg(loc2)) => {
fmtln!(f, "let one = {loc1}.as_ref().{}.to_reg();", access_reg(op1));
fmtln!(f, "let two = {loc2}.as_ref().{}.to_reg();", access_reg(op2));
fmtln!(f, "let regs = ValueRegs::two(one, two);");
fmtln!(f, "AssemblerOutputs::RetValueRegs {{ inst, regs }}");
}
_ => unimplemented!("unhandled results: {results:?}"),
},
_ => panic!("instruction has more than one result"),
}
},
);
}
pub fn generate_rust_macro(f: &mut Formatter, insts: &[Inst]) {
fmtln!(f, "#[doc(hidden)]");
fmtln!(f, "macro_rules! isle_assembler_methods {{");
f.indent(|f| {
fmtln!(f, "() => {{");
f.indent(|f| {
for inst in insts {
generate_macro_inst_fn(f, inst);
}
});
fmtln!(f, "}};");
});
fmtln!(f, "}}");
}
pub fn isle_param_raw(op: &Operand) -> String {
match op.location.kind() {
OperandKind::Imm(loc) => {
let bits = loc.bits();
if op.extension.is_sign_extended() {
format!("i{bits}")
} else {
format!("u{bits}")
}
}
OperandKind::Reg(r) | OperandKind::FixedReg(r) => r.reg_class().unwrap().to_string(),
OperandKind::Mem(_) => {
if op.align {
unimplemented!("no way yet to mark an SyntheticAmode as aligned")
} else {
"SyntheticAmode".to_string()
}
}
OperandKind::RegMem(rm) => {
let reg = rm.reg_class().unwrap();
let aligned = if op.align { "Aligned" } else { "" };
format!("{reg}Mem{aligned}")
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum IsleConstructor {
RetMemorySideEffect,
RetGpr,
RetXmm,
RetValueRegs,
}
impl IsleConstructor {
pub fn result_ty(&self) -> &'static str {
match self {
IsleConstructor::RetMemorySideEffect => "SideEffectNoResult",
IsleConstructor::RetGpr => "Gpr",
IsleConstructor::RetXmm => "Xmm",
IsleConstructor::RetValueRegs => "ValueRegs",
}
}
pub fn conversion_constructor(&self) -> &'static str {
match self {
IsleConstructor::RetMemorySideEffect => "defer_side_effect",
IsleConstructor::RetGpr => "emit_ret_gpr",
IsleConstructor::RetXmm => "emit_ret_xmm",
IsleConstructor::RetValueRegs => "emit_ret_value_regs",
}
}
pub fn suffix(&self) -> &'static str {
match self {
IsleConstructor::RetMemorySideEffect => "_mem",
IsleConstructor::RetGpr | IsleConstructor::RetXmm | IsleConstructor::RetValueRegs => "",
}
}
pub fn includes_write_only_reg_mem(&self) -> bool {
match self {
IsleConstructor::RetMemorySideEffect => true,
IsleConstructor::RetGpr | IsleConstructor::RetXmm | IsleConstructor::RetValueRegs => {
false
}
}
}
}
pub fn isle_param_for_ctor(op: &Operand, ctor: IsleConstructor) -> String {
match op.location.kind() {
OperandKind::RegMem(_) if op.mutability.is_write() => match ctor {
IsleConstructor::RetMemorySideEffect => "SyntheticAmode".to_string(),
IsleConstructor::RetGpr => "Gpr".to_string(),
IsleConstructor::RetXmm => "Xmm".to_string(),
IsleConstructor::RetValueRegs => "ValueRegs".to_string(),
},
_ => isle_param_raw(op),
}
}
pub fn isle_constructors(format: &Format) -> Vec<IsleConstructor> {
use Mutability::*;
use OperandKind::*;
let write_operands = format
.operands
.iter()
.filter(|o| o.mutability.is_write())
.collect::<Vec<_>>();
match &write_operands[..] {
[] => unimplemented!(
"if you truly need this (and not a `SideEffect*`), add a `NoReturn` variant to `AssemblerOutputs`"
),
[one] => match one.mutability {
Read => unreachable!(),
ReadWrite | Write => match one.location.kind() {
Imm(_) => unreachable!(),
Reg(r) | FixedReg(r) => match r.reg_class().unwrap() {
RegClass::Xmm => vec![IsleConstructor::RetXmm],
RegClass::Gpr => vec![IsleConstructor::RetGpr],
},
Mem(_) => vec![IsleConstructor::RetMemorySideEffect],
RegMem(rm) => match rm.reg_class().unwrap() {
RegClass::Xmm => vec![
IsleConstructor::RetXmm,
IsleConstructor::RetMemorySideEffect,
],
RegClass::Gpr => vec![
IsleConstructor::RetGpr,
IsleConstructor::RetMemorySideEffect,
],
},
},
},
[one, two] => {
assert!(matches!(one.location.kind(), FixedReg(_) | Reg(_)));
assert!(matches!(two.location.kind(), FixedReg(_) | Reg(_)));
vec![IsleConstructor::RetValueRegs]
}
other => panic!("unsupported number of write operands {other:?}"),
}
}
pub fn generate_isle_inst_decls(f: &mut Formatter, inst: &Inst) {
let (trap_type, trap_name) = if inst.has_trap {
(Some("TrapCode".to_string()), Some("trap".to_string()))
} else {
(None, None)
};
let struct_name = inst.name();
let raw_name = format!("x64_{struct_name}_raw");
let params = inst
.format
.operands
.iter()
.filter(|o| is_raw_operand_param(o))
.collect::<Vec<_>>();
let raw_param_tys = params
.iter()
.map(|o| isle_param_raw(o))
.chain(trap_type.clone())
.collect::<Vec<_>>()
.join(" ");
fmtln!(f, "(decl {raw_name} ({raw_param_tys}) AssemblerOutputs)");
fmtln!(f, "(extern constructor {raw_name} {raw_name})");
for ctor in isle_constructors(&inst.format) {
let suffix = ctor.suffix();
let rule_name = format!("x64_{struct_name}{suffix}");
let result_ty = ctor.result_ty();
let mut explicit_params = Vec::new();
let mut implicit_params = Vec::new();
for param in params.iter() {
if param.mutability.is_read() || ctor.includes_write_only_reg_mem() {
explicit_params.push(param);
} else {
implicit_params.push(param);
}
}
assert!(implicit_params.len() <= 1);
let param_tys = explicit_params
.iter()
.map(|o| isle_param_for_ctor(o, ctor))
.chain(trap_type.clone())
.collect::<Vec<_>>()
.join(" ");
let param_names = explicit_params
.iter()
.map(|o| o.location.to_string())
.chain(trap_name.clone())
.collect::<Vec<_>>()
.join(" ");
let convert = ctor.conversion_constructor();
let implicit_params = implicit_params
.iter()
.map(|o| {
assert!(matches!(o.location.kind(), OperandKind::RegMem(_)));
match ctor {
IsleConstructor::RetMemorySideEffect => unreachable!(),
IsleConstructor::RetGpr => "(temp_writable_gpr)",
IsleConstructor::RetXmm => "(temp_writable_xmm)",
IsleConstructor::RetValueRegs => todo!(),
}
})
.collect::<Vec<_>>()
.join(" ");
fmtln!(f, "(decl {rule_name} ({param_tys}) {result_ty})");
fmtln!(
f,
"(rule ({rule_name} {param_names}) ({convert} ({raw_name} {implicit_params} {param_names})))"
);
}
}
pub fn generate_isle(f: &mut Formatter, insts: &[Inst]) {
fmtln!(f, "(type AssemblerOutputs (enum");
fmtln!(f, " ;; Used for instructions that have ISLE");
fmtln!(f, " ;; `SideEffect`s (memory stores, traps,");
fmtln!(f, " ;; etc.) and do not return a `Value`.");
fmtln!(f, " (SideEffect (inst MInst))");
fmtln!(f, " ;; Used for instructions that return a");
fmtln!(f, " ;; GPR (including `GprMem` variants with");
fmtln!(f, " ;; a GPR as the first argument).");
fmtln!(f, " (RetGpr (inst MInst) (gpr Gpr))");
fmtln!(f, " ;; Used for instructions that return an");
fmtln!(f, " ;; XMM register.");
fmtln!(f, " (RetXmm (inst MInst) (xmm Xmm))");
fmtln!(f, " ;; Used for multi-return instructions.");
fmtln!(f, " (RetValueRegs (inst MInst) (regs ValueRegs))");
fmtln!(
f,
" ;; https://github.com/bytecodealliance/wasmtime/pull/10276"
);
fmtln!(f, "))");
f.empty_line();
fmtln!(f, ";; Directly emit instructions that return a GPR.");
fmtln!(f, "(decl emit_ret_gpr (AssemblerOutputs) Gpr)");
fmtln!(f, "(rule (emit_ret_gpr (AssemblerOutputs.RetGpr inst gpr))");
fmtln!(f, " (let ((_ Unit (emit inst))) gpr))");
f.empty_line();
fmtln!(f, ";; Directly emit instructions that return an");
fmtln!(f, ";; XMM register.");
fmtln!(f, "(decl emit_ret_xmm (AssemblerOutputs) Xmm)");
fmtln!(f, "(rule (emit_ret_xmm (AssemblerOutputs.RetXmm inst xmm))");
fmtln!(f, " (let ((_ Unit (emit inst))) xmm))");
f.empty_line();
fmtln!(f, ";; Directly emit instructions that return multiple");
fmtln!(f, ";; registers (e.g. `mul`).");
fmtln!(f, "(decl emit_ret_value_regs (AssemblerOutputs) ValueRegs)");
fmtln!(
f,
"(rule (emit_ret_value_regs (AssemblerOutputs.RetValueRegs inst regs))"
);
fmtln!(f, " (let ((_ Unit (emit inst))) regs))");
f.empty_line();
fmtln!(f, ";; Pass along the side-effecting instruction");
fmtln!(f, ";; for later emission.");
fmtln!(
f,
"(decl defer_side_effect (AssemblerOutputs) SideEffectNoResult)"
);
fmtln!(
f,
"(rule (defer_side_effect (AssemblerOutputs.SideEffect inst))"
);
fmtln!(f, " (SideEffectNoResult.Inst inst))");
f.empty_line();
for inst in insts {
generate_isle_inst_decls(f, inst);
f.empty_line();
}
}
fn is_raw_operand_param(o: &Operand) -> bool {
o.mutability.is_read()
|| matches!(
o.location.kind(),
OperandKind::RegMem(_) | OperandKind::Mem(_)
)
}