Initial ISLE integration with the x64 backend

On the build side, this commit introduces two things:

1. The automatic generation of various ISLE definitions for working with
CLIF. Specifically, it generates extern type definitions for clif opcodes and
the clif instruction data `enum`, as well as extractors for matching each clif
instructions. This happens inside the `cranelift-codegen-meta` crate.

2. The compilation of ISLE DSL sources to Rust code, that can be included in the
main `cranelift-codegen` compilation.

Next, this commit introduces the integration glue code required to get
ISLE-generated Rust code hooked up in clif-to-x64 lowering. When lowering a clif
instruction, we first try to use the ISLE code path. If it succeeds, then we are
done lowering this instruction. If it fails, then we proceed along the existing
hand-written code path for lowering.

Finally, this commit ports many lowering rules over from hand-written,
open-coded Rust to ISLE.

In the process of supporting ISLE, this commit also makes the x64 `Inst` capable
of expressing SSA by supporting 3-operand forms for all of the existing
instructions that only have a 2-operand form encoding:

    dst = src1 op src2

Rather than only the typical x86-64 2-operand form:

    dst = dst op src

This allows `MachInst` to be in SSA form, since `dst` and `src1` are
disentangled.

("3-operand" and "2-operand" are a little bit of a misnomer since not all
operations are binary operations, but we do the same thing for, e.g., unary
operations by disentangling the sole operand from the result.)

There are two motivations for this change:

1. To allow ISLE lowering code to have value-equivalence semantics. We want ISLE
   lowering to translate a CLIF expression that evaluates to some value into a
   `MachInst` expression that evaluates to the same value. We want both the
   lowering itself and the resulting `MachInst` to be pure and referentially
   transparent. This is both a nice paradigm for compiler writers that are
   authoring and maintaining lowering rules and is a prerequisite to any sort of
   formal verification of our lowering rules in the future.

2. Better align `MachInst` with `regalloc2`'s API, which requires that the input
   be in SSA form.
This commit is contained in:
Nick Fitzgerald
2021-10-12 17:11:58 -07:00
parent f227699536
commit d377b665c6
29 changed files with 9086 additions and 1032 deletions

View File

@@ -1,5 +1,6 @@
//! Generate instruction data (including opcodes, formats, builders, etc.).
use std::fmt;
use std::path::Path;
use cranelift_codegen_shared::constant_hash;
@@ -1084,6 +1085,243 @@ fn gen_inst_builder(inst: &Instruction, format: &InstructionFormat, fmt: &mut Fo
fmtln!(fmt, "}")
}
#[cfg(feature = "rebuild-isle")]
fn gen_isle(formats: &[&InstructionFormat], instructions: &AllInstructions, fmt: &mut Formatter) {
use std::collections::BTreeSet;
use std::fmt::Write;
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();
// Generate all the extern type declarations we need for various immediates.
fmt.line(";;;; Extern type declarations for immediates ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;");
fmt.empty_line();
let imm_tys: BTreeSet<_> = formats
.iter()
.flat_map(|f| {
f.imm_fields
.iter()
.map(|i| i.kind.rust_type.rsplit("::").next().unwrap())
.collect::<Vec<_>>()
})
.collect();
for ty in imm_tys {
fmtln!(fmt, "(type {} (primitive {}))", ty, ty);
}
fmt.empty_line();
// 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 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`.
fmt.line(";;;; `InstructionData` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;");
fmt.empty_line();
fmt.line("(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.typevar_operand.is_some() {
if format.has_value_list {
s.push_str(" (args ValueList)");
} else if format.num_value_operands == 1 {
s.push_str(" (arg Value)");
} else {
write!(&mut s, " (args ValueArray{})", format.num_value_operands).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.
//
// TODO: if/when we port our peephole optimization passes to ISLE we will
// want helper constructors as well.
fmt.line(";;;; Extracting Opcode, Operands, and Immediates from `InstructionData` ;;;;;;;;");
fmt.empty_line();
for inst in instructions {
fmtln!(
fmt,
"(decl {} ({}) Inst)",
inst.name,
inst.operands_in
.iter()
.map(|o| {
let ty = o.kind.rust_type;
if ty == "&[Value]" {
"ValueSlice"
} else {
ty.rsplit("::").next().unwrap()
}
})
.collect::<Vec<_>>()
.join(" ")
);
fmtln!(fmt, "(extractor");
fmt.indent(|fmt| {
fmtln!(
fmt,
"({} {})",
inst.name,
inst.operands_in
.iter()
.map(|o| { o.name })
.collect::<Vec<_>>()
.join(" ")
);
let mut s = format!(
"(inst_data (InstructionData.{} (Opcode.{})",
inst.format.name, inst.camel_name
);
// Immediates.
let imm_operands: Vec<_> = inst
.operands_in
.iter()
.filter(|o| !o.is_value() && !o.is_varargs())
.collect();
assert_eq!(imm_operands.len(), inst.format.imm_fields.len());
for op in imm_operands {
write!(&mut s, " {}", op.name).unwrap();
}
// Value and varargs operands.
if inst.format.typevar_operand.is_some() {
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 {
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();
}
}
s.push_str("))");
fmt.line(&s);
});
fmt.line(")");
fmt.empty_line();
}
}
/// Generate a Builder trait with methods for all instructions.
fn gen_builder(
instructions: &AllInstructions,
@@ -1128,7 +1366,9 @@ pub(crate) fn generate(
all_inst: &AllInstructions,
opcode_filename: &str,
inst_builder_filename: &str,
isle_filename: &str,
out_dir: &str,
crate_dir: &Path,
) -> Result<(), error::Error> {
// Opcodes.
let mut fmt = Formatter::new();
@@ -1144,6 +1384,20 @@ pub(crate) fn generate(
gen_try_from(all_inst, &mut fmt);
fmt.update_file(opcode_filename, out_dir)?;
// ISLE DSL.
#[cfg(feature = "rebuild-isle")]
{
let mut fmt = Formatter::new();
gen_isle(&formats, all_inst, &mut fmt);
let crate_src_dir = crate_dir.join("src");
fmt.update_file(isle_filename, &crate_src_dir.display().to_string())?;
}
#[cfg(not(feature = "rebuild-isle"))]
{
// Silence unused variable warnings.
let _ = (isle_filename, crate_dir);
}
// Instruction builder.
let mut fmt = Formatter::new();
gen_builder(all_inst, &formats, &mut fmt);