diff --git a/lib/cretonne/meta/base/legalize.py b/lib/cretonne/meta/base/legalize.py index 91e1e1114a..1afefcda89 100644 --- a/lib/cretonne/meta/base/legalize.py +++ b/lib/cretonne/meta/base/legalize.py @@ -27,6 +27,13 @@ narrow = XFormGroup('narrow', """ operations are expressed in terms of smaller integer types. """) +widen = XFormGroup('widen', """ + Legalize instructions by widening. + + The transformations in the 'widen' group work by expressing + instructions in terms of larger types. + """) + expand = XFormGroup('expand', """ Legalize instructions by expansion. diff --git a/lib/cretonne/meta/cdsl/isa.py b/lib/cretonne/meta/cdsl/isa.py index 0562df10dc..ea10c64640 100644 --- a/lib/cretonne/meta/cdsl/isa.py +++ b/lib/cretonne/meta/cdsl/isa.py @@ -1,8 +1,10 @@ """Defining instruction set architectures.""" from __future__ import absolute_import +from collections import OrderedDict from .predicates import And from .registers import RegClass, Register, Stack from .ast import Apply +from .types import ValueType # The typing module is only required by mypy, and we don't use these imports # outside type comments. @@ -12,8 +14,8 @@ try: from .instructions import MaybeBoundInst, InstructionGroup, InstructionFormat # noqa from .predicates import PredNode # noqa from .settings import SettingGroup # noqa - from .types import ValueType # noqa from .registers import RegBank # noqa + from .xform import XFormGroup # noqa OperandConstraint = Union[RegClass, Register, int, Stack] ConstraintSeq = Union[OperandConstraint, Tuple[OperandConstraint, ...]] # Instruction specification for encodings. Allows for predicated @@ -43,6 +45,11 @@ class TargetISA(object): self.cpumodes = list() # type: List[CPUMode] self.regbanks = list() # type: List[RegBank] self.regclasses = list() # type: List[RegClass] + self.legalize_codes = OrderedDict() # type: OrderedDict[XFormGroup, int] # noqa + + def __str__(self): + # type: () -> str + return self.name def finish(self): # type: () -> TargetISA @@ -138,6 +145,25 @@ class TargetISA(object): # `isa/registers.rs`. assert len(self.regclasses) <= 32, "Too many register classes" + def legalize_code(self, xgrp): + # type: (XFormGroup) -> int + """ + Get the legalization code for the transform group `xgrp`. Assign one if + necessary. + + Each target ISA has its own list of legalization actions with + associated legalize codes that appear in the encoding tables. + + This method is used to maintain the registry of legalization actions + and their table codes. + """ + if xgrp in self.legalize_codes: + code = self.legalize_codes[xgrp] + else: + code = len(self.legalize_codes) + self.legalize_codes[xgrp] = code + return code + class CPUMode(object): """ @@ -157,6 +183,11 @@ class CPUMode(object): self.encodings = [] # type: List[Encoding] isa.cpumodes.append(self) + # Tables for configuring legalization actions when no valid encoding + # exists for an instruction. + self.default_legalize = None # type: XFormGroup + self.type_legalize = dict() # type: Dict[ValueType, XFormGroup] + def __str__(self): # type: () -> str return self.name @@ -171,6 +202,36 @@ class CPUMode(object): """ self.encodings.append(Encoding(self, *args, **kwargs)) + def legalize_type(self, default=None, **kwargs): + # type: (XFormGroup, **XFormGroup) -> None + """ + Configure the legalization action per controlling type variable. + + Instructions that have a controlling type variable mentioned in one of + the arguments will be legalized according to the action specified here + instead of using the `legalize_default` action. + + The keyword arguments are value type names: + + mode.legalize_type(i8=widen, i16=widen, i32=expand) + + The `default` argument specifies the action to take for controlling + type variables that don't have an explicitly configured action. + """ + if default is not None: + self.default_legalize = default + + for name, xgrp in kwargs.items(): + ty = ValueType.by_name(name) + self.type_legalize[ty] = xgrp + + def get_legalize_action(self, ty): + # type: (ValueType) -> XFormGroup + """ + Get the legalization action to use for `ty`. + """ + return self.type_legalize.get(ty, self.default_legalize) + class EncRecipe(object): """ diff --git a/lib/cretonne/meta/cdsl/xform.py b/lib/cretonne/meta/cdsl/xform.py index f809a91ce6..6729267f7b 100644 --- a/lib/cretonne/meta/cdsl/xform.py +++ b/lib/cretonne/meta/cdsl/xform.py @@ -241,6 +241,10 @@ class XFormGroup(object): self.name = name self.__doc__ = doc + def __str__(self): + # type: () -> str + return self.name + def legalize(self, src, dst): # type: (Union[Def, Apply], Rtl) -> None """ diff --git a/lib/cretonne/meta/gen_encoding.py b/lib/cretonne/meta/gen_encoding.py index 135940dd23..de8b138cf6 100644 --- a/lib/cretonne/meta/gen_encoding.py +++ b/lib/cretonne/meta/gen_encoding.py @@ -66,6 +66,7 @@ try: from cdsl.predicates import PredNode, PredLeaf # noqa from cdsl.types import ValueType # noqa from cdsl.instructions import Instruction # noqa + from cdsl.xform import XFormGroup # noqa except ImportError: pass @@ -261,12 +262,17 @@ class Level2Table(object): """ Level 2 table mapping instruction opcodes to `EncList` objects. + A level 2 table can be completely empty if it only holds a custom + legalization action for `ty`. + :param ty: Controlling type variable of all entries, or `None`. + :param legalize: Default legalize action for `ty`. """ - def __init__(self, ty): - # type: (ValueType) -> None + def __init__(self, ty, legalize): + # type: (ValueType, XFormGroup) -> None self.ty = ty + self.legalize = legalize # Maps inst -> EncList self.lists = OrderedDict() # type: OrderedDict[Instruction, EncList] @@ -278,6 +284,16 @@ class Level2Table(object): self.lists[inst] = ls return ls + def is_empty(self): + # type: () -> bool + """ + Check if this level 2 table is completely empty. + + This can happen if the associated type simply has an overridden + legalize action. + """ + return len(self.lists) == 0 + def enclists(self): # type: () -> Iterable[EncList] return iter(self.lists.values()) @@ -310,21 +326,32 @@ class Level1Table(object): Level 1 table mapping types to `Level2` objects. """ - def __init__(self): - # type: () -> None + def __init__(self, cpumode): + # type: (CPUMode) -> None + self.cpumode = cpumode self.tables = OrderedDict() # type: OrderedDict[ValueType, Level2Table] # noqa + if cpumode.default_legalize is None: + raise AssertionError( + 'CPU mode {}.{} needs a default legalize action' + .format(cpumode.isa, cpumode)) + self.legalize_code = cpumode.isa.legalize_code( + cpumode.default_legalize) + def __getitem__(self, ty): # type: (ValueType) -> Level2Table tbl = self.tables.get(ty) if not tbl: - tbl = Level2Table(ty) + legalize = self.cpumode.get_legalize_action(ty) + # Allocate a legalization code in a predictable order. + self.cpumode.isa.legalize_code(legalize) + tbl = Level2Table(ty, legalize) self.tables[ty] = tbl return tbl def l2tables(self): # type: () -> Iterable[Level2Table] - return iter(self.tables.values()) + return (l2 for l2 in self.tables.values() if not l2.is_empty()) def make_tables(cpumode): @@ -332,11 +359,17 @@ def make_tables(cpumode): """ Generate tables for `cpumode` as described above. """ - table = Level1Table() + table = Level1Table(cpumode) for enc in cpumode.encodings: ty = enc.ctrl_typevar() inst = enc.inst table[ty][inst].encodings.append(enc) + + # Ensure there are level 1 table entries for all types with a custom + # legalize action. Try to be stable relative to dict ordering. + for ty in sorted(cpumode.type_legalize.keys(), key=str): + table[ty] + return table @@ -412,22 +445,42 @@ def emit_level1_hashtable(cpumode, level1, offt, fmt): 'pub static LEVEL1_{}: [Level1Entry<{}>; {}] = [' .format(cpumode.name.upper(), offt, len(hash_table)), '];'): for level2 in hash_table: - if level2: - l2l = int(math.log(level2.hash_table_len, 2)) - assert l2l > 0, "Hash table too small" - tyname = level2.ty.name if level2.ty is not None else 'void' - fmt.line( - 'Level1Entry ' + - '{{ ty: types::{}, log2len: {}, offset: {:#08x} }},' - .format( - tyname.upper(), - l2l, - level2.hash_table_offset)) + # Empty hash table entry. Include the default legalization action. + if not level2: + fmt.format( + 'Level1Entry {{ ty: types::VOID, log2len: !0, ' + 'offset: 0, legalize: {} }},', + level1.legalize_code) + continue + + if level2.ty is not None: + tyname = level2.ty.rust_name() else: - # Empty entry. - fmt.line( - 'Level1Entry ' + - '{ ty: types::VOID, log2len: 0, offset: 0 },') + tyname = 'types::VOID' + + lcode = cpumode.isa.legalize_code(level2.legalize) + + # Empty level 2 table: Only a specialized legalization action, no + # actual table. + # Set an offset that is out of bounds, but make sure it doesn't + # overflow its type when adding `1< 0, "Level2 hash table too small" + fmt.format( + 'Level1Entry {{ ' + 'ty: {}, log2len: {}, offset: {:#08x}, ' + 'legalize: {} }}, // {}', + tyname, l2l, level2.hash_table_offset, + lcode, level2.legalize) def offset_type(length): diff --git a/lib/cretonne/meta/isa/arm32/defs.py b/lib/cretonne/meta/isa/arm32/defs.py index f90abc2001..6bba598d27 100644 --- a/lib/cretonne/meta/isa/arm32/defs.py +++ b/lib/cretonne/meta/isa/arm32/defs.py @@ -6,9 +6,14 @@ Commonly used definitions. from __future__ import absolute_import from cdsl.isa import TargetISA, CPUMode import base.instructions +from base.legalize import narrow ISA = TargetISA('arm32', [base.instructions.GROUP]) # CPU modes for 32-bit ARM and Thumb2. A32 = CPUMode('A32', ISA) T32 = CPUMode('T32', ISA) + +# TODO: Refine these. +A32.legalize_type(narrow) +T32.legalize_type(narrow) diff --git a/lib/cretonne/meta/isa/arm64/defs.py b/lib/cretonne/meta/isa/arm64/defs.py index e493b4424a..b1ed79b5d6 100644 --- a/lib/cretonne/meta/isa/arm64/defs.py +++ b/lib/cretonne/meta/isa/arm64/defs.py @@ -6,6 +6,10 @@ Commonly used definitions. from __future__ import absolute_import from cdsl.isa import TargetISA, CPUMode import base.instructions +from base.legalize import narrow ISA = TargetISA('arm64', [base.instructions.GROUP]) A64 = CPUMode('A64', ISA) + +# TODO: Refine these +A64.legalize_type(narrow) diff --git a/lib/cretonne/meta/isa/intel/encodings.py b/lib/cretonne/meta/isa/intel/encodings.py index f155795b6f..df1a542101 100644 --- a/lib/cretonne/meta/isa/intel/encodings.py +++ b/lib/cretonne/meta/isa/intel/encodings.py @@ -9,6 +9,20 @@ from .defs import I32, I64 from . import recipes as r from . import settings as cfg from . import instructions as x86 +from base.legalize import narrow, expand + +I32.legalize_type( + default=narrow, + i32=expand, + f32=expand, + f64=expand) + +I64.legalize_type( + default=narrow, + i32=expand, + i64=expand, + f32=expand, + f64=expand) for inst, opc in [ (base.iadd, 0x01), diff --git a/lib/cretonne/meta/isa/riscv/encodings.py b/lib/cretonne/meta/isa/riscv/encodings.py index 9ec6b34fc0..b9f1f8245d 100644 --- a/lib/cretonne/meta/isa/riscv/encodings.py +++ b/lib/cretonne/meta/isa/riscv/encodings.py @@ -11,6 +11,20 @@ from .recipes import R, Rshamt, Ricmp, I, Iz, Iicmp, Iret, Icall, Icopy from .recipes import U, UJ, UJcall, SB, SBzero, GPsp, GPfi, Irmov from .settings import use_m from cdsl.ast import Var +from base.legalize import narrow, expand + +RV32.legalize_type( + default=narrow, + i32=expand, + f32=expand, + f64=expand) + +RV64.legalize_type( + default=narrow, + i32=expand, + i64=expand, + f32=expand, + f64=expand) # Dummies for instruction predicates. x = Var('x') diff --git a/lib/cretonne/src/isa/enc_tables.rs b/lib/cretonne/src/isa/enc_tables.rs index 1c6645002e..33ec38d3c1 100644 --- a/lib/cretonne/src/isa/enc_tables.rs +++ b/lib/cretonne/src/isa/enc_tables.rs @@ -9,6 +9,11 @@ use isa::{Encoding, Legalize}; use settings::PredicateView; use std::ops::Range; +/// Legalization action to perform when no encoding can be found for an instruction. +/// +/// This is an index into an ISA-specific table of legalization actions. +pub type LegalizeCode = u8; + /// Level 1 hash table entry. /// /// One level 1 hash table is generated per CPU mode. This table is keyed by the controlling type @@ -19,14 +24,15 @@ use std::ops::Range; /// have a power-of-two size. /// /// Entries are generic over the offset type. It will typically be `u32` or `u16`, depending on the -/// size of the `LEVEL2` table. A `u16` offset allows entries to shrink to 32 bits each, but some -/// ISAs may have tables so large that `u32` offsets are needed. +/// size of the `LEVEL2` table. /// -/// Empty entries are encoded with a 0 `log2len`. This is on the assumption that no level 2 tables -/// have only a single entry. +/// Empty entries are encoded with a `!0` value for `log2len` which will always be out of range. +/// Entries that have a `legalize` value but no level 2 table have an `offset` field that is out f +/// bounds. pub struct Level1Entry + Copy> { pub ty: Type, pub log2len: u8, + pub legalize: LegalizeCode, pub offset: OffT, } @@ -44,7 +50,7 @@ impl + Copy> Table for [Level1Entry] { } fn key(&self, idx: usize) -> Option { - if self[idx].log2len != 0 { + if self[idx].log2len != !0 { Some(self[idx].ty) } else { None @@ -55,7 +61,7 @@ impl + Copy> Table for [Level1Entry] { /// Level 2 hash table entry. /// /// The second level hash tables are keyed by `Opcode`, and contain an offset into the `ENCLISTS` -/// table where the encoding recipes for the instrution are stored. +/// table where the encoding recipes for the instruction are stored. /// /// Entries are generic over the offset type which depends on the size of `ENCLISTS`. A `u16` /// offset allows the entries to be only 32 bits each. There is no benefit to dropping down to `u8` @@ -93,22 +99,28 @@ pub fn lookup_enclist(ctrl_typevar: Type, where OffT1: Into + Copy, OffT2: Into + Copy { - // TODO: The choice of legalization actions here is naive. This needs to be configurable. match probe(level1_table, ctrl_typevar, ctrl_typevar.index()) { - Err(_) => { - // No level 1 entry for the type. - Err(if ctrl_typevar.lane_type().bits() > 32 { - Legalize::Narrow - } else { - Legalize::Expand - }) + Err(l1idx) => { + // No level 1 entry found for the type. + // We have a sentinel entry with the default legalization code. + let l1ent = &level1_table[l1idx]; + Err(l1ent.legalize.into()) } Ok(l1idx) => { + // We have a valid level 1 entry for this type. let l1ent = &level1_table[l1idx]; - let l2tab = &level2_table[l1ent.range()]; - probe(l2tab, opcode, opcode as usize) - .map(|l2idx| l2tab[l2idx].offset.into() as usize) - .map_err(|_| Legalize::Expand) + match level2_table.get(l1ent.range()) { + Some(l2tab) => { + probe(l2tab, opcode, opcode as usize) + .map(|l2idx| l2tab[l2idx].offset.into() as usize) + .map_err(|_| l1ent.legalize.into()) + } + None => { + // The l1ent range is invalid. This means that we just have a customized + // legalization code for this type. The level 2 table is empty. + Err(l1ent.legalize.into()) + } + } } } } diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index 100c901ec0..7b25aec8ec 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -126,6 +126,20 @@ pub enum Legalize { Expand, } +/// Translate a legalization code into a `Legalize` enum. +/// +/// This mapping is going away soon. It depends on matching the `TargetISA.legalize_code()` +/// mapping. +impl From for Legalize { + fn from(x: u8) -> Legalize { + match x { + 0 => Legalize::Narrow, + 1 => Legalize::Expand, + _ => panic!("Unknown legalization code {}"), + } + } +} + /// Methods that are specialized to a target ISA. pub trait TargetIsa { /// Get the name of this ISA.