use std::fmt;
use std::rc::Rc;
use cranelift_codegen_shared::constant_hash;
use crate::cdsl::camel_case;
use crate::cdsl::formats::InstructionFormat;
use crate::cdsl::instructions::{AllInstructions, Instruction};
use crate::cdsl::operands::Operand;
use crate::cdsl::typevar::{TypeSet, TypeVar};
use crate::error;
use crate::srcgen::{Formatter, Match};
use crate::unique_table::{UniqueSeqTable, UniqueTable};
const TYPESET_LIMIT: usize = 0xff;
fn gen_formats(formats: &[Rc<InstructionFormat>], fmt: &mut Formatter) {
fmt.doc_comment(
r#"
An instruction format
Every opcode has a corresponding instruction format
which is represented by both the `InstructionFormat`
and the `InstructionData` enums.
"#,
);
fmt.line("#[derive(Copy, Clone, PartialEq, Eq, Debug)]");
fmt.line("pub enum InstructionFormat {");
fmt.indent(|fmt| {
for format in formats {
fmt.doc_comment(format.to_string());
fmtln!(fmt, "{},", format.name);
}
});
fmt.line("}");
fmt.empty_line();
fmt.line("impl<'a> From<&'a InstructionData> for InstructionFormat {");
fmt.indent(|fmt| {
fmt.line("fn from(inst: &'a InstructionData) -> Self {");
fmt.indent(|fmt| {
let mut m = Match::new("*inst");
for format in formats {
m.arm(
format!("InstructionData::{}", format.name),
vec![".."],
format!("Self::{}", format.name),
);
}
fmt.add_match(m);
});
fmt.line("}");
});
fmt.line("}");
fmt.empty_line();
}
fn gen_instruction_data(formats: &[Rc<InstructionFormat>], fmt: &mut Formatter) {
fmt.line("#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]");
fmt.line(r#"#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]"#);
fmt.line("#[allow(missing_docs)]");
fmtln!(fmt, "pub enum InstructionData {");
fmt.indent(|fmt| {
for format in formats {
fmtln!(fmt, "{} {{", format.name);
fmt.indent(|fmt| {
fmt.line("opcode: Opcode,");
if format.has_value_list {
fmt.line("args: ValueList,");
} else if format.num_value_operands == 1 {
fmt.line("arg: Value,");
} else if format.num_value_operands > 0 {
fmtln!(fmt, "args: [Value; {}],", format.num_value_operands);
}
match format.num_block_operands {
0 => (),
1 => fmt.line("destination: ir::BlockCall,"),
2 => fmtln!(
fmt,
"blocks: [ir::BlockCall; {}],",
format.num_block_operands
),
n => panic!("Too many block operands in instruction: {}", n),
}
for field in &format.imm_fields {
fmtln!(fmt, "{}: {},", field.member, field.kind.rust_type);
}
});
fmtln!(fmt, "},");
}
});
fmt.line("}");
}
fn gen_arguments_method(formats: &[Rc<InstructionFormat>], fmt: &mut Formatter, is_mut: bool) {
let (method, mut_, rslice, as_slice) = if is_mut {
(
"arguments_mut",
"mut ",
"core::slice::from_mut",
"as_mut_slice",
)
} else {
("arguments", "", "core::slice::from_ref", "as_slice")
};
fmtln!(
fmt,
"pub fn {}<'a>(&'a {}self, pool: &'a {}ir::ValueListPool) -> &{}[Value] {{",
method,
mut_,
mut_,
mut_
);
fmt.indent(|fmt| {
let mut m = Match::new("*self");
for format in formats {
let name = format!("Self::{}", format.name);
if format.has_value_list {
m.arm(
name,
vec![format!("ref {}args", mut_), "..".to_string()],
format!("args.{}(pool)", as_slice),
);
continue;
}
let mut fields = Vec::new();
let arg = if format.num_value_operands == 0 {
format!("&{}[]", mut_)
} else if format.num_value_operands == 1 {
fields.push(format!("ref {}arg", mut_));
format!("{}(arg)", rslice)
} else {
let arg = format!("args_arity{}", format.num_value_operands);
fields.push(format!("args: ref {}{}", mut_, arg));
arg
};
fields.push("..".into());
m.arm(name, fields, arg);
}
fmt.add_match(m);
});
fmtln!(fmt, "}");
}
fn gen_instruction_data_impl(formats: &[Rc<InstructionFormat>], fmt: &mut Formatter) {
fmt.line("impl InstructionData {");
fmt.indent(|fmt| {
fmt.doc_comment("Get the opcode of this instruction.");
fmt.line("pub fn opcode(&self) -> Opcode {");
fmt.indent(|fmt| {
let mut m = Match::new("*self");
for format in formats {
m.arm(format!("Self::{}", format.name), vec!["opcode", ".."],
"opcode".to_string());
}
fmt.add_match(m);
});
fmt.line("}");
fmt.empty_line();
fmt.doc_comment("Get the controlling type variable operand.");
fmt.line("pub fn typevar_operand(&self, pool: &ir::ValueListPool) -> Option<Value> {");
fmt.indent(|fmt| {
let mut m = Match::new("*self");
for format in formats {
let name = format!("Self::{}", format.name);
if format.typevar_operand.is_none() {
m.arm(name, vec![".."], "None".to_string());
} else if format.has_value_list {
m.arm(name, vec!["ref args", ".."], format!("args.get({}, pool)", format.typevar_operand.unwrap()));
} else if format.num_value_operands == 1 {
m.arm(name, vec!["arg", ".."], "Some(arg)".to_string());
} else {
let args = format!("args_arity{}", format.num_value_operands);
m.arm(name, vec![format!("args: ref {}", args), "..".to_string()],
format!("Some({}[{}])", args, format.typevar_operand.unwrap()));
}
}
fmt.add_match(m);
});
fmt.line("}");
fmt.empty_line();
fmt.doc_comment("Get the value arguments to this instruction.");
gen_arguments_method(formats, fmt, false);
fmt.empty_line();
fmt.doc_comment(r#"Get mutable references to the value arguments to this
instruction."#);
gen_arguments_method(formats, fmt, true);
fmt.empty_line();
fmt.doc_comment(r#"
Compare two `InstructionData` for equality.
This operation requires a reference to a `ValueListPool` to
determine if the contents of any `ValueLists` are equal.
This operation takes a closure that is allowed to map each
argument value to some other value before the instructions
are compared. This allows various forms of canonicalization.
"#);
fmt.line("pub fn eq<F: Fn(Value) -> Value>(&self, other: &Self, pool: &ir::ValueListPool, mapper: F) -> bool {");
fmt.indent(|fmt| {
fmt.line("if ::core::mem::discriminant(self) != ::core::mem::discriminant(other) {");
fmt.indent(|fmt| {
fmt.line("return false;");
});
fmt.line("}");
fmt.line("match (self, other) {");
fmt.indent(|fmt| {
for format in formats {
let name = format!("&Self::{}", format.name);
let mut members = vec!["opcode"];
let args_eq = if format.has_value_list {
members.push("args");
Some("args1.as_slice(pool).iter().zip(args2.as_slice(pool).iter()).all(|(a, b)| mapper(*a) == mapper(*b))")
} else if format.num_value_operands == 1 {
members.push("arg");
Some("mapper(*arg1) == mapper(*arg2)")
} else if format.num_value_operands > 0 {
members.push("args");
Some("args1.iter().zip(args2.iter()).all(|(a, b)| mapper(*a) == mapper(*b))")
} else {
None
};
let blocks_eq = match format.num_block_operands {
0 => None,
1 => {
members.push("destination");
Some("destination1 == destination2")
},
_ => {
members.push("blocks");
Some("blocks1.iter().zip(blocks2.iter()).all(|(a, b)| a.block(pool) == b.block(pool))")
}
};
for field in &format.imm_fields {
members.push(field.member);
}
let pat1 = members.iter().map(|x| format!("{}: ref {}1", x, x)).collect::<Vec<_>>().join(", ");
let pat2 = members.iter().map(|x| format!("{}: ref {}2", x, x)).collect::<Vec<_>>().join(", ");
fmtln!(fmt, "({} {{ {} }}, {} {{ {} }}) => {{", name, pat1, name, pat2);
fmt.indent(|fmt| {
fmt.line("opcode1 == opcode2");
for field in &format.imm_fields {
fmtln!(fmt, "&& {}1 == {}2", field.member, field.member);
}
if let Some(args_eq) = args_eq {
fmtln!(fmt, "&& {}", args_eq);
}
if let Some(blocks_eq) = blocks_eq {
fmtln!(fmt, "&& {}", blocks_eq);
}
});
fmtln!(fmt, "}");
}
fmt.line("_ => unreachable!()");
});
fmt.line("}");
});
fmt.line("}");
fmt.empty_line();
fmt.doc_comment(r#"
Hash an `InstructionData`.
This operation requires a reference to a `ValueListPool` to
hash the contents of any `ValueLists`.
This operation takes a closure that is allowed to map each
argument value to some other value before it is hashed. This
allows various forms of canonicalization.
"#);
fmt.line("pub fn hash<H: ::core::hash::Hasher, F: Fn(Value) -> Value>(&self, state: &mut H, pool: &ir::ValueListPool, mapper: F) {");
fmt.indent(|fmt| {
fmt.line("match *self {");
fmt.indent(|fmt| {
for format in formats {
let name = format!("Self::{}", format.name);
let mut members = vec!["opcode"];
let (args, len) = if format.has_value_list {
members.push("ref args");
("args.as_slice(pool)", "args.len(pool)")
} else if format.num_value_operands == 1 {
members.push("ref arg");
("std::slice::from_ref(arg)", "1")
} else if format.num_value_operands > 0 {
members.push("ref args");
("args", "args.len()")
} else {
("&[]", "0")
};
let blocks = match format.num_block_operands {
0 => None,
1 => {
members.push("ref destination");
Some(("std::slice::from_ref(destination)", "1"))
}
_ => {
members.push("ref blocks");
Some(("blocks", "blocks.len()"))
}
};
for field in &format.imm_fields {
members.push(field.member);
}
let members = members.join(", ");
fmtln!(fmt, "{}{{{}}} => {{", name, members ); fmt.indent(|fmt| {
fmt.line("::core::hash::Hash::hash( &::core::mem::discriminant(self), state);");
fmt.line("::core::hash::Hash::hash(&opcode, state);");
for field in &format.imm_fields {
fmtln!(fmt, "::core::hash::Hash::hash(&{}, state);", field.member);
}
fmtln!(fmt, "::core::hash::Hash::hash(&{}, state);", len);
fmtln!(fmt, "for &arg in {} {{", args);
fmt.indent(|fmt| {
fmtln!(fmt, "let arg = mapper(arg);");
fmtln!(fmt, "::core::hash::Hash::hash(&arg, state);");
});
fmtln!(fmt, "}");
if let Some((blocks, len)) = blocks {
fmtln!(fmt, "::core::hash::Hash::hash(&{}, state);", len);
fmtln!(fmt, "for &block in {} {{", blocks);
fmt.indent(|fmt| {
fmtln!(fmt, "::core::hash::Hash::hash(&block.block(pool), state);");
fmtln!(fmt, "for &arg in block.args_slice(pool) {");
fmt.indent(|fmt| {
fmtln!(fmt, "let arg = mapper(arg);");
fmtln!(fmt, "::core::hash::Hash::hash(&arg, state);");
});
fmtln!(fmt, "}");
});
fmtln!(fmt, "}");
}
});
fmtln!(fmt, "}");
}
});
fmt.line("}");
});
fmt.line("}");
fmt.empty_line();
fmt.doc_comment(r#"
Deep-clone an `InstructionData`, including any referenced lists.
This operation requires a reference to a `ValueListPool` to
clone the `ValueLists`.
"#);
fmt.line("pub fn deep_clone(&self, pool: &mut ir::ValueListPool) -> Self {");
fmt.indent(|fmt| {
fmt.line("match *self {");
fmt.indent(|fmt| {
for format in formats {
let name = format!("Self::{}", format.name);
let mut members = vec!["opcode"];
if format.has_value_list {
members.push("ref args");
} else if format.num_value_operands == 1 {
members.push("arg");
} else if format.num_value_operands > 0 {
members.push("args");
}
match format.num_block_operands {
0 => {}
1 => {
members.push("destination");
}
_ => {
members.push("blocks");
}
};
for field in &format.imm_fields {
members.push(field.member);
}
let members = members.join(", ");
fmtln!(fmt, "{}{{{}}} => {{", name, members ); fmt.indent(|fmt| {
fmtln!(fmt, "Self::{} {{", format.name);
fmt.indent(|fmt| {
fmtln!(fmt, "opcode,");
if format.has_value_list {
fmtln!(fmt, "args: args.deep_clone(pool),");
} else if format.num_value_operands == 1 {
fmtln!(fmt, "arg,");
} else if format.num_value_operands > 0 {
fmtln!(fmt, "args,");
}
match format.num_block_operands {
0 => {}
1 => {
fmtln!(fmt, "destination: destination.deep_clone(pool),");
}
2 => {
fmtln!(fmt, "blocks: [blocks[0].deep_clone(pool), blocks[1].deep_clone(pool)],");
}
_ => panic!("Too many block targets in instruction"),
}
for field in &format.imm_fields {
fmtln!(fmt, "{},", field.member);
}
});
fmtln!(fmt, "}");
});
fmtln!(fmt, "}");
}
});
fmt.line("}");
});
fmt.line("}");
});
fmt.line("}");
}
fn gen_bool_accessor<T: Fn(&Instruction) -> bool>(
all_inst: &AllInstructions,
get_attr: T,
name: &'static str,
doc: &'static str,
fmt: &mut Formatter,
) {
fmt.doc_comment(doc);
fmtln!(fmt, "pub fn {}(self) -> bool {{", name);
fmt.indent(|fmt| {
let mut m = Match::new("self");
for inst in all_inst.iter() {
if get_attr(inst) {
m.arm_no_fields(format!("Self::{}", inst.camel_name), "true");
}
}
m.arm_no_fields("_", "false");
fmt.add_match(m);
});
fmtln!(fmt, "}");
fmt.empty_line();
}
fn gen_opcodes(all_inst: &AllInstructions, fmt: &mut Formatter) {
fmt.doc_comment(
r#"
An instruction opcode.
All instructions from all supported ISAs are present.
"#,
);
fmt.line("#[repr(u8)]");
fmt.line("#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]");
fmt.line(
r#"#[cfg_attr(
feature = "enable-serde",
derive(serde_derive::Serialize, serde_derive::Deserialize)
)]"#,
);
fmt.line("pub enum Opcode {");
fmt.indent(|fmt| {
let mut is_first_opcode = true;
for inst in all_inst.iter() {
fmt.doc_comment(format!("`{}`. ({})", inst, inst.format.name));
if let Some(poly) = &inst.polymorphic_info {
if poly.use_typevar_operand {
let op_num = inst.value_opnums[inst.format.typevar_operand.unwrap()];
fmt.doc_comment(format!(
"Type inferred from `{}`.",
inst.operands_in[op_num].name
));
}
}
if is_first_opcode {
fmtln!(fmt, "{} = 1,", inst.camel_name);
is_first_opcode = false;
} else {
fmtln!(fmt, "{},", inst.camel_name)
}
}
});
fmt.line("}");
fmt.empty_line();
fmt.line("impl Opcode {");
fmt.indent(|fmt| {
gen_bool_accessor(
all_inst,
|inst| inst.is_terminator,
"is_terminator",
"True for instructions that terminate the block",
fmt,
);
gen_bool_accessor(
all_inst,
|inst| inst.is_branch,
"is_branch",
"True for all branch or jump instructions.",
fmt,
);
gen_bool_accessor(
all_inst,
|inst| inst.is_call,
"is_call",
"Is this a call instruction?",
fmt,
);
gen_bool_accessor(
all_inst,
|inst| inst.is_return,
"is_return",
"Is this a return instruction?",
fmt,
);
gen_bool_accessor(
all_inst,
|inst| inst.can_load,
"can_load",
"Can this instruction read from memory?",
fmt,
);
gen_bool_accessor(
all_inst,
|inst| inst.can_store,
"can_store",
"Can this instruction write to memory?",
fmt,
);
gen_bool_accessor(
all_inst,
|inst| inst.can_trap,
"can_trap",
"Can this instruction cause a trap?",
fmt,
);
gen_bool_accessor(
all_inst,
|inst| inst.other_side_effects,
"other_side_effects",
"Does this instruction have other side effects besides can_* flags?",
fmt,
);
gen_bool_accessor(
all_inst,
|inst| inst.side_effects_idempotent,
"side_effects_idempotent",
"Despite having side effects, is this instruction okay to GVN?",
fmt,
);
fmt.doc_comment("All cranelift opcodes.");
fmt.line("pub fn all() -> &'static [Opcode] {");
fmt.indent(|fmt| {
fmt.line("return &[");
for inst in all_inst {
fmt.indent(|fmt| {
fmtln!(fmt, "Opcode::{},", inst.camel_name);
});
}
fmt.line("];");
});
fmt.line("}");
fmt.empty_line();
});
fmt.line("}");
fmt.empty_line();
fmtln!(
fmt,
"const OPCODE_FORMAT: [InstructionFormat; {}] = [",
all_inst.len()
);
fmt.indent(|fmt| {
for inst in all_inst.iter() {
fmtln!(
fmt,
"InstructionFormat::{}, // {}",
inst.format.name,
inst.name
);
}
});
fmtln!(fmt, "];");
fmt.empty_line();
fmt.line("fn opcode_name(opc: Opcode) -> &\'static str {");
fmt.indent(|fmt| {
let mut m = Match::new("opc");
for inst in all_inst.iter() {
m.arm_no_fields(
format!("Opcode::{}", inst.camel_name),
format!("\"{}\"", inst.name),
);
}
fmt.add_match(m);
});
fmt.line("}");
fmt.empty_line();
let hash_table =
crate::constant_hash::generate_table(all_inst.iter(), all_inst.len(), |inst| {
constant_hash::simple_hash(&inst.name)
});
fmtln!(
fmt,
"const OPCODE_HASH_TABLE: [Option<Opcode>; {}] = [",
hash_table.len()
);
fmt.indent(|fmt| {
for i in hash_table {
match i {
Some(i) => fmtln!(fmt, "Some(Opcode::{}),", i.camel_name),
None => fmtln!(fmt, "None,"),
}
}
});
fmtln!(fmt, "];");
fmt.empty_line();
}
fn get_constraint<'entries, 'table>(
operand: &'entries Operand,
ctrl_typevar: Option<&TypeVar>,
type_sets: &'table mut UniqueTable<'entries, TypeSet>,
) -> String {
assert!(operand.is_value());
let type_var = operand.type_var().unwrap();
if let Some(typ) = type_var.singleton_type() {
return format!("Concrete({})", typ.rust_name());
}
if let Some(free_typevar) = type_var.free_typevar() {
if ctrl_typevar.is_some() && free_typevar != *ctrl_typevar.unwrap() {
assert!(type_var.base.is_none());
return format!("Free({})", type_sets.add(type_var.get_raw_typeset()));
}
}
if let Some(base) = &type_var.base {
assert!(base.type_var == *ctrl_typevar.unwrap());
return camel_case(base.derived_func.name());
}
assert!(type_var == ctrl_typevar.unwrap());
"Same".into()
}
fn gen_bitset<'a, T: IntoIterator<Item = &'a u16>>(
iterable: T,
name: &'static str,
field_size: u8,
fmt: &mut Formatter,
) {
let bits = iterable.into_iter().fold(0, |acc, x| {
assert!(x.is_power_of_two());
assert!(u32::from(*x) < (1 << u32::from(field_size)));
acc | x
});
fmtln!(fmt, "{}: BitSet::<u{}>({}),", name, field_size, bits);
}
fn iterable_to_string<I: fmt::Display, T: IntoIterator<Item = I>>(iterable: T) -> String {
let elems = iterable
.into_iter()
.map(|x| x.to_string())
.collect::<Vec<_>>()
.join(", ");
format!("{{{}}}", elems)
}
fn typeset_to_string(ts: &TypeSet) -> String {
let mut result = format!("TypeSet(lanes={}", iterable_to_string(&ts.lanes));
if !ts.ints.is_empty() {
result += &format!(", ints={}", iterable_to_string(&ts.ints));
}
if !ts.floats.is_empty() {
result += &format!(", floats={}", iterable_to_string(&ts.floats));
}
if !ts.refs.is_empty() {
result += &format!(", refs={}", iterable_to_string(&ts.refs));
}
result += ")";
result
}
pub(crate) fn gen_typesets_table(type_sets: &UniqueTable<TypeSet>, fmt: &mut Formatter) {
if type_sets.len() == 0 {
return;
}
fmt.comment("Table of value type sets.");
assert!(type_sets.len() <= TYPESET_LIMIT, "Too many type sets!");
fmtln!(
fmt,
"const TYPE_SETS: [ir::instructions::ValueTypeSet; {}] = [",
type_sets.len()
);
fmt.indent(|fmt| {
for ts in type_sets.iter() {
fmt.line("ir::instructions::ValueTypeSet {");
fmt.indent(|fmt| {
fmt.comment(typeset_to_string(ts));
gen_bitset(&ts.lanes, "lanes", 16, fmt);
gen_bitset(&ts.dynamic_lanes, "dynamic_lanes", 16, fmt);
gen_bitset(&ts.ints, "ints", 8, fmt);
gen_bitset(&ts.floats, "floats", 8, fmt);
gen_bitset(&ts.refs, "refs", 8, fmt);
});
fmt.line("},");
}
});
fmtln!(fmt, "];");
}
fn gen_type_constraints(all_inst: &AllInstructions, fmt: &mut Formatter) {
let mut type_sets = UniqueTable::new();
let mut operand_seqs = UniqueSeqTable::new();
operand_seqs.add(&vec!["Same".to_string(); 3]);
fmt.comment("Table of opcode constraints.");
fmtln!(
fmt,
"const OPCODE_CONSTRAINTS: [OpcodeConstraints; {}] = [",
all_inst.len()
);
fmt.indent(|fmt| {
for inst in all_inst.iter() {
let (ctrl_typevar, ctrl_typeset) = if let Some(poly) = &inst.polymorphic_info {
let index = type_sets.add(poly.ctrl_typevar.get_raw_typeset());
(Some(&poly.ctrl_typevar), index)
} else {
(None, TYPESET_LIMIT)
};
let mut constraints = Vec::new();
for &index in &inst.value_results {
constraints.push(get_constraint(&inst.operands_out[index], ctrl_typevar, &mut type_sets));
}
for &index in &inst.value_opnums {
constraints.push(get_constraint(&inst.operands_in[index], ctrl_typevar, &mut type_sets));
}
let constraint_offset = operand_seqs.add(&constraints);
let fixed_results = inst.value_results.len();
let fixed_values = inst.value_opnums.len();
let use_typevar_operand = if let Some(poly) = &inst.polymorphic_info {
poly.use_typevar_operand
} else {
false
};
let use_result = fixed_results > 0 && inst.operands_out[inst.value_results[0]].type_var() == ctrl_typevar;
let requires_typevar_operand = use_typevar_operand && !use_result;
fmt.comment(
format!("{}: fixed_results={}, use_typevar_operand={}, requires_typevar_operand={}, fixed_values={}",
inst.camel_name,
fixed_results,
use_typevar_operand,
requires_typevar_operand,
fixed_values)
);
fmt.comment(format!("Constraints=[{}]", constraints
.iter()
.map(|x| format!("'{}'", x))
.collect::<Vec<_>>()
.join(", ")));
if let Some(poly) = &inst.polymorphic_info {
fmt.comment(format!("Polymorphic over {}", typeset_to_string(poly.ctrl_typevar.get_raw_typeset())));
}
assert!(fixed_results < 8 && fixed_values < 8, "Bit field encoding too tight");
let mut flags = fixed_results; if use_typevar_operand {
flags |= 1<<3; }
if requires_typevar_operand {
flags |= 1<<4; }
flags |= fixed_values << 5; fmt.line("OpcodeConstraints {");
fmt.indent(|fmt| {
fmtln!(fmt, "flags: {:#04x},", flags);
fmtln!(fmt, "typeset_offset: {},", ctrl_typeset);
fmtln!(fmt, "constraint_offset: {},", constraint_offset);
});
fmt.line("},");
}
});
fmtln!(fmt, "];");
fmt.empty_line();
gen_typesets_table(&type_sets, fmt);
fmt.empty_line();
fmt.comment("Table of operand constraint sequences.");
fmtln!(
fmt,
"const OPERAND_CONSTRAINTS: [OperandConstraint; {}] = [",
operand_seqs.len()
);
fmt.indent(|fmt| {
for constraint in operand_seqs.iter() {
fmtln!(fmt, "OperandConstraint::{},", constraint);
}
});
fmtln!(fmt, "];");
}
fn gen_member_inits(format: &InstructionFormat, fmt: &mut Formatter) {
for f in &format.imm_fields {
fmtln!(fmt, "{},", f.member);
}
if format.has_value_list {
fmt.line("args,");
} else if format.num_value_operands == 1 {
fmt.line("arg: arg0,");
} else if format.num_value_operands > 1 {
let mut args = Vec::new();
for i in 0..format.num_value_operands {
args.push(format!("arg{}", i));
}
fmtln!(fmt, "args: [{}],", args.join(", "));
}
match format.num_block_operands {
0 => (),
1 => fmt.line("destination: block0"),
n => {
let mut blocks = Vec::new();
for i in 0..n {
blocks.push(format!("block{}", i));
}
fmtln!(fmt, "blocks: [{}],", blocks.join(", "));
}
}
}
fn gen_format_constructor(format: &InstructionFormat, fmt: &mut Formatter) {
let mut args = vec![
"self".to_string(),
"opcode: Opcode".into(),
"ctrl_typevar: Type".into(),
];
for f in &format.imm_fields {
args.push(format!("{}: {}", f.member, f.kind.rust_type));
}
args.extend((0..format.num_block_operands).map(|i| format!("block{}: ir::BlockCall", i)));
if format.has_value_list {
args.push("args: ir::ValueList".into());
} else {
for i in 0..format.num_value_operands {
args.push(format!("arg{}: Value", i));
}
}
let proto = format!(
"{}({}) -> (Inst, &'f mut ir::DataFlowGraph)",
format.name,
args.join(", ")
);
let imms_need_sign_extension = format
.imm_fields
.iter()
.any(|f| f.kind.rust_type == "ir::immediates::Imm64");
fmt.doc_comment(format.to_string());
fmt.line("#[allow(non_snake_case)]");
fmtln!(fmt, "fn {} {{", proto);
fmt.indent(|fmt| {
fmtln!(
fmt,
"let{} data = ir::InstructionData::{} {{",
if imms_need_sign_extension { " mut" } else { "" },
format.name
);
fmt.indent(|fmt| {
fmt.line("opcode,");
gen_member_inits(format, fmt);
});
fmtln!(fmt, "};");
if imms_need_sign_extension {
fmtln!(fmt, "data.sign_extend_immediates(ctrl_typevar);");
}
fmtln!(fmt, "debug_assert_eq!(opcode.format(), InstructionFormat::from(&data), \"Wrong InstructionFormat for Opcode: {}\", opcode);");
fmt.line("self.build(data, ctrl_typevar)");
});
fmtln!(fmt, "}");
}
fn gen_inst_builder(inst: &Instruction, format: &InstructionFormat, fmt: &mut Formatter) {
let mut args = vec![String::new()];
let mut args_doc = Vec::new();
let mut rets_doc = Vec::new();
if let Some(poly) = &inst.polymorphic_info {
if !poly.use_typevar_operand {
args.push(format!("{}: crate::ir::Type", poly.ctrl_typevar.name));
args_doc.push(format!(
"- {} (controlling type variable): {}",
poly.ctrl_typevar.name, poly.ctrl_typevar.doc
));
}
}
let mut tmpl_types = Vec::new();
let mut into_args = Vec::new();
let mut block_args = Vec::new();
for op in &inst.operands_in {
if op.kind.is_block() {
args.push(format!("{}_label: {}", op.name, "ir::Block"));
args_doc.push(format!(
"- {}_label: {}",
op.name, "Destination basic block"
));
args.push(format!("{}_args: {}", op.name, "&[Value]"));
args_doc.push(format!("- {}_args: {}", op.name, "Block arguments"));
block_args.push(op);
} else {
let t = if op.is_immediate() {
let t = format!("T{}", tmpl_types.len() + 1);
tmpl_types.push(format!("{}: Into<{}>", t, op.kind.rust_type));
into_args.push(op.name);
t
} else {
op.kind.rust_type.to_string()
};
args.push(format!("{}: {}", op.name, t));
args_doc.push(format!("- {}: {}", op.name, op.doc()));
}
}
if format.has_value_list || !block_args.is_empty() {
args[0].push_str("mut self");
} else {
args[0].push_str("self");
}
for op in &inst.operands_out {
rets_doc.push(format!("- {}: {}", op.name, op.doc()));
}
let rtype = match inst.value_results.len() {
0 => "Inst".into(),
1 => "Value".into(),
_ => format!("({})", vec!["Value"; inst.value_results.len()].join(", ")),
};
let tmpl = if !tmpl_types.is_empty() {
format!("<{}>", tmpl_types.join(", "))
} else {
"".into()
};
let proto = format!(
"{}{}({}) -> {}",
inst.snake_name(),
tmpl,
args.join(", "),
rtype
);
fmt.doc_comment(&inst.doc);
if !args_doc.is_empty() {
fmt.line("///");
fmt.doc_comment("Inputs:");
fmt.line("///");
for doc_line in args_doc {
fmt.doc_comment(doc_line);
}
}
if !rets_doc.is_empty() {
fmt.line("///");
fmt.doc_comment("Outputs:");
fmt.line("///");
for doc_line in rets_doc {
fmt.doc_comment(doc_line);
}
}
fmt.line("#[allow(non_snake_case)]");
fmtln!(fmt, "fn {} {{", proto);
fmt.indent(|fmt| {
for arg in into_args {
fmtln!(fmt, "let {} = {}.into();", arg, arg);
}
for op in block_args {
fmtln!(
fmt,
"let {0} = self.data_flow_graph_mut().block_call({0}_label, {0}_args);",
op.name
);
}
let first_arg = format!("Opcode::{}", inst.camel_name);
let mut args = vec![first_arg.as_str()];
if let Some(poly) = &inst.polymorphic_info {
if poly.use_typevar_operand {
let op_num = inst.value_opnums[format.typevar_operand.unwrap()];
fmtln!(
fmt,
"let ctrl_typevar = self.data_flow_graph().value_type({});",
inst.operands_in[op_num].name
);
args.push("ctrl_typevar");
} else {
args.push(&poly.ctrl_typevar.name);
}
} else {
args.push("types::INVALID");
}
for &op_num in &inst.imm_opnums {
args.push(inst.operands_in[op_num].name);
}
if format.has_value_list {
fmt.line("let mut vlist = ir::ValueList::default();");
args.push("vlist");
fmt.line("{");
fmt.indent(|fmt| {
fmt.line("let pool = &mut self.data_flow_graph_mut().value_lists;");
for op in &inst.operands_in {
if op.is_value() {
fmtln!(fmt, "vlist.push({}, pool);", op.name);
} else if op.is_varargs() {
fmtln!(fmt, "vlist.extend({}.iter().cloned(), pool);", op.name);
}
}
});
fmt.line("}");
} else {
for &op_num in &inst.value_opnums {
args.push(inst.operands_in[op_num].name);
}
}
let fcall = format!("self.{}({})", format.name, args.join(", "));
if inst.value_results.is_empty() {
fmtln!(fmt, "{}.0", fcall);
return;
}
fmtln!(fmt, "let (inst, dfg) = {};", fcall);
if inst.value_results.len() == 1 {
fmt.line("dfg.first_result(inst)");
} else {
fmtln!(
fmt,
"let results = &dfg.inst_results(inst)[0..{}];",
inst.value_results.len()
);
fmtln!(
fmt,
"({})",
inst.value_results
.iter()
.enumerate()
.map(|(i, _)| format!("results[{}]", i))
.collect::<Vec<_>>()
.join(", ")
);
}
});
fmtln!(fmt, "}")
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum IsleTarget {
Lower,
Opt,
}
fn gen_common_isle(
formats: &[Rc<InstructionFormat>],
instructions: &AllInstructions,
fmt: &mut Formatter,
isle_target: IsleTarget,
) {
use std::collections::{BTreeMap, BTreeSet};
use std::fmt::Write;
use crate::cdsl::formats::FormatField;
fmt.multi_line(
r#"
;; GENERATED BY `gen_isle`. DO NOT EDIT!!!
;;
;; This ISLE file defines all the external type declarations for Cranelift's
;; data structures that ISLE will process, such as `InstructionData` and
;; `Opcode`.
"#,
);
fmt.empty_line();
let rust_name = |f: &FormatField| f.kind.rust_type.rsplit("::").next().unwrap();
let fields = |f: &FormatField| f.kind.fields.clone();
let immediate_types: BTreeMap<_, _> = formats
.iter()
.flat_map(|f| {
f.imm_fields
.iter()
.map(|i| (rust_name(i), fields(i)))
.collect::<Vec<_>>()
})
.collect();
let (enums, others): (BTreeMap<_, _>, BTreeMap<_, _>) = immediate_types
.iter()
.partition(|(_, field)| field.enum_values().is_some());
fmt.line(";;;; Extern type declarations for immediates ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;");
fmt.empty_line();
for ty in others.keys() {
fmtln!(fmt, "(type {} (primitive {}))", ty, ty);
}
fmt.empty_line();
for (name, field) in enums {
let field = field.enum_values().expect("only enums considered here");
let variants = field.values().cloned().collect();
gen_isle_enum(name, variants, fmt)
}
fmt.line(";;;; Value Arrays ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;");
fmt.empty_line();
let value_array_arities: BTreeSet<_> = formats
.iter()
.filter(|f| f.typevar_operand.is_some() && !f.has_value_list && f.num_value_operands != 1)
.map(|f| f.num_value_operands)
.collect();
for n in value_array_arities {
fmtln!(fmt, ";; ISLE representation of `[Value; {}]`.", n);
fmtln!(fmt, "(type ValueArray{} extern (enum))", n);
fmt.empty_line();
fmtln!(
fmt,
"(decl value_array_{} ({}) ValueArray{})",
n,
(0..n).map(|_| "Value").collect::<Vec<_>>().join(" "),
n
);
fmtln!(
fmt,
"(extern constructor value_array_{} pack_value_array_{})",
n,
n
);
fmtln!(
fmt,
"(extern extractor infallible value_array_{} unpack_value_array_{})",
n,
n
);
fmt.empty_line();
}
fmt.line(";;;; Block Arrays ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;");
fmt.empty_line();
let block_array_arities: BTreeSet<_> = formats
.iter()
.filter(|f| f.num_block_operands > 1)
.map(|f| f.num_block_operands)
.collect();
for n in block_array_arities {
fmtln!(fmt, ";; ISLE representation of `[BlockCall; {}]`.", n);
fmtln!(fmt, "(type BlockArray{} extern (enum))", n);
fmt.empty_line();
fmtln!(
fmt,
"(decl block_array_{0} ({1}) BlockArray{0})",
n,
(0..n).map(|_| "BlockCall").collect::<Vec<_>>().join(" ")
);
fmtln!(
fmt,
"(extern constructor block_array_{0} pack_block_array_{0})",
n
);
fmtln!(
fmt,
"(extern extractor infallible block_array_{0} unpack_block_array_{0})",
n
);
fmt.empty_line();
}
fmt.line(";;;; `Opcode` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;");
fmt.empty_line();
fmt.line("(type Opcode extern");
fmt.indent(|fmt| {
fmt.line("(enum");
fmt.indent(|fmt| {
for inst in instructions {
fmtln!(fmt, "{}", inst.camel_name);
}
});
fmt.line(")");
});
fmt.line(")");
fmt.empty_line();
fmtln!(
fmt,
";;;; `InstructionData` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;",
);
fmt.empty_line();
fmtln!(fmt, "(type InstructionData extern");
fmt.indent(|fmt| {
fmt.line("(enum");
fmt.indent(|fmt| {
for format in formats {
let mut s = format!("({} (opcode Opcode)", format.name);
if format.has_value_list {
s.push_str(" (args ValueList)");
} else if format.num_value_operands == 1 {
s.push_str(" (arg Value)");
} else if format.num_value_operands > 1 {
write!(&mut s, " (args ValueArray{})", format.num_value_operands).unwrap();
}
match format.num_block_operands {
0 => (),
1 => write!(&mut s, " (destination BlockCall)").unwrap(),
n => write!(&mut s, " (blocks BlockArray{})", n).unwrap(),
}
for field in &format.imm_fields {
write!(
&mut s,
" ({} {})",
field.member,
field.kind.rust_type.rsplit("::").next().unwrap()
)
.unwrap();
}
s.push(')');
fmt.line(&s);
}
});
fmt.line(")");
});
fmt.line(")");
fmt.empty_line();
fmtln!(
fmt,
";;;; Extracting Opcode, Operands, and Immediates from `InstructionData` ;;;;;;;;",
);
fmt.empty_line();
let ret_ty = match isle_target {
IsleTarget::Lower => "Inst",
IsleTarget::Opt => "Value",
};
for inst in instructions {
if isle_target == IsleTarget::Opt
&& (inst.format.has_value_list || inst.value_results.len() != 1)
{
continue;
}
fmtln!(
fmt,
"(decl {} ({}{}) {})",
inst.name,
match isle_target {
IsleTarget::Lower => "",
IsleTarget::Opt => "Type ",
},
inst.operands_in
.iter()
.map(|o| {
let ty = o.kind.rust_type;
if ty == "&[Value]" {
"ValueSlice"
} else {
ty.rsplit("::").next().unwrap()
}
})
.collect::<Vec<_>>()
.join(" "),
ret_ty
);
fmtln!(fmt, "(extractor");
fmt.indent(|fmt| {
fmtln!(
fmt,
"({} {}{})",
inst.name,
match isle_target {
IsleTarget::Lower => "",
IsleTarget::Opt => "ty ",
},
inst.operands_in
.iter()
.map(|o| { o.name })
.collect::<Vec<_>>()
.join(" ")
);
let mut s = format!(
"(inst_data{} (InstructionData.{} (Opcode.{})",
match isle_target {
IsleTarget::Lower => "",
IsleTarget::Opt => " ty",
},
inst.format.name,
inst.camel_name
);
if inst.format.has_value_list {
let values: Vec<_> = inst
.operands_in
.iter()
.filter(|o| o.is_value())
.map(|o| o.name)
.collect();
let varargs = inst
.operands_in
.iter()
.find(|o| o.is_varargs())
.unwrap()
.name;
if values.is_empty() {
write!(&mut s, " (value_list_slice {})", varargs).unwrap();
} else {
write!(
&mut s,
" (unwrap_head_value_list_{} {} {})",
values.len(),
values.join(" "),
varargs
)
.unwrap();
}
} else if inst.format.num_value_operands == 1 {
write!(
&mut s,
" {}",
inst.operands_in.iter().find(|o| o.is_value()).unwrap().name
)
.unwrap();
} else if inst.format.num_value_operands > 1 {
let values = inst
.operands_in
.iter()
.filter(|o| o.is_value())
.map(|o| o.name)
.collect::<Vec<_>>();
assert_eq!(values.len(), inst.format.num_value_operands);
let values = values.join(" ");
write!(
&mut s,
" (value_array_{} {})",
inst.format.num_value_operands, values,
)
.unwrap();
}
let imm_operands: Vec<_> = inst
.operands_in
.iter()
.filter(|o| !o.is_value() && !o.is_varargs() && !o.kind.is_block())
.collect();
assert_eq!(imm_operands.len(), inst.format.imm_fields.len(),);
for op in imm_operands {
write!(&mut s, " {}", op.name).unwrap();
}
let block_operands: Vec<_> = inst
.operands_in
.iter()
.filter(|o| o.kind.is_block())
.collect();
assert_eq!(block_operands.len(), inst.format.num_block_operands);
assert!(block_operands.len() <= 2);
if !block_operands.is_empty() {
if block_operands.len() == 1 {
write!(&mut s, " {}", block_operands[0].name).unwrap();
} else {
let blocks: Vec<_> = block_operands.iter().map(|o| o.name).collect();
let blocks = blocks.join(" ");
write!(
&mut s,
" (block_array_{} {})",
inst.format.num_block_operands, blocks,
)
.unwrap();
}
}
s.push_str("))");
fmt.line(&s);
});
fmt.line(")");
if isle_target == IsleTarget::Opt {
fmtln!(
fmt,
"(rule ({} ty {})",
inst.name,
inst.operands_in
.iter()
.map(|o| o.name)
.collect::<Vec<_>>()
.join(" ")
);
fmt.indent(|fmt| {
let mut s = format!(
"(make_inst ty (InstructionData.{} (Opcode.{})",
inst.format.name, inst.camel_name
);
assert!(!inst.format.has_value_list);
if inst.format.num_value_operands == 1 {
write!(
&mut s,
" {}",
inst.operands_in.iter().find(|o| o.is_value()).unwrap().name
)
.unwrap();
} else if inst.format.num_value_operands > 1 {
let values = inst
.operands_in
.iter()
.filter(|o| o.is_value())
.map(|o| o.name)
.collect::<Vec<_>>();
assert_eq!(values.len(), inst.format.num_value_operands);
let values = values.join(" ");
write!(
&mut s,
" (value_array_{}_ctor {})",
inst.format.num_value_operands, values
)
.unwrap();
}
if inst.format.num_block_operands > 0 {
let blocks: Vec<_> = inst
.operands_in
.iter()
.filter(|o| o.kind.is_block())
.map(|o| o.name)
.collect();
if inst.format.num_block_operands == 1 {
write!(&mut s, " {}", blocks.first().unwrap(),).unwrap();
} else {
write!(
&mut s,
" (block_array_{} {})",
inst.format.num_block_operands,
blocks.join(" ")
)
.unwrap();
}
}
for o in inst
.operands_in
.iter()
.filter(|o| !o.is_value() && !o.is_varargs() && !o.kind.is_block())
{
write!(&mut s, " {}", o.name).unwrap();
}
s.push_str("))");
fmt.line(&s);
});
fmt.line(")");
}
fmt.empty_line();
}
}
fn gen_opt_isle(
formats: &[Rc<InstructionFormat>],
instructions: &AllInstructions,
fmt: &mut Formatter,
) {
gen_common_isle(formats, instructions, fmt, IsleTarget::Opt);
}
fn gen_lower_isle(
formats: &[Rc<InstructionFormat>],
instructions: &AllInstructions,
fmt: &mut Formatter,
) {
gen_common_isle(formats, instructions, fmt, IsleTarget::Lower);
}
fn gen_isle_enum(name: &str, mut variants: Vec<&str>, fmt: &mut Formatter) {
variants.sort();
let prefix = format!(";;;; Enumerated Immediate: {} ", name);
fmtln!(fmt, "{:;<80}", prefix);
fmt.empty_line();
fmtln!(fmt, "(type {} extern", name);
fmt.indent(|fmt| {
fmt.line("(enum");
fmt.indent(|fmt| {
for variant in variants {
fmtln!(fmt, "{}", variant);
}
});
fmt.line(")");
});
fmt.line(")");
fmt.empty_line();
}
fn gen_builder(
instructions: &AllInstructions,
formats: &[Rc<InstructionFormat>],
fmt: &mut Formatter,
) {
fmt.doc_comment(
r#"
Convenience methods for building instructions.
The `InstBuilder` trait has one method per instruction opcode for
conveniently constructing the instruction with minimum arguments.
Polymorphic instructions infer their result types from the input
arguments when possible. In some cases, an explicit `ctrl_typevar`
argument is required.
The opcode methods return the new instruction's result values, or
the `Inst` itself for instructions that don't have any results.
There is also a method per instruction format. These methods all
return an `Inst`.
"#,
);
fmt.line("pub trait InstBuilder<'f>: InstBuilderBase<'f> {");
fmt.indent(|fmt| {
for inst in instructions.iter() {
gen_inst_builder(inst, &inst.format, fmt);
fmt.empty_line();
}
for (i, format) in formats.iter().enumerate() {
gen_format_constructor(format, fmt);
if i + 1 != formats.len() {
fmt.empty_line();
}
}
});
fmt.line("}");
}
pub(crate) fn generate(
formats: &[Rc<InstructionFormat>],
all_inst: &AllInstructions,
opcode_filename: &str,
inst_builder_filename: &str,
isle_opt_filename: &str,
isle_lower_filename: &str,
out_dir: &str,
isle_dir: &str,
) -> Result<(), error::Error> {
let mut fmt = Formatter::new();
gen_formats(&formats, &mut fmt);
gen_instruction_data(&formats, &mut fmt);
fmt.empty_line();
gen_instruction_data_impl(&formats, &mut fmt);
fmt.empty_line();
gen_opcodes(all_inst, &mut fmt);
fmt.empty_line();
gen_type_constraints(all_inst, &mut fmt);
fmt.update_file(opcode_filename, out_dir)?;
let mut fmt = Formatter::new();
gen_opt_isle(&formats, all_inst, &mut fmt);
fmt.update_file(isle_opt_filename, isle_dir)?;
let mut fmt = Formatter::new();
gen_lower_isle(&formats, all_inst, &mut fmt);
fmt.update_file(isle_lower_filename, isle_dir)?;
let mut fmt = Formatter::new();
gen_builder(all_inst, &formats, &mut fmt);
fmt.update_file(inst_builder_filename, out_dir)?;
Ok(())
}