egraph-based midend: draw the rest of the owl (productionized). (#4953)

* egraph-based midend: draw the rest of the owl.

* Rename `egg` submodule of cranelift-codegen to `egraph`.

* Apply some feedback from @jsharp during code walkthrough.

* Remove recursion from find_best_node by doing a single pass.

Rather than recursively computing the lowest-cost node for a given
eclass and memoizing the answer at each eclass node, we can do a single
forward pass; because every eclass node refers only to earlier nodes,
this is sufficient. The behavior may slightly differ from the earlier
behavior because we cannot short-circuit costs to zero once a node is
elaborated; but in practice this should not matter.

* Make elaboration non-recursive.

Use an explicit stack instead (with `ElabStackEntry` entries,
alongside a result stack).

* Make elaboration traversal of the domtree non-recursive/stack-safe.

* Work analysis logic in Cranelift-side egraph glue into a general analysis framework in cranelift-egraph.

* Apply static recursion limit to rule application.

* Fix aarch64 wrt dynamic-vector support -- broken rebase.

* Topo-sort cranelift-egraph before cranelift-codegen in publish script, like the comment instructs me to!

* Fix multi-result call testcase.

* Include `cranelift-egraph` in `PUBLISHED_CRATES`.

* Fix atomic_rmw: not really a load.

* Remove now-unnecessary PartialOrd/Ord derivations.

* Address some code-review comments.

* Review feedback.

* Review feedback.

* No overlap in mid-end rules, because we are defining a multi-constructor.

* rustfmt

* Review feedback.

* Review feedback.

* Review feedback.

* Review feedback.

* Remove redundant `mut`.

* Add comment noting what rules can do.

* Review feedback.

* Clarify comment wording.

* Update `has_memory_fence_semantics`.

* Apply @jameysharp's improved loop-level computation.

Co-authored-by: Jamey Sharp <jamey@minilop.net>

* Fix suggestion commit.

* Fix off-by-one in new loop-nest analysis.

* Review feedback.

* Review feedback.

* Review feedback.

* Use `Default`, not `std::default::Default`, as per @fitzgen

Co-authored-by: Nick Fitzgerald <fitzgen@gmail.com>

* Apply @fitzgen's comment elaboration to a doc-comment.

Co-authored-by: Nick Fitzgerald <fitzgen@gmail.com>

* Add stat for hitting the rewrite-depth limit.

* Some code motion in split prelude to make the diff a little clearer wrt `main`.

* Take @jameysharp's suggested `try_into()` usage for blockparam indices.

Co-authored-by: Jamey Sharp <jamey@minilop.net>

* Take @jameysharp's suggestion to avoid double-match on load op.

Co-authored-by: Jamey Sharp <jamey@minilop.net>

* Fix suggestion (add import).

* Review feedback.

* Fix stack_load handling.

* Remove redundant can_store case.

* Take @jameysharp's suggested improvement to FuncEGraph::build() logic

Co-authored-by: Jamey Sharp <jamey@minilop.net>

* Tweaks to FuncEGraph::build() on top of suggestion.

* Take @jameysharp's suggested clarified condition

Co-authored-by: Jamey Sharp <jamey@minilop.net>

* Clean up after suggestion (unused variable).

* Fix loop analysis.

* loop level asserts

* Revert constant-space loop analysis -- edge cases were incorrect, so let's go with the simple thing for now.

* Take @jameysharp's suggestion re: result_tys

Co-authored-by: Jamey Sharp <jamey@minilop.net>

* Fix up after suggestion

* Take @jameysharp's suggestion to use fold rather than reduce

Co-authored-by: Jamey Sharp <jamey@minilop.net>

* Fixup after suggestion

* Take @jameysharp's suggestion to remove elaborate_eclass_use's return value.

* Clarifying comment in terminator insts.

Co-authored-by: Jamey Sharp <jamey@minilop.net>
Co-authored-by: Nick Fitzgerald <fitzgen@gmail.com>
This commit is contained in:
Chris Fallin
2022-10-11 18:15:53 -07:00
committed by GitHub
parent e2f1ced0b6
commit 2be12a5167
59 changed files with 5125 additions and 1580 deletions

View File

@@ -60,36 +60,52 @@ fn gen_formats(formats: &[&InstructionFormat], fmt: &mut Formatter) {
fmt.empty_line();
}
/// Generate the InstructionData enum.
/// Generate the InstructionData and InstructionImms enums.
///
/// 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.
///
/// `InstructionImms` stores everything about an instruction except for the arguments: in other
/// words, the `Opcode` and any immediates or other parameters. `InstructionData` stores this, plus
/// the SSA `Value` arguments.
fn gen_instruction_data(formats: &[&InstructionFormat], fmt: &mut Formatter) {
fmt.line("#[derive(Clone, Debug, PartialEq, Hash)]");
fmt.line(r#"#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]"#);
fmt.line("#[allow(missing_docs)]");
fmt.line("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);
}
for field in &format.imm_fields {
fmtln!(fmt, "{}: {},", field.member, field.kind.rust_type);
}
});
fmtln!(fmt, "},");
for (name, include_args) in &[("InstructionData", true), ("InstructionImms", false)] {
fmt.line("#[derive(Clone, Debug, PartialEq, Hash)]");
if !include_args {
// `InstructionImms` gets some extra derives: it acts like
// a sort of extended opcode and we want to allow for
// hashconsing via Eq. `Copy` also turns out to be useful.
fmt.line("#[derive(Copy, Eq)]");
}
});
fmt.line("}");
fmt.line(r#"#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]"#);
fmt.line("#[allow(missing_docs)]");
// generate `enum InstructionData` or `enum InstructionImms`.
// (This comment exists so one can grep for `enum InstructionData`!)
fmtln!(fmt, "pub enum {} {{", name);
fmt.indent(|fmt| {
for format in formats {
fmtln!(fmt, "{} {{", format.name);
fmt.indent(|fmt| {
fmt.line("opcode: Opcode,");
if *include_args {
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);
}
}
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) {
@@ -150,6 +166,122 @@ fn gen_arguments_method(formats: &[&InstructionFormat], fmt: &mut Formatter, is_
fmtln!(fmt, "}");
}
/// Generate the conversion from `InstructionData` to `InstructionImms`, stripping out the
/// `Value`s.
fn gen_instruction_data_to_instruction_imms(formats: &[&InstructionFormat], fmt: &mut Formatter) {
fmt.line("impl std::convert::From<&InstructionData> for InstructionImms {");
fmt.indent(|fmt| {
fmt.doc_comment("Convert an `InstructionData` into an `InstructionImms`.");
fmt.line("fn from(data: &InstructionData) -> InstructionImms {");
fmt.indent(|fmt| {
fmt.line("match data {");
fmt.indent(|fmt| {
for format in formats {
fmtln!(fmt, "InstructionData::{} {{", format.name);
fmt.indent(|fmt| {
fmt.line("opcode,");
for field in &format.imm_fields {
fmtln!(fmt, "{},", field.member);
}
fmt.line("..");
});
fmtln!(fmt, "}} => InstructionImms::{} {{", format.name);
fmt.indent(|fmt| {
fmt.line("opcode: *opcode,");
for field in &format.imm_fields {
fmtln!(fmt, "{}: {}.clone(),", field.member, field.member);
}
});
fmt.line("},");
}
});
fmt.line("}");
});
fmt.line("}");
});
fmt.line("}");
fmt.empty_line();
}
/// Generate the conversion from `InstructionImms` to `InstructionData`, adding the
/// `Value`s.
fn gen_instruction_imms_to_instruction_data(formats: &[&InstructionFormat], fmt: &mut Formatter) {
fmt.line("impl InstructionImms {");
fmt.indent(|fmt| {
fmt.doc_comment("Convert an `InstructionImms` into an `InstructionData` by adding args.");
fmt.line(
"pub fn with_args(&self, values: &[Value], value_list: &mut ValueListPool) -> InstructionData {",
);
fmt.indent(|fmt| {
fmt.line("match self {");
fmt.indent(|fmt| {
for format in formats {
fmtln!(fmt, "InstructionImms::{} {{", format.name);
fmt.indent(|fmt| {
fmt.line("opcode,");
for field in &format.imm_fields {
fmtln!(fmt, "{},", field.member);
}
});
fmt.line("} => {");
if format.has_value_list {
fmtln!(fmt, "let args = ValueList::from_slice(values, value_list);");
}
fmt.indent(|fmt| {
fmtln!(fmt, "InstructionData::{} {{", format.name);
fmt.indent(|fmt| {
fmt.line("opcode: *opcode,");
for field in &format.imm_fields {
fmtln!(fmt, "{}: {}.clone(),", field.member, field.member);
}
if format.has_value_list {
fmtln!(fmt, "args,");
} else if format.num_value_operands == 1 {
fmtln!(fmt, "arg: values[0],");
} else if format.num_value_operands > 0 {
let mut args = vec![];
for i in 0..format.num_value_operands {
args.push(format!("values[{}]", i));
}
fmtln!(fmt, "args: [{}],", args.join(", "));
}
});
fmt.line("}");
});
fmt.line("},");
}
});
fmt.line("}");
});
fmt.line("}");
});
fmt.line("}");
fmt.empty_line();
}
/// Generate the `opcode` method on InstructionImms.
fn gen_instruction_imms_impl(formats: &[&InstructionFormat], fmt: &mut Formatter) {
fmt.line("impl InstructionImms {");
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.line("}");
fmt.empty_line();
}
/// Generate the boring parts of the InstructionData implementation.
///
/// These methods in `impl InstructionData` can be generated automatically from the instruction
@@ -1070,7 +1202,12 @@ fn gen_inst_builder(inst: &Instruction, format: &InstructionFormat, fmt: &mut Fo
fmtln!(fmt, "}")
}
fn gen_isle(formats: &[&InstructionFormat], instructions: &AllInstructions, fmt: &mut Formatter) {
fn gen_common_isle(
formats: &[&InstructionFormat],
instructions: &AllInstructions,
fmt: &mut Formatter,
is_lower: bool,
) {
use std::collections::{BTreeMap, BTreeSet};
use std::fmt::Write;
@@ -1123,40 +1260,46 @@ fn gen_isle(formats: &[&InstructionFormat], instructions: &AllInstructions, fmt:
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);
if is_lower {
// 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();
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`.
@@ -1175,21 +1318,33 @@ fn gen_isle(formats: &[&InstructionFormat], instructions: &AllInstructions, fmt:
fmt.line(")");
fmt.empty_line();
// Generate the extern type declaration for `InstructionData`.
fmt.line(";;;; `InstructionData` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;");
// Generate the extern type declaration for `InstructionData`
// (lowering) or `InstructionImms` (opt).
let inst_data_name = if is_lower {
"InstructionData"
} else {
"InstructionImms"
};
fmtln!(
fmt,
";;;; `{}` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;",
inst_data_name
);
fmt.empty_line();
fmt.line("(type InstructionData extern");
fmtln!(fmt, "(type {} extern", inst_data_name);
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();
if is_lower {
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();
}
}
for field in &format.imm_fields {
write!(
@@ -1210,85 +1365,157 @@ fn gen_isle(formats: &[&InstructionFormat], instructions: &AllInstructions, fmt:
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` ;;;;;;;;");
fmtln!(
fmt,
";;;; Extracting Opcode, Operands, and Immediates from `{}` ;;;;;;;;",
inst_data_name
);
fmt.empty_line();
let ret_ty = if is_lower { "Inst" } else { "Id" };
for inst in instructions {
if !is_lower && inst.format.has_value_list {
continue;
}
fmtln!(
fmt,
"(decl {} ({}) Inst)",
"(decl {} ({}{}) {})",
inst.name,
if is_lower { "" } else { "Type " },
inst.operands_in
.iter()
.map(|o| {
let ty = o.kind.rust_type;
if ty == "&[Value]" {
"ValueSlice"
if is_lower {
if ty == "&[Value]" {
"ValueSlice"
} else {
ty.rsplit("::").next().unwrap()
}
} else {
ty.rsplit("::").next().unwrap()
if ty == "&[Value]" {
panic!("value slice in mid-end extractor");
} else if ty == "Value" || ty == "ir::Value" {
"Id"
} else {
ty.rsplit("::").next().unwrap()
}
}
})
.collect::<Vec<_>>()
.join(" ")
.join(" "),
ret_ty
);
fmtln!(fmt, "(extractor");
fmt.indent(|fmt| {
fmtln!(
fmt,
"({} {})",
"({} {}{})",
inst.name,
if is_lower { "" } else { "ty " },
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
);
// 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 {
if is_lower {
let mut s = format!(
"(inst_data (InstructionData.{} (Opcode.{})",
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,
" (unwrap_head_value_list_{} {} {})",
values.len(),
values.join(" "),
varargs
" {}",
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();
}
} 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 {
// 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();
}
s.push_str("))");
fmt.line(&s);
} else {
// Mid-end case.
let mut s = format!(
"(enodes ty (InstructionImms.{} (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();
}
// End of `InstructionImms`.
s.push_str(")");
// Second arg to `enode`: value args.
assert!(!inst.operands_in.iter().any(|op| op.is_varargs()));
let values = inst
.operands_in
.iter()
@@ -1299,31 +1526,83 @@ fn gen_isle(formats: &[&InstructionFormat], instructions: &AllInstructions, fmt:
let values = values.join(" ");
write!(
&mut s,
" (value_array_{} {})",
" (id_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())
.collect();
assert_eq!(imm_operands.len(), inst.format.imm_fields.len());
for op in imm_operands {
write!(&mut s, " {}", op.name).unwrap();
s.push_str(")");
fmt.line(&s);
}
s.push_str("))");
fmt.line(&s);
});
fmt.line(")");
// Generate a constructor if this is the mid-end prelude.
if !is_lower {
fmtln!(
fmt,
"(rule ({} ty {})",
inst.name,
inst.operands_in
.iter()
.map(|o| o.name)
.collect::<Vec<_>>()
.join(" ")
);
fmt.indent(|fmt| {
let mut s = format!(
"(pure_enode ty (InstructionImms.{} (Opcode.{})",
inst.format.name, inst.camel_name
);
for o in inst
.operands_in
.iter()
.filter(|o| !o.is_value() && !o.is_varargs())
{
write!(&mut s, " {}", o.name).unwrap();
}
s.push_str(")");
let values = inst
.operands_in
.iter()
.filter(|o| o.is_value())
.map(|o| o.name)
.collect::<Vec<_>>();
let values = values.join(" ");
write!(
&mut s,
" (id_array_{} {})",
inst.format.num_value_operands, values
)
.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, /* is_lower = */ false);
}
fn gen_lower_isle(
formats: &[&InstructionFormat],
instructions: &AllInstructions,
fmt: &mut Formatter,
) {
gen_common_isle(formats, instructions, fmt, /* is_lower = */ true);
}
/// Generate an `enum` immediate in ISLE.
fn gen_isle_enum(name: &str, mut variants: Vec<&str>, fmt: &mut Formatter) {
variants.sort();
@@ -1388,7 +1667,8 @@ pub(crate) fn generate(
all_inst: &AllInstructions,
opcode_filename: &str,
inst_builder_filename: &str,
isle_filename: &str,
isle_opt_filename: &str,
isle_lower_filename: &str,
out_dir: &str,
isle_dir: &str,
) -> Result<(), error::Error> {
@@ -1398,16 +1678,24 @@ pub(crate) fn generate(
gen_instruction_data(&formats, &mut fmt);
fmt.empty_line();
gen_instruction_data_impl(&formats, &mut fmt);
gen_instruction_data_to_instruction_imms(&formats, &mut fmt);
gen_instruction_imms_impl(&formats, &mut fmt);
gen_instruction_imms_to_instruction_data(&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.
// ISLE DSL: mid-end ("opt") generated bindings.
let mut fmt = Formatter::new();
gen_isle(&formats, all_inst, &mut fmt);
fmt.update_file(isle_filename, isle_dir)?;
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();