1785 lines
62 KiB
Rust
1785 lines
62 KiB
Rust
//! Generate instruction data (including opcodes, formats, builders, etc.).
|
|
use std::fmt;
|
|
|
|
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};
|
|
|
|
// TypeSet indexes are encoded in 8 bits, with `0xff` reserved.
|
|
const TYPESET_LIMIT: usize = 0xff;
|
|
|
|
/// Generate an instruction format enumeration.
|
|
fn gen_formats(formats: &[&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();
|
|
|
|
// Emit a From<InstructionData> which also serves to verify that
|
|
// InstructionFormat and InstructionData are in sync.
|
|
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();
|
|
}
|
|
|
|
/// Generate the InstructionData enum.
|
|
///
|
|
/// Every variant must contain an `opcode` field. The size of `InstructionData` should be kept at
|
|
/// 16 bytes on 64-bit architectures. If more space is needed to represent an instruction, use a
|
|
/// `ValueList` to store the additional information out of line.
|
|
fn gen_instruction_data(formats: &[&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: &[&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);
|
|
|
|
// Formats with a value list put all of their arguments in the list. We don't split
|
|
// them up, just return it all as variable arguments. (I expect the distinction to go
|
|
// away).
|
|
if format.has_value_list {
|
|
m.arm(
|
|
name,
|
|
vec![format!("ref {}args", mut_), "..".to_string()],
|
|
format!("args.{}(pool)", as_slice),
|
|
);
|
|
continue;
|
|
}
|
|
|
|
// Fixed args.
|
|
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, "}");
|
|
}
|
|
|
|
/// Generate the boring parts of the InstructionData implementation.
|
|
///
|
|
/// These methods in `impl InstructionData` can be generated automatically from the instruction
|
|
/// formats:
|
|
///
|
|
/// - `pub fn opcode(&self) -> Opcode`
|
|
/// - `pub fn arguments(&self, &pool) -> &[Value]`
|
|
/// - `pub fn arguments_mut(&mut self, &pool) -> &mut [Value]`
|
|
/// - `pub fn eq(&self, &other: Self, &pool) -> bool`
|
|
/// - `pub fn hash<H: Hasher>(&self, state: &mut H, &pool)`
|
|
fn gen_instruction_data_impl(formats: &[&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 {
|
|
// We keep all arguments in a 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 {
|
|
// We have multiple value operands and an array `args`.
|
|
// Which `args` index to use?
|
|
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 ); // beware the moustaches
|
|
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 ); // beware the moustaches
|
|
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::Serialize, serde::Deserialize)
|
|
)]"#,
|
|
);
|
|
|
|
// We explicitly set the discriminant of the first variant to 1, which allows us to take
|
|
// advantage of the NonZero optimization, meaning that wrapping enums can use the 0
|
|
// discriminant instead of increasing the size of the whole type, and so the size of
|
|
// Option<Opcode> is the same as Opcode's.
|
|
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));
|
|
|
|
// Document polymorphism.
|
|
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
|
|
));
|
|
}
|
|
}
|
|
|
|
// Enum variant itself.
|
|
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,
|
|
);
|
|
|
|
// Generate an opcode list, for iterating over all known opcodes.
|
|
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();
|
|
|
|
// Generate a private opcode_format table.
|
|
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();
|
|
|
|
// Generate a private opcode_name function.
|
|
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();
|
|
|
|
// Generate an opcode hash table for looking up opcodes by name.
|
|
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();
|
|
}
|
|
|
|
/// Get the value type constraint for an SSA value operand, where
|
|
/// `ctrl_typevar` is the controlling type variable.
|
|
///
|
|
/// Each operand constraint is represented as a string, one of:
|
|
/// - `Concrete(vt)`, where `vt` is a value type name.
|
|
/// - `Free(idx)` where `idx` is an index into `type_sets`.
|
|
/// - `Same`, `Lane`, `AsTruthy` for controlling typevar-derived constraints.
|
|
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
|
|
}
|
|
|
|
/// Generate the table of ValueTypeSets described by type_sets.
|
|
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, "];");
|
|
}
|
|
|
|
/// Generate value type constraints for all instructions.
|
|
/// - Emit a compact constant table of ValueTypeSet objects.
|
|
/// - Emit a compact constant table of OperandConstraint objects.
|
|
/// - Emit an opcode-indexed table of instruction constraints.
|
|
fn gen_type_constraints(all_inst: &AllInstructions, fmt: &mut Formatter) {
|
|
// Table of TypeSet instances.
|
|
let mut type_sets = UniqueTable::new();
|
|
|
|
// Table of operand constraint sequences (as tuples). Each operand
|
|
// constraint is represented as a string, one of:
|
|
// - `Concrete(vt)`, where `vt` is a value type name.
|
|
// - `Free(idx)` where `idx` is an index into `type_sets`.
|
|
// - `Same`, `Lane`, `AsTruthy` for controlling typevar-derived constraints.
|
|
let mut operand_seqs = UniqueSeqTable::new();
|
|
|
|
// Preload table with constraints for typical binops.
|
|
#[allow(clippy::useless_vec)]
|
|
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)
|
|
};
|
|
|
|
// Collect constraints for the value results, not including `variable_args` results
|
|
// which are always special cased.
|
|
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();
|
|
|
|
// Can the controlling type variable be inferred from the designated operand?
|
|
let use_typevar_operand = if let Some(poly) = &inst.polymorphic_info {
|
|
poly.use_typevar_operand
|
|
} else {
|
|
false
|
|
};
|
|
|
|
// Can the controlling type variable be inferred from the result?
|
|
let use_result = fixed_results > 0 && inst.operands_out[inst.value_results[0]].type_var() == ctrl_typevar;
|
|
|
|
// Are we required to use the designated operand instead of the result?
|
|
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())));
|
|
}
|
|
|
|
// Compute the bit field encoding, c.f. instructions.rs.
|
|
assert!(fixed_results < 8 && fixed_values < 8, "Bit field encoding too tight");
|
|
let mut flags = fixed_results; // 3 bits
|
|
if use_typevar_operand {
|
|
flags |= 1<<3; // 4th bit
|
|
}
|
|
if requires_typevar_operand {
|
|
flags |= 1<<4; // 5th bit
|
|
}
|
|
flags |= fixed_values << 5; // 6th bit and more
|
|
|
|
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, "];");
|
|
}
|
|
|
|
/// Emit member initializers for an instruction format.
|
|
fn gen_member_inits(format: &InstructionFormat, fmt: &mut Formatter) {
|
|
// Immediate operands.
|
|
// We have local variables with the same names as the members.
|
|
for f in &format.imm_fields {
|
|
fmtln!(fmt, "{},", f.member);
|
|
}
|
|
|
|
// Value operands.
|
|
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(", "));
|
|
}
|
|
|
|
// Block operands
|
|
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(", "));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Emit a method for creating and inserting an instruction format.
|
|
///
|
|
/// All instruction formats take an `opcode` argument and a `ctrl_typevar` argument for deducing
|
|
/// the result types.
|
|
fn gen_format_constructor(format: &InstructionFormat, fmt: &mut Formatter) {
|
|
// Construct method arguments.
|
|
let mut args = vec![
|
|
"self".to_string(),
|
|
"opcode: Opcode".into(),
|
|
"ctrl_typevar: Type".into(),
|
|
];
|
|
|
|
// Normal operand arguments. Start with the immediate operands.
|
|
for f in &format.imm_fields {
|
|
args.push(format!("{}: {}", f.member, f.kind.rust_type));
|
|
}
|
|
|
|
// Then the block operands.
|
|
args.extend((0..format.num_block_operands).map(|i| format!("block{}: ir::BlockCall", i)));
|
|
|
|
// Then the value operands.
|
|
if format.has_value_list {
|
|
// Take all value arguments as a finished value list. The value lists
|
|
// are created by the individual instruction constructors.
|
|
args.push("args: ir::ValueList".into());
|
|
} else {
|
|
// Take a fixed number of value operands.
|
|
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| {
|
|
// Generate the instruction data.
|
|
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);");
|
|
}
|
|
|
|
// Assert that this opcode belongs to this format
|
|
fmtln!(fmt, "debug_assert_eq!(opcode.format(), InstructionFormat::from(&data), \"Wrong InstructionFormat for Opcode: {}\", opcode);");
|
|
|
|
fmt.line("self.build(data, ctrl_typevar)");
|
|
});
|
|
fmtln!(fmt, "}");
|
|
}
|
|
|
|
/// Emit a method for generating the instruction `inst`.
|
|
///
|
|
/// The method will create and insert an instruction, then return the result values, or the
|
|
/// instruction reference itself for instructions that don't have results.
|
|
fn gen_inst_builder(inst: &Instruction, format: &InstructionFormat, fmt: &mut Formatter) {
|
|
// Construct method arguments.
|
|
let mut args = vec![String::new()];
|
|
|
|
let mut args_doc = Vec::new();
|
|
let mut rets_doc = Vec::new();
|
|
|
|
// The controlling type variable will be inferred from the input values if
|
|
// possible. Otherwise, it is the first method argument.
|
|
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()));
|
|
}
|
|
}
|
|
|
|
// We need to mutate `self` if this instruction accepts a value list, or will construct
|
|
// BlockCall values.
|
|
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| {
|
|
// Convert all of the `Into<>` arguments.
|
|
for arg in into_args {
|
|
fmtln!(fmt, "let {} = {}.into();", arg, arg);
|
|
}
|
|
|
|
// Convert block references
|
|
for op in block_args {
|
|
fmtln!(
|
|
fmt,
|
|
"let {0} = self.data_flow_graph_mut().block_call({0}_label, {0}_args);",
|
|
op.name
|
|
);
|
|
}
|
|
|
|
// Arguments for instruction constructor.
|
|
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 {
|
|
// Infer the controlling type variable from the input operands.
|
|
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
|
|
);
|
|
|
|
// The format constructor will resolve the result types from the type var.
|
|
args.push("ctrl_typevar");
|
|
} else {
|
|
// This was an explicit method argument.
|
|
args.push(&poly.ctrl_typevar.name);
|
|
}
|
|
} else {
|
|
// No controlling type variable needed.
|
|
args.push("types::INVALID");
|
|
}
|
|
|
|
// Now add all of the immediate operands to the constructor arguments.
|
|
for &op_num in &inst.imm_opnums {
|
|
args.push(inst.operands_in[op_num].name);
|
|
}
|
|
|
|
// Finally, the value operands.
|
|
if format.has_value_list {
|
|
// We need to build a value list with all the arguments.
|
|
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 {
|
|
// With no value list, we're guaranteed to just have a set of fixed value operands.
|
|
for &op_num in &inst.value_opnums {
|
|
args.push(inst.operands_in[op_num].name);
|
|
}
|
|
}
|
|
|
|
// Call to the format constructor,
|
|
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, "}")
|
|
}
|
|
|
|
/// Which ISLE target are we generating code for?
|
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
enum IsleTarget {
|
|
/// Generating code for instruction selection and lowering.
|
|
Lower,
|
|
/// Generating code for CLIF to CLIF optimizations.
|
|
Opt,
|
|
}
|
|
|
|
fn gen_common_isle(
|
|
formats: &[&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();
|
|
|
|
// Collect and deduplicate the immediate types from the instruction fields.
|
|
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();
|
|
|
|
// Separate the `enum` immediates (e.g., `FloatCC`) from other kinds of
|
|
// immediates.
|
|
let (enums, others): (BTreeMap<_, _>, BTreeMap<_, _>) = immediate_types
|
|
.iter()
|
|
.partition(|(_, field)| field.enum_values().is_some());
|
|
|
|
// Generate all the extern type declarations we need for the non-`enum`
|
|
// immediates.
|
|
fmt.line(";;;; Extern type declarations for immediates ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;");
|
|
fmt.empty_line();
|
|
for ty in others.keys() {
|
|
fmtln!(fmt, "(type {} (primitive {}))", ty, ty);
|
|
}
|
|
fmt.empty_line();
|
|
|
|
// Generate the `enum` immediates, expanding all of the available variants
|
|
// into ISLE.
|
|
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)
|
|
}
|
|
|
|
// Generate all of the value arrays we need for `InstructionData` as well as
|
|
// the constructors and extractors for them.
|
|
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();
|
|
}
|
|
|
|
// Generate all of the block arrays we need for `InstructionData` as well as
|
|
// the constructors and extractors for them.
|
|
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();
|
|
}
|
|
|
|
// Generate the extern type declaration for `Opcode`.
|
|
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();
|
|
|
|
// Generate the extern type declaration for `InstructionData`.
|
|
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();
|
|
|
|
// Generate the helper extractors for each opcode's full instruction.
|
|
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
|
|
);
|
|
|
|
// Value and varargs operands.
|
|
if inst.format.has_value_list {
|
|
// The instruction format uses a value list, but the
|
|
// instruction itself might have not only a `&[Value]`
|
|
// varargs operand, but also one or more `Value` operands as
|
|
// well. If this is the case, then we need to read them off
|
|
// the front of the `ValueList`.
|
|
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();
|
|
}
|
|
|
|
// Immediates.
|
|
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();
|
|
}
|
|
|
|
// Blocks.
|
|
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(")");
|
|
|
|
// Generate a constructor if this is the mid-end prelude.
|
|
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
|
|
);
|
|
|
|
// Handle values. Note that we skip generating
|
|
// constructors for any instructions with variadic
|
|
// value lists. This is fine for the mid-end because
|
|
// in practice only calls and branches (for branch
|
|
// args) use this functionality, and neither can
|
|
// really be optimized or rewritten in the mid-end
|
|
// (currently).
|
|
//
|
|
// As a consequence, we only have to handle the
|
|
// one-`Value` case, in which the `Value` is directly
|
|
// in the `InstructionData`, and the multiple-`Value`
|
|
// case, in which the `Value`s are in a
|
|
// statically-sized array (e.g. `[Value; 2]` for a
|
|
// binary op).
|
|
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 {
|
|
// As above, get all bindings together, and pass
|
|
// to a sub-term; here we use a constructor to
|
|
// build the value array.
|
|
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();
|
|
}
|
|
}
|
|
|
|
// Immediates (non-value args).
|
|
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: &[&InstructionFormat],
|
|
instructions: &AllInstructions,
|
|
fmt: &mut Formatter,
|
|
) {
|
|
gen_common_isle(formats, instructions, fmt, IsleTarget::Opt);
|
|
}
|
|
|
|
fn gen_lower_isle(
|
|
formats: &[&InstructionFormat],
|
|
instructions: &AllInstructions,
|
|
fmt: &mut Formatter,
|
|
) {
|
|
gen_common_isle(formats, instructions, fmt, IsleTarget::Lower);
|
|
}
|
|
|
|
/// Generate an `enum` immediate in ISLE.
|
|
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();
|
|
}
|
|
|
|
/// Generate a Builder trait with methods for all instructions.
|
|
fn gen_builder(
|
|
instructions: &AllInstructions,
|
|
formats: &[&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: Vec<&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> {
|
|
// Opcodes.
|
|
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)?;
|
|
|
|
// ISLE DSL: mid-end ("opt") generated bindings.
|
|
let mut fmt = Formatter::new();
|
|
gen_opt_isle(&formats, all_inst, &mut fmt);
|
|
fmt.update_file(isle_opt_filename, isle_dir)?;
|
|
|
|
// ISLE DSL: lowering generated bindings.
|
|
let mut fmt = Formatter::new();
|
|
gen_lower_isle(&formats, all_inst, &mut fmt);
|
|
fmt.update_file(isle_lower_filename, isle_dir)?;
|
|
|
|
// Instruction builder.
|
|
let mut fmt = Formatter::new();
|
|
gen_builder(all_inst, &formats, &mut fmt);
|
|
fmt.update_file(inst_builder_filename, out_dir)?;
|
|
|
|
Ok(())
|
|
}
|