From 2390e3e3f0f891d62e00a0178e5f6d55216681ca Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 24 Jan 2017 11:19:31 -0800 Subject: [PATCH] Add operand register constraints. Every encoding recipe must specify register constraints on input and output values. Generate recipe constraint tables along with the other encoding tables. --- cranelift/docs/metaref.rst | 82 +++++++++++++++++++++++- lib/cretonne/meta/cdsl/isa.py | 37 ++++++++++- lib/cretonne/meta/cdsl/registers.py | 26 +++++++- lib/cretonne/meta/gen_encoding.py | 53 ++++++++++++++- lib/cretonne/meta/isa/riscv/recipes.py | 9 ++- lib/cretonne/src/isa/arm32/enc_tables.rs | 1 + lib/cretonne/src/isa/arm32/mod.rs | 6 +- lib/cretonne/src/isa/arm64/enc_tables.rs | 1 + lib/cretonne/src/isa/arm64/mod.rs | 6 +- lib/cretonne/src/isa/constraints.rs | 66 +++++++++++++++++++ lib/cretonne/src/isa/intel/enc_tables.rs | 1 + lib/cretonne/src/isa/intel/mod.rs | 6 +- lib/cretonne/src/isa/mod.rs | 11 +++- lib/cretonne/src/isa/riscv/enc_tables.rs | 2 + lib/cretonne/src/isa/riscv/mod.rs | 6 +- 15 files changed, 299 insertions(+), 14 deletions(-) create mode 100644 lib/cretonne/src/isa/constraints.rs diff --git a/cranelift/docs/metaref.rst b/cranelift/docs/metaref.rst index ec4555a8d7..05050f8f40 100644 --- a/cranelift/docs/metaref.rst +++ b/cranelift/docs/metaref.rst @@ -331,8 +331,6 @@ encoded: - The CPU mode that must be active. - A :term:`sub-target predicate` that must be satisfied by the currently active sub-target. -- :term:`Register constraint`\s that must be satisfied by the instruction's value - operands and results. An encoding specifies an *encoding recipe* along with some *encoding bits* that the recipe can use for native opcode fields etc. The encoding recipe has @@ -349,6 +347,83 @@ encodings only need the recipe predicates. .. autoclass:: EncRecipe +Register constraints +==================== + +After an encoding recipe has been chosen for an instruction, it is the register +allocator's job to make sure that the recipe's :term:`Register constraint`\s +are satisfied. Most ISAs have separate integer and floating point registers, +and instructions can usually only use registers from one of the banks. Some +instruction encodings are even more constrained and can only use a subset of +the registers in a bank. These constraints are expressed in terms of register +classes. + +Sometimes the result of an instruction is placed in a register that must be the +same as one of the input registers. Some instructions even use a fixed register +for inputs or results. + +Each encoding recipe specifies separate constraints for its value operands and +result. These constraints are separate from the instruction predicate which can +only evaluate the instruction's immediate operands. + +.. module:: cdsl.registers +.. autoclass:: RegBank + +Register class constraints +-------------------------- + +The most common type of register constraint is the register class. It specifies +that an operand or result must be allocated one of the registers from the given +register class:: + + IntRegs = RegBank('IntRegs', ISA, 'General purpose registers', units=16, prefix='r') + GPR = RegClass(IntRegs) + R = EncRecipe('R', Binary, ins=(GPR, GPR), outs=GPR) + +This defines an encoding recipe for the ``Binary`` instruction format where +both input operands must be allocated from the ``GPR`` register class. + +.. autoclass:: RegClass + +Tied register operands +---------------------- + +In more compact machine code encodings, it is common to require that the result +register is the same as one of the inputs. This is represented with tied +operands:: + + CR = EncRecipe('CR', Binary, ins=(GPR, GPR), outs=0) + +This indicates that the result value must be allocated to the same register as +the first input value. Tied operand constraints can only be used for result +values, so the number always refers to one of the input values. + +Fixed register operands +----------------------- + +Some instructions use hard-coded input and output registers for some value +operands. An example is the ``pblendvb`` Intel SSE instruction which takes one +of its three value operands in the hard-coded ``%xmm0`` register:: + + XMM0 = FPR[0] + SSE66_XMM0 = EncRecipe('SSE66_XMM0', Ternary, ins=(FPR, FPR, XMM0), outs=0) + +The syntax ``FPR[0]`` selects the first register from the ``FPR`` register +class which consists of all the XMM registers. + +Stack operands +-------------- + +Cretonne's register allocator can assign an SSA value to a stack slot if there +isn't enough registers. It will insert :cton:inst:`spill` and :cton:inst:`fill` +instructions as needed to satisfy instruction operand constraints, but it is +also possible to have instructions that can access stack slots directly:: + + CSS = EncRecipe('CSS', Unary, ins=GPR, outs=Stack(GPR)) + +An output stack value implies a store to the stack, an input value implies a +load. + .. module:: cdsl.isa Targets @@ -366,6 +441,9 @@ The definitions for each supported target live in a package under :members: .. automodule:: isa.riscv +.. automodule:: isa.intel +.. automodule:: isa.arm32 +.. automodule:: isa.arm64 Glossary diff --git a/lib/cretonne/meta/cdsl/isa.py b/lib/cretonne/meta/cdsl/isa.py index 6c324603a5..8e1423315c 100644 --- a/lib/cretonne/meta/cdsl/isa.py +++ b/lib/cretonne/meta/cdsl/isa.py @@ -1,6 +1,7 @@ """Defining instruction set architectures.""" from __future__ import absolute_import from .predicates import And +from .registers import RegClass, Register # The typing module is only required by mypy, and we don't use these imports # outside type comments. @@ -12,6 +13,8 @@ try: from .types import ValueType # noqa from .registers import RegBank # noqa AnyPredicate = Union[Predicate, FieldPredicate] + OperandConstraint = Union[RegClass, Register, int] + ConstraintSeq = Union[OperandConstraint, Tuple[OperandConstraint, ...]] except ImportError: pass @@ -133,13 +136,24 @@ class EncRecipe(object): Many different instructions can be encoded by the same recipe, but they must all have the same instruction format. + The `ins` and `outs` arguments are tuples specifying the register + allocation constraints for the value operands and results respectively. The + possible constraints for an operand are: + + - A `RegClass` specifying the set of allowed registers. + - A `Register` specifying a fixed-register operand. + - An integer indicating that this result is tied to a value operand, so + they must use the same register. + :param name: Short mnemonic name for this recipe. :param format: All encoded instructions must have this :py:class:`InstructionFormat`. + :param: ins Tuple of register constraints for value operands. + :param: outs Tuple of register constraints for results. """ - def __init__(self, name, format, instp=None, isap=None): - # type: (str, InstructionFormat, AnyPredicate, AnyPredicate) -> None + def __init__(self, name, format, ins, outs, instp=None, isap=None): + # type: (str, InstructionFormat, ConstraintSeq, ConstraintSeq, AnyPredicate, AnyPredicate) -> None # noqa self.name = name self.format = format self.instp = instp @@ -148,10 +162,29 @@ class EncRecipe(object): assert instp.predicate_context() == format self.number = None # type: int + self.ins = self._verify_constraints(ins) + assert len(self.ins) == len(format.value_operands) + self.outs = self._verify_constraints(outs) + if len(self.outs) > 1: + assert format.multiple_results + def __str__(self): # type: () -> str return self.name + def _verify_constraints(self, seq): + # (ConstraintSeq) -> Sequence[OperandConstraint] + if not isinstance(seq, tuple): + seq = (seq,) + for c in seq: + if isinstance(c, int): + # An integer constraint is bound to a value operand. + # Check that it is in range. + assert c >= 0 and c < len(self.format.value_operands) + else: + assert isinstance(c, RegClass) or isinstance(c, Register) + return seq + class Encoding(object): """ diff --git a/lib/cretonne/meta/cdsl/registers.py b/lib/cretonne/meta/cdsl/registers.py index dc6885c296..828e6a2704 100644 --- a/lib/cretonne/meta/cdsl/registers.py +++ b/lib/cretonne/meta/cdsl/registers.py @@ -62,7 +62,7 @@ class RegBank(object): `units`, the remaining units are named using `prefix`. """ - def __init__(self, name, isa, doc, units, prefix='p', names=()): + def __init__(self, name, isa, doc, units, prefix='r', names=()): # type: (str, TargetISA, str, int, str, Sequence[str]) -> None self.name = name self.isa = isa @@ -124,12 +124,16 @@ class RegClass(object): bank.classes.append(self) + def __str__(self): + return self.name + def __getitem__(self, sliced): """ Create a sub-class of a register class using slice notation. The slice indexes refer to allocations in the parent register class, not register units. """ + print(repr(sliced)) assert isinstance(sliced, slice), "RegClass slicing can't be 1 reg" # We could add strided sub-classes if needed. assert sliced.step is None, 'Subclass striding not supported' @@ -142,6 +146,7 @@ class RegClass(object): return RegClass(self.bank, count=c, width=w, start=s) def mask(self): + # type: () -> List[int] """ Compute a bit-mask of the register units allocated by this register class. @@ -173,3 +178,22 @@ class RegClass(object): if isinstance(obj, RegClass): assert obj.name is None obj.name = name + + +class Register(object): + """ + A specific register in a register class. + + A register is identified by the top-level register class it belongs to and + its first register unit. + + Specific registers are used to describe constraints on instructions where + some operands must use a fixed register. + + Register objects should be created using the indexing syntax on the + register class. + """ + def __init__(self, rc, unit): + # type: (RegClass, int) -> None + self.regclass = rc + self.unit = unit diff --git a/lib/cretonne/meta/gen_encoding.py b/lib/cretonne/meta/gen_encoding.py index 8477f31de2..30d1ed3370 100644 --- a/lib/cretonne/meta/gen_encoding.py +++ b/lib/cretonne/meta/gen_encoding.py @@ -1,7 +1,7 @@ """ Generate sources for instruction encoding. -The tables and functions generated here support the `TargetIsa::encode()` +The tables and functions generated here support the `TargetISA::encode()` function which determines if a given instruction is legal, and if so, it's `Encoding` data which consists of a *recipe* and some *encoding* bits. @@ -56,6 +56,13 @@ from unique_table import UniqueSeqTable from collections import OrderedDict, defaultdict import math import itertools +from cdsl.registers import RegClass, Register + +try: + from typing import Sequence # noqa + from cdsl.isa import TargetISA, OperandConstraint # noqa +except ImportError: + pass def emit_instp(instp, fmt): @@ -399,6 +406,7 @@ def offset_type(length): def emit_recipe_names(isa, fmt): + # type: (TargetISA, srcgen.Formatter) -> None """ Emit a table of encoding recipe names keyed by recipe number. @@ -411,6 +419,48 @@ def emit_recipe_names(isa, fmt): fmt.line('"{}",'.format(r.name)) +def emit_recipe_constraints(isa, fmt): + # type: (TargetISA, srcgen.Formatter) -> None + """ + Emit a table of encoding recipe operand constraints keyed by recipe number. + + These are used by the register allocator to pick registers that can be + properly encoded. + """ + with fmt.indented( + 'pub static RECIPE_CONSTRAINTS: [RecipeConstraints; {}] = [' + .format(len(isa.all_recipes)), '];'): + for r in isa.all_recipes: + fmt.comment(r.name) + with fmt.indented('RecipeConstraints {', '},'): + emit_operand_constraints(r.ins, 'ins', fmt) + emit_operand_constraints(r.outs, 'outs', fmt) + + +def emit_operand_constraints(seq, field, fmt): + # type: (Sequence[OperandConstraint], str, srcgen.Formatter) -> None + """ + Emit a struct field initializer for an array of operand constraints. + """ + if len(seq) == 0: + fmt.line('{}: &[],'.format(field)) + return + with fmt.indented('{}: &['.format(field), '],'): + for cons in seq: + with fmt.indented('OperandConstraint {', '},'): + if isinstance(cons, RegClass): + fmt.line('kind: ConstraintKind::Reg,') + fmt.line('regclass: {},'.format(cons)) + elif isinstance(cons, Register): + fmt.line( + 'kind: ConstraintKind::FixedReg({}),' + .format(cons.unit)) + fmt.line('regclass: {},'.format(cons.regclass)) + else: + raise AssertionError( + 'Unsupported constraint {}'.format(cons)) + + def gen_isa(isa, fmt): # First assign numbers to relevant instruction predicates and generate the # check_instp() function.. @@ -446,6 +496,7 @@ def gen_isa(isa, fmt): cpumode, level1_tables[cpumode], level1_offt, fmt) emit_recipe_names(isa, fmt) + emit_recipe_constraints(isa, fmt) def generate(isas, out_dir): diff --git a/lib/cretonne/meta/isa/riscv/recipes.py b/lib/cretonne/meta/isa/riscv/recipes.py index ae8d5b3add..fe0618d6cc 100644 --- a/lib/cretonne/meta/isa/riscv/recipes.py +++ b/lib/cretonne/meta/isa/riscv/recipes.py @@ -12,6 +12,7 @@ from __future__ import absolute_import from cdsl.isa import EncRecipe from cdsl.predicates import IsSignedInt from base.formats import Binary, BinaryImm +from .registers import GPR # The low 7 bits of a RISC-V instruction is the base opcode. All 32-bit # instructions have 11 as the two low bits, with bits 6:2 determining the base @@ -67,9 +68,11 @@ def OP32(funct3, funct7): # R-type 32-bit instructions: These are mostly binary arithmetic instructions. # The encbits are `opcode[6:2] | (funct3 << 5) | (funct7 << 8) -R = EncRecipe('R', Binary) +R = EncRecipe('R', Binary, ins=(GPR, GPR), outs=GPR) # R-type with an immediate shift amount instead of rs2. -Rshamt = EncRecipe('Rshamt', BinaryImm) +Rshamt = EncRecipe('Rshamt', BinaryImm, ins=GPR, outs=GPR) -I = EncRecipe('I', BinaryImm, instp=IsSignedInt(BinaryImm.imm, 12)) +I = EncRecipe( + 'I', BinaryImm, ins=GPR, outs=GPR, + instp=IsSignedInt(BinaryImm.imm, 12)) diff --git a/lib/cretonne/src/isa/arm32/enc_tables.rs b/lib/cretonne/src/isa/arm32/enc_tables.rs index e40362a32f..3c3ffae695 100644 --- a/lib/cretonne/src/isa/arm32/enc_tables.rs +++ b/lib/cretonne/src/isa/arm32/enc_tables.rs @@ -4,5 +4,6 @@ use ir::InstructionData; use ir::instructions::InstructionFormat; use ir::types; use isa::enc_tables::{Level1Entry, Level2Entry}; +use isa::constraints::*; include!(concat!(env!("OUT_DIR"), "/encoding-arm32.rs")); diff --git a/lib/cretonne/src/isa/arm32/mod.rs b/lib/cretonne/src/isa/arm32/mod.rs index 617f52f43e..63f6293516 100644 --- a/lib/cretonne/src/isa/arm32/mod.rs +++ b/lib/cretonne/src/isa/arm32/mod.rs @@ -7,7 +7,7 @@ mod registers; use super::super::settings as shared_settings; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, general_encoding}; use isa::Builder as IsaBuilder; -use isa::{TargetIsa, RegInfo, Encoding, Legalize}; +use isa::{TargetIsa, RegInfo, Encoding, Legalize, RecipeConstraints}; use ir::{InstructionData, DataFlowGraph}; #[allow(dead_code)] @@ -70,4 +70,8 @@ impl TargetIsa for Isa { fn recipe_names(&self) -> &'static [&'static str] { &enc_tables::RECIPE_NAMES[..] } + + fn recipe_constraints(&self) -> &'static [RecipeConstraints] { + &enc_tables::RECIPE_CONSTRAINTS + } } diff --git a/lib/cretonne/src/isa/arm64/enc_tables.rs b/lib/cretonne/src/isa/arm64/enc_tables.rs index dbe65c4e8f..92b5ad58c3 100644 --- a/lib/cretonne/src/isa/arm64/enc_tables.rs +++ b/lib/cretonne/src/isa/arm64/enc_tables.rs @@ -4,5 +4,6 @@ use ir::InstructionData; use ir::instructions::InstructionFormat; use ir::types; use isa::enc_tables::{Level1Entry, Level2Entry}; +use isa::constraints::*; include!(concat!(env!("OUT_DIR"), "/encoding-arm64.rs")); diff --git a/lib/cretonne/src/isa/arm64/mod.rs b/lib/cretonne/src/isa/arm64/mod.rs index a4367a878b..2c2b98437b 100644 --- a/lib/cretonne/src/isa/arm64/mod.rs +++ b/lib/cretonne/src/isa/arm64/mod.rs @@ -7,7 +7,7 @@ mod registers; use super::super::settings as shared_settings; use isa::enc_tables::{lookup_enclist, general_encoding}; use isa::Builder as IsaBuilder; -use isa::{TargetIsa, RegInfo, Encoding, Legalize}; +use isa::{TargetIsa, RegInfo, Encoding, Legalize, RecipeConstraints}; use ir::{InstructionData, DataFlowGraph}; #[allow(dead_code)] @@ -63,4 +63,8 @@ impl TargetIsa for Isa { fn recipe_names(&self) -> &'static [&'static str] { &enc_tables::RECIPE_NAMES[..] } + + fn recipe_constraints(&self) -> &'static [RecipeConstraints] { + &enc_tables::RECIPE_CONSTRAINTS + } } diff --git a/lib/cretonne/src/isa/constraints.rs b/lib/cretonne/src/isa/constraints.rs new file mode 100644 index 0000000000..bdd2bea958 --- /dev/null +++ b/lib/cretonne/src/isa/constraints.rs @@ -0,0 +1,66 @@ +//! Register constraints for instruction operands. +//! +//! An encoding recipe specifies how an instruction is encoded as binary machine code, but it only +//! works if the operands and results satisfy certain constraints. Constraints on immediate +//! operands are checked by instruction predicates when the recipe is chosen. +//! +//! It is the register allocator's job to make sure that the register constraints on value operands +//! are satisfied. + +use isa::{RegClass, RegUnit}; + +/// Register constraint for a single value operand or instruction result. +pub struct OperandConstraint { + /// The kind of constraint. + pub kind: ConstraintKind, + + /// The register class of the operand. + /// + /// This applies to all kinds of constraints, but with slightly different meaning. + pub regclass: RegClass, +} + +/// The different kinds of operand constraints. +pub enum ConstraintKind { + /// This operand or result must be a register from the given register class. + Reg, + + /// This operand or result must be a fixed register. + /// + /// The constraint's `regclass` field is the top-level register class containing the fixed + /// register. + FixedReg(RegUnit), + + /// This result value must use the same register as an input value operand. Input operands + /// can't be tied. + /// + /// The associated number is the index of the input value operand this result is tied to. + /// + /// The constraint's `regclass` field is the top-level register class containing the tied + /// operand's register class. + Tied(u8), + + /// This operand must be a value in a stack slot. + /// + /// The constraint's `regclass` field is the register class that would normally be used to load + /// and store values of this type. + Stack, +} + +/// Constraints for an encoding recipe. +pub struct RecipeConstraints { + /// Constraints for the instruction's fixed value operands. + /// + /// If the instruction takes a variable number of operands, the register constraints for those + /// operands must be computed dynamically. + /// + /// - For branches and jumps, EBB arguments must match the expectations of the destination EBB. + /// - For calls and returns, the calling convention ABI specifies constraints. + pub ins: &'static [OperandConstraint], + + /// Constraints for the instruction's fixed results. + /// + /// If the instruction produces a variable number of results, it's probably a call and the + /// constraints must be derived from the calling convention ABI. + pub outs: &'static [OperandConstraint], +} diff --git a/lib/cretonne/src/isa/intel/enc_tables.rs b/lib/cretonne/src/isa/intel/enc_tables.rs index 842629b058..0b01f481f2 100644 --- a/lib/cretonne/src/isa/intel/enc_tables.rs +++ b/lib/cretonne/src/isa/intel/enc_tables.rs @@ -4,5 +4,6 @@ use ir::InstructionData; use ir::instructions::InstructionFormat; use ir::types; use isa::enc_tables::{Level1Entry, Level2Entry}; +use isa::constraints::*; include!(concat!(env!("OUT_DIR"), "/encoding-intel.rs")); diff --git a/lib/cretonne/src/isa/intel/mod.rs b/lib/cretonne/src/isa/intel/mod.rs index acad76743b..13b77e1dd2 100644 --- a/lib/cretonne/src/isa/intel/mod.rs +++ b/lib/cretonne/src/isa/intel/mod.rs @@ -7,7 +7,7 @@ mod registers; use super::super::settings as shared_settings; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, general_encoding}; use isa::Builder as IsaBuilder; -use isa::{TargetIsa, RegInfo, Encoding, Legalize}; +use isa::{TargetIsa, RegInfo, Encoding, Legalize, RecipeConstraints}; use ir::{InstructionData, DataFlowGraph}; #[allow(dead_code)] @@ -70,4 +70,8 @@ impl TargetIsa for Isa { fn recipe_names(&self) -> &'static [&'static str] { &enc_tables::RECIPE_NAMES[..] } + + fn recipe_constraints(&self) -> &'static [RecipeConstraints] { + &enc_tables::RECIPE_CONSTRAINTS + } } diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index 6d22d5dd71..0bd9e5e9fa 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -42,6 +42,8 @@ pub use isa::encoding::Encoding; pub use isa::registers::{RegInfo, RegUnit, RegClass}; +pub use isa::constraints::RecipeConstraints; + use settings; use ir::{InstructionData, DataFlowGraph}; @@ -52,6 +54,7 @@ pub mod arm64; pub mod registers; mod encoding; mod enc_tables; +mod constraints; /// Look for a supported ISA with the given `name`. /// Return a builder that can create a corresponding `TargetIsa`. @@ -140,11 +143,17 @@ pub trait TargetIsa { fn encode(&self, dfg: &DataFlowGraph, inst: &InstructionData) -> Result; /// Get a static array of names associated with encoding recipes in this ISA. Encoding recipes - /// are numbered starting from 0, corresponding to indexes into th name array. + /// are numbered starting from 0, corresponding to indexes into the name array. /// /// This is just used for printing and parsing encodings in the textual IL format. fn recipe_names(&self) -> &'static [&'static str]; + /// Get a static array of value operand constraints associated with encoding recipes in this + /// ISA. + /// + /// The constraints describe which registers can be used with an encoding recipe. + fn recipe_constraints(&self) -> &'static [RecipeConstraints]; + /// Create an object that can display an ISA-dependent encoding properly. fn display_enc(&self, enc: Encoding) -> encoding::DisplayEncoding { encoding::DisplayEncoding { diff --git a/lib/cretonne/src/isa/riscv/enc_tables.rs b/lib/cretonne/src/isa/riscv/enc_tables.rs index f32a313c92..2538911a25 100644 --- a/lib/cretonne/src/isa/riscv/enc_tables.rs +++ b/lib/cretonne/src/isa/riscv/enc_tables.rs @@ -5,6 +5,8 @@ use ir::instructions::InstructionFormat; use ir::types; use predicates; use isa::enc_tables::{Level1Entry, Level2Entry}; +use isa::constraints::*; +use super::registers::*; // Include the generated encoding tables: // - `LEVEL1_RV32` diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index 762df2249c..34b2a43c99 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -7,7 +7,7 @@ mod registers; use super::super::settings as shared_settings; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, general_encoding}; use isa::Builder as IsaBuilder; -use isa::{TargetIsa, RegInfo, Encoding, Legalize}; +use isa::{TargetIsa, RegInfo, Encoding, Legalize, RecipeConstraints}; use ir::{InstructionData, DataFlowGraph}; #[allow(dead_code)] @@ -70,6 +70,10 @@ impl TargetIsa for Isa { fn recipe_names(&self) -> &'static [&'static str] { &enc_tables::RECIPE_NAMES[..] } + + fn recipe_constraints(&self) -> &'static [RecipeConstraints] { + &enc_tables::RECIPE_CONSTRAINTS + } } #[cfg(test)]