This lets us avoid the cost of `cranelift_codegen::ir::Opcode` to `peepmatic_runtime::Operator` conversion overhead, and paves the way for allowing Peepmatic to support non-clif optimizations (e.g. vcode optimizations). Rather than defining our own `peepmatic::Operator` type like we used to, now the whole `peepmatic` crate is effectively generic over a `TOperator` type parameter. For the Cranelift integration, we use `cranelift_codegen::ir::Opcode` as the concrete type for our `TOperator` type parameter. For testing, we also define a `TestOperator` type, so that we can test Peepmatic code without building all of Cranelift, and we can keep them somewhat isolated from each other. The methods that `peepmatic::Operator` had are now translated into trait bounds on the `TOperator` type. These traits need to be shared between all of `peepmatic`, `peepmatic-runtime`, and `cranelift-codegen`'s Peepmatic integration. Therefore, these new traits live in a new crate: `peepmatic-traits`. This crate acts as a header file of sorts for shared trait/type/macro definitions. Additionally, the `peepmatic-runtime` crate no longer depends on the `peepmatic-macro` procedural macro crate, which should lead to faster build times for Cranelift when it is using pre-built peephole optimizers.
150 lines
5.2 KiB
Rust
150 lines
5.2 KiB
Rust
//! Interfacing with actual instructions.
|
|
|
|
use crate::part::{Constant, Part};
|
|
use crate::paths::Path;
|
|
use crate::r#type::Type;
|
|
use std::fmt::Debug;
|
|
use std::hash::Hash;
|
|
use std::num::NonZeroU32;
|
|
|
|
/// A trait for interfacing with actual instruction sequences.
|
|
///
|
|
/// This trait enables both:
|
|
///
|
|
/// * `peepmatic-runtime` to be used by `cranelift-codegen` without a circular
|
|
/// dependency from `peepmatic-runtime` to `cranelift-codegen` to get access
|
|
/// to Cranelift's IR types, and
|
|
///
|
|
/// * enables us to write local tests that exercise peephole optimizers on a
|
|
/// simple, testing-only instruction set without pulling in all of Cranelift.
|
|
///
|
|
/// Finally, this should also make the task of adding support for Cranelift's
|
|
/// new `MachInst` and vcode backend easier, since all that needs to be done is
|
|
/// "just" implementing this trait. (And probably add/modify some
|
|
/// `peepmatic_runtime::operation::Operation`s as well).
|
|
///
|
|
/// ## Safety
|
|
///
|
|
/// See doc comment for `instruction_result_bit_width`.
|
|
pub unsafe trait InstructionSet<'a> {
|
|
/// Mutable context passed into all trait methods. Can be whatever you want!
|
|
///
|
|
/// In practice, this is a `FuncCursor` for `cranelift-codegen`'s trait
|
|
/// implementation.
|
|
type Context;
|
|
|
|
/// An operator.
|
|
type Operator: 'static + Copy + Debug + Eq + Hash + Into<NonZeroU32>;
|
|
|
|
/// An instruction (or identifier for an instruction).
|
|
type Instruction: Copy + Debug + Eq;
|
|
|
|
/// Replace the `old` instruction with `new`.
|
|
///
|
|
/// `new` is either a `Part::Instruction` or a constant `Part::Boolean` or
|
|
/// `Part::Integer`. In the former case, it can directly replace `old`. In
|
|
/// the latter case, implementations of this trait should transparently
|
|
/// create an `iconst` or `bconst` instruction to wrap the given constant.
|
|
///
|
|
/// `new` will never be `Part::ConditionCode`.
|
|
fn replace_instruction(
|
|
&self,
|
|
context: &mut Self::Context,
|
|
old: Self::Instruction,
|
|
new: Part<Self::Instruction>,
|
|
) -> Self::Instruction;
|
|
|
|
/// Get the instruction, constant, or condition code at the given path.
|
|
///
|
|
/// If there is no such entity at the given path (e.g. we run into a
|
|
/// function parameter and can't traverse the path any further) then `None`
|
|
/// should be returned.
|
|
fn get_part_at_path(
|
|
&self,
|
|
context: &mut Self::Context,
|
|
root: Self::Instruction,
|
|
path: Path,
|
|
) -> Option<Part<Self::Instruction>>;
|
|
|
|
/// Get the given instruction's operator.
|
|
///
|
|
/// If the instruction isn't supported, then `None` should be returned.
|
|
fn operator(
|
|
&self,
|
|
context: &mut Self::Context,
|
|
instr: Self::Instruction,
|
|
) -> Option<Self::Operator>;
|
|
|
|
/// Make a unary instruction.
|
|
///
|
|
/// If the type is not given, then it should be inferred.
|
|
fn make_inst_1(
|
|
&self,
|
|
context: &mut Self::Context,
|
|
root: Self::Instruction,
|
|
operator: Self::Operator,
|
|
r#type: Type,
|
|
a: Part<Self::Instruction>,
|
|
) -> Self::Instruction;
|
|
|
|
/// Make a binary instruction.
|
|
///
|
|
/// Operands are given as immediates first and arguments following
|
|
/// them. Condition codes are treated as immediates. So if we are creating
|
|
/// an `iadd_imm` instruction, then `a` will be the constant integer
|
|
/// immediate and `b` will be the instruction whose result is the dynamic
|
|
/// argument.
|
|
fn make_inst_2(
|
|
&self,
|
|
context: &mut Self::Context,
|
|
root: Self::Instruction,
|
|
operator: Self::Operator,
|
|
r#type: Type,
|
|
a: Part<Self::Instruction>,
|
|
b: Part<Self::Instruction>,
|
|
) -> Self::Instruction;
|
|
|
|
/// Make a ternary instruction.
|
|
///
|
|
/// Operands are given as immediates first and arguments following
|
|
/// them. Condition codes are treated as immediates. So if we are creating
|
|
/// an `icmp` instruction, then `a` will be the condition code, and `b` and
|
|
/// `c` will be instructions whose results are the dynamic arguments.
|
|
fn make_inst_3(
|
|
&self,
|
|
context: &mut Self::Context,
|
|
root: Self::Instruction,
|
|
operator: Self::Operator,
|
|
r#type: Type,
|
|
a: Part<Self::Instruction>,
|
|
b: Part<Self::Instruction>,
|
|
c: Part<Self::Instruction>,
|
|
) -> Self::Instruction;
|
|
|
|
/// Try to resolve the given instruction into a constant value.
|
|
///
|
|
/// If we can tell that the instruction returns a constant value, then
|
|
/// return that constant value as either a `Part::Boolean` or
|
|
/// `Part::Integer`. Otherwise, return `None`.
|
|
fn instruction_to_constant(
|
|
&self,
|
|
context: &mut Self::Context,
|
|
inst: Self::Instruction,
|
|
) -> Option<Constant>;
|
|
|
|
/// Get the bit width of the given instruction's result.
|
|
///
|
|
/// ## Safety
|
|
///
|
|
/// There is code that makes memory-safety assumptions that the result is
|
|
/// always one of 1, 8, 16, 32, 64, or 128. Implementors must uphold this.
|
|
fn instruction_result_bit_width(
|
|
&self,
|
|
context: &mut Self::Context,
|
|
inst: Self::Instruction,
|
|
) -> u8;
|
|
|
|
/// Get the size of a native word in bits.
|
|
fn native_word_size_in_bits(&self, context: &mut Self::Context) -> u8;
|
|
}
|