This PR fixes #4066: it modifies the Cranelift `build.rs` workflow to invoke the ISLE DSL compiler on every compilation, rather than only when the user specifies a special "rebuild ISLE" feature. The main benefit of this change is that it vastly simplifies the mental model required of developers, and removes a bunch of failure modes we have tried to work around in other ways. There is now just one "source of truth", the ISLE source itself, in the repository, and so there is no need to understand a special "rebuild" step and how to handle merge errors. There is no special process needed to develop the compiler when modifying the DSL. And there is no "noise" in the git history produced by constantly-regenerated files. The two main downsides we discussed in #4066 are: - Compile time could increase, by adding more to the "meta" step before the main build; - It becomes less obvious where the source definitions are (everything becomes more "magic"), which makes exploration and debugging harder. This PR addresses each of these concerns: 1. To maintain reasonable compile time, it includes work to cut down the dependencies of the `cranelift-isle` crate to *nothing* (only the Rust stdlib), in the default build. It does this by putting the error-reporting bits (`miette` crate) under an optional feature, and the logging (`log` crate) under a feature-controlled macro, and manually writing an `Error` impl rather than using `thiserror`. This completely avoids proc macros and the `syn` build slowness. The user can still get nice errors out of `miette`: this is enabled by specifying a Cargo feature `--features isle-errors`. 2. To allow the user to optionally inspect the generated source, which nominally lives in a hard-to-find path inside `target/` now, this PR adds a feature `isle-in-source-tree` that, as implied by the name, moves the target for ISLE generated source into the source tree, at `cranelift/codegen/isle_generated_source/`. It seems reasonable to do this when an explicit feature (opt-in) is specified because this is how ISLE regeneration currently works as well. To prevent surprises, if the feature is *not* specified, the build fails if this directory exists.
183 lines
5.0 KiB
Rust
183 lines
5.0 KiB
Rust
use std::collections::HashMap;
|
|
|
|
use crate::cdsl::typevar::TypeVar;
|
|
|
|
/// An instruction operand can be an *immediate*, an *SSA value*, or an *entity reference*. The
|
|
/// type of the operand is one of:
|
|
///
|
|
/// 1. A `ValueType` instance indicates an SSA value operand with a concrete type.
|
|
///
|
|
/// 2. A `TypeVar` instance indicates an SSA value operand, and the instruction is polymorphic over
|
|
/// the possible concrete types that the type variable can assume.
|
|
///
|
|
/// 3. An `ImmediateKind` instance indicates an immediate operand whose value is encoded in the
|
|
/// instruction itself rather than being passed as an SSA value.
|
|
///
|
|
/// 4. An `EntityRefKind` instance indicates an operand that references another entity in the
|
|
/// function, typically something declared in the function preamble.
|
|
#[derive(Clone, Debug)]
|
|
pub(crate) struct Operand {
|
|
/// Name of the operand variable, as it appears in function parameters, legalizations, etc.
|
|
pub name: &'static str,
|
|
|
|
/// Type of the operand.
|
|
pub kind: OperandKind,
|
|
|
|
doc: Option<&'static str>,
|
|
}
|
|
|
|
impl Operand {
|
|
pub fn new(name: &'static str, kind: impl Into<OperandKind>) -> Self {
|
|
Self {
|
|
name,
|
|
doc: None,
|
|
kind: kind.into(),
|
|
}
|
|
}
|
|
pub fn with_doc(mut self, doc: &'static str) -> Self {
|
|
self.doc = Some(doc);
|
|
self
|
|
}
|
|
|
|
pub fn doc(&self) -> &str {
|
|
if let Some(doc) = &self.doc {
|
|
return doc;
|
|
}
|
|
match &self.kind.fields {
|
|
OperandKindFields::TypeVar(tvar) => &tvar.doc,
|
|
_ => self.kind.doc(),
|
|
}
|
|
}
|
|
|
|
pub fn is_value(&self) -> bool {
|
|
match self.kind.fields {
|
|
OperandKindFields::TypeVar(_) => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
pub fn type_var(&self) -> Option<&TypeVar> {
|
|
match &self.kind.fields {
|
|
OperandKindFields::TypeVar(typevar) => Some(typevar),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn is_varargs(&self) -> bool {
|
|
match self.kind.fields {
|
|
OperandKindFields::VariableArgs => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
/// Returns true if the operand has an immediate kind or is an EntityRef.
|
|
pub fn is_immediate_or_entityref(&self) -> bool {
|
|
match self.kind.fields {
|
|
OperandKindFields::ImmEnum(_)
|
|
| OperandKindFields::ImmValue
|
|
| OperandKindFields::EntityRef => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
/// Returns true if the operand has an immediate kind.
|
|
pub fn is_immediate(&self) -> bool {
|
|
match self.kind.fields {
|
|
OperandKindFields::ImmEnum(_) | OperandKindFields::ImmValue => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
pub fn is_cpu_flags(&self) -> bool {
|
|
match &self.kind.fields {
|
|
OperandKindFields::TypeVar(type_var)
|
|
if type_var.name == "iflags" || type_var.name == "fflags" =>
|
|
{
|
|
true
|
|
}
|
|
_ => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub type EnumValues = HashMap<&'static str, &'static str>;
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub(crate) enum OperandKindFields {
|
|
EntityRef,
|
|
VariableArgs,
|
|
ImmValue,
|
|
ImmEnum(EnumValues),
|
|
TypeVar(TypeVar),
|
|
}
|
|
|
|
impl OperandKindFields {
|
|
/// Return the [EnumValues] for this field if it is an `enum`.
|
|
pub(crate) fn enum_values(&self) -> Option<&EnumValues> {
|
|
match self {
|
|
OperandKindFields::ImmEnum(map) => Some(map),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub(crate) struct OperandKind {
|
|
/// String representation of the Rust type mapping to this OperandKind.
|
|
pub rust_type: &'static str,
|
|
|
|
/// Name of this OperandKind in the format's member field.
|
|
pub rust_field_name: &'static str,
|
|
|
|
/// Type-specific fields for this OperandKind.
|
|
pub fields: OperandKindFields,
|
|
|
|
doc: Option<&'static str>,
|
|
}
|
|
|
|
impl OperandKind {
|
|
pub fn new(
|
|
rust_field_name: &'static str,
|
|
rust_type: &'static str,
|
|
fields: OperandKindFields,
|
|
doc: &'static str,
|
|
) -> Self {
|
|
Self {
|
|
rust_field_name,
|
|
rust_type,
|
|
fields,
|
|
doc: Some(doc),
|
|
}
|
|
}
|
|
fn doc(&self) -> &str {
|
|
if let Some(doc) = &self.doc {
|
|
return doc;
|
|
}
|
|
match &self.fields {
|
|
OperandKindFields::TypeVar(type_var) => &type_var.doc,
|
|
// The only method to create an OperandKind with `doc` set to None is using a TypeVar,
|
|
// so all other options are unreachable here.
|
|
OperandKindFields::ImmEnum(_)
|
|
| OperandKindFields::ImmValue
|
|
| OperandKindFields::EntityRef
|
|
| OperandKindFields::VariableArgs => unreachable!(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Into<OperandKind> for &TypeVar {
|
|
fn into(self) -> OperandKind {
|
|
OperandKind {
|
|
rust_field_name: "value",
|
|
rust_type: "ir::Value",
|
|
fields: OperandKindFields::TypeVar(self.into()),
|
|
doc: None,
|
|
}
|
|
}
|
|
}
|
|
impl Into<OperandKind> for &OperandKind {
|
|
fn into(self) -> OperandKind {
|
|
self.clone()
|
|
}
|
|
}
|