Generate a RECIPE_PREDICATES table for each ISA.

It turns out that most encoding predicates are expressed as recipe
predicates. This means that the encoding tables can be more compact
since we can check the recipe predicate separately from individual
instruction predicates, and the recipe number is already present in the
table.

- Don't combine recipe and encoding-specific predicates when creating an
  Encoding. Keep them separate.
- Generate a table of recipe predicates with function pointers. Many of
  these are null.
- Check any recipe predicate before accepting a recipe+bits pair.

This has the effect of making almost all instruction predicates
CODE_ALWAYS.
This commit is contained in:
Jakob Stoklund Olesen
2017-07-21 15:33:35 -07:00
parent 351d4af4eb
commit a31dd3aa7a
11 changed files with 126 additions and 31 deletions

View File

@@ -23,6 +23,9 @@ try:
# instructions. # instructions.
InstSpec = Union[MaybeBoundInst, Apply] InstSpec = Union[MaybeBoundInst, Apply]
BranchRange = Sequence[int] BranchRange = Sequence[int]
# A recipe predicate consisting of an ISA predicate and an instruction
# predicate.
RecipePred = Tuple[PredNode, PredNode]
except ImportError: except ImportError:
pass pass
@@ -62,7 +65,7 @@ class TargetISA(object):
Finish the definition of a target ISA after adding all CPU modes and Finish the definition of a target ISA after adding all CPU modes and
settings. settings.
This computes some derived properties that are used in multilple This computes some derived properties that are used in multiple
places. places.
:returns self: :returns self:
@@ -86,6 +89,9 @@ class TargetISA(object):
recipe.number = len(rcps) recipe.number = len(rcps)
rcps.add(recipe) rcps.add(recipe)
self.all_recipes.append(recipe) self.all_recipes.append(recipe)
# Make sure ISA predicates are registered.
if recipe.isap:
self.settings.number_predicate(recipe.isap)
def _collect_predicates(self): def _collect_predicates(self):
# type: () -> None # type: () -> None
@@ -336,6 +342,19 @@ class EncRecipe(object):
o2i[o] = i o2i[o] = i
return (i2o, o2i) return (i2o, o2i)
def recipe_pred(self):
# type: () -> RecipePred
"""
Get the combined recipe predicate which includes both the ISA predicate
and the instruction predicate.
Return `None` if this recipe has neither predicate.
"""
if self.isap is None and self.instp is None:
return None
else:
return (self.isap, self.instp)
class Encoding(object): class Encoding(object):
""" """
@@ -386,9 +405,9 @@ class Encoding(object):
self.recipe = recipe self.recipe = recipe
self.encbits = encbits self.encbits = encbits
# Combine recipe predicates with the manually specified ones. # Record specific predicates. Note that the recipe also has predicates.
self.instp = And.combine(recipe.instp, instp) self.instp = instp
self.isap = And.combine(recipe.isap, isap) self.isap = isap
def __str__(self): def __str__(self):
# type: () -> str # type: () -> str

View File

@@ -11,11 +11,12 @@ are allocated.
This is the information available to us: This is the information available to us:
- The instruction to be encoded as an `Inst` reference. - The instruction to be encoded as an `InstructionData` reference.
- The data-flow graph containing the instruction, giving us access to the - The controlling type variable.
`InstructionData` representation and the types of all values involved. - The data-flow graph giving us access to the types of all values involved.
- A target ISA instance with shared and ISA-specific settings for evaluating This is needed for testing any secondary type variables.
ISA predicates. - A `PredicateView` reference for the ISA-specific settings for evaluating ISA
predicates.
- The currently active CPU mode is determined by the ISA. - The currently active CPU mode is determined by the ISA.
## Level 1 table lookup ## Level 1 table lookup
@@ -62,7 +63,7 @@ from cdsl.predicates import FieldPredicate
try: try:
from typing import Sequence, Set, Tuple, List, Dict, Iterable, DefaultDict, TYPE_CHECKING # noqa from typing import Sequence, Set, Tuple, List, Dict, Iterable, DefaultDict, TYPE_CHECKING # noqa
if TYPE_CHECKING: if TYPE_CHECKING:
from cdsl.isa import TargetISA, OperandConstraint, Encoding, CPUMode, EncRecipe # noqa from cdsl.isa import TargetISA, OperandConstraint, Encoding, CPUMode, EncRecipe, RecipePred # noqa
from cdsl.predicates import PredNode, PredLeaf # noqa from cdsl.predicates import PredNode, PredLeaf # noqa
from cdsl.types import ValueType # noqa from cdsl.types import ValueType # noqa
from cdsl.instructions import Instruction # noqa from cdsl.instructions import Instruction # noqa
@@ -77,8 +78,8 @@ def emit_instp(instp, fmt):
Emit code for matching an instruction predicate against an Emit code for matching an instruction predicate against an
`InstructionData` reference called `inst`. `InstructionData` reference called `inst`.
The generated code is a pattern match that falls through if the instruction The generated code is an `if let` pattern match that falls through if the
has an unexpected format. This should lead to a panic. instruction has an unexpected format. This should lead to a panic.
""" """
iform = instp.predicate_context() iform = instp.predicate_context()
@@ -94,11 +95,10 @@ def emit_instp(instp, fmt):
fnames.add(p.field.rust_name()) fnames.add(p.field.rust_name())
fields = ', '.join(sorted(fnames)) fields = ', '.join(sorted(fnames))
with fmt.indented('{} => {{'.format(instp.number), '}'): with fmt.indented(
with fmt.indented( 'if let InstructionData::{} {{ {}, .. }} = *inst {{'
'if let InstructionData::{} {{ {}, .. }} = *inst {{' .format(iform.name, fields), '}'):
.format(iform.name, fields), '}'): fmt.line('return {};'.format(instp.rust_predicate(0)))
fmt.line('return {};'.format(instp.rust_predicate(0)))
def emit_instps(instps, fmt): def emit_instps(instps, fmt):
@@ -122,7 +122,8 @@ def emit_instps(instps, fmt):
fmt.line('use ir::instructions::InstructionFormat;') fmt.line('use ir::instructions::InstructionFormat;')
with fmt.indented('match instp_idx {', '}'): with fmt.indented('match instp_idx {', '}'):
for instp in instps: for instp in instps:
emit_instp(instp, fmt) with fmt.indented('{} => {{'.format(instp.number), '}'):
emit_instp(instp, fmt)
fmt.line('_ => panic!("Invalid instruction predicate")') fmt.line('_ => panic!("Invalid instruction predicate")')
# The match cases will fall through if the instruction format is wrong. # The match cases will fall through if the instruction format is wrong.
@@ -132,6 +133,55 @@ def emit_instps(instps, fmt):
fmt.line(' instp_idx);') fmt.line(' instp_idx);')
def emit_recipe_predicates(recipes, fmt):
# type: (Sequence[EncRecipe], srcgen.Formatter) -> None
"""
Emit private functions for checking recipe predicates as well as a static
`RECIPE_PREDICATES` array indexed by recipe number.
A recipe predicate is a combination of an ISA predicate and an instruction
predicates. Many recipes have identical predicates.
"""
# Table for uniquing recipe predicates. Maps predicate to generated
# function name.
pname = dict() # type: Dict[RecipePred, str]
# Generate unique recipe predicates.
for rcp in recipes:
p = rcp.recipe_pred()
if p is None or p in pname:
continue
name = 'recipe_predicate_{}'.format(rcp.name.lower())
pname[p] = name
isap, instp = p
# Generate the predicate function.
with fmt.indented(
'fn {}({}: ::settings::PredicateView, '
'inst: &InstructionData) -> bool {{'
.format(
name,
'isap' if isap else '_'), '}'):
if isap:
with fmt.indented(
'if isap.test({})'.format(isap.number),
'}'):
fmt.line('return false;')
emit_instp(instp, fmt)
fmt.line('unreachable!();')
# Generate the static table.
with fmt.indented(
'pub static RECIPE_PREDICATES: [RecipePredicate; {}] = ['
.format(len(recipes)), '];'):
for rcp in recipes:
p = rcp.recipe_pred()
if p is None:
fmt.line('None,')
else:
fmt.format('Some({}),', pname[p])
# Encoding lists are represented as u16 arrays. # Encoding lists are represented as u16 arrays.
CODE_BITS = 16 CODE_BITS = 16
PRED_BITS = 12 PRED_BITS = 12
@@ -604,8 +654,11 @@ def emit_recipe_sizing(isa, fmt):
def gen_isa(isa, fmt): def gen_isa(isa, fmt):
# type: (TargetISA, srcgen.Formatter) -> None # type: (TargetISA, srcgen.Formatter) -> None
# First assign numbers to relevant instruction predicates and generate the
# check_instp() function.. # Make the `RECIPE_PREDICATES` table.
emit_recipe_predicates(isa.all_recipes, fmt)
# Generate the check_instp() function..
emit_instps(isa.all_instps, fmt) emit_instps(isa.all_instps, fmt)
# Level1 tables, one per CPU mode # Level1 tables, one per CPU mode

View File

@@ -4,7 +4,7 @@ use ir::InstructionData;
use ir::types; use ir::types;
use isa::EncInfo; use isa::EncInfo;
use isa::constraints::*; use isa::constraints::*;
use isa::enc_tables::{Level1Entry, Level2Entry}; use isa::enc_tables::*;
use isa::encoding::RecipeSizing; use isa::encoding::RecipeSizing;
include!(concat!(env!("OUT_DIR"), "/encoding-arm32.rs")); include!(concat!(env!("OUT_DIR"), "/encoding-arm32.rs"));

View File

@@ -73,6 +73,7 @@ impl TargetIsa for Isa {
.and_then(|enclist_offset| { .and_then(|enclist_offset| {
Ok(Encodings::new(enclist_offset, Ok(Encodings::new(enclist_offset,
&enc_tables::ENCLISTS[..], &enc_tables::ENCLISTS[..],
&enc_tables::RECIPE_PREDICATES[..],
inst, inst,
enc_tables::check_instp, enc_tables::check_instp,
self.isa_flags.predicate_view())) self.isa_flags.predicate_view()))

View File

@@ -4,7 +4,7 @@ use ir::InstructionData;
use ir::types; use ir::types;
use isa::EncInfo; use isa::EncInfo;
use isa::constraints::*; use isa::constraints::*;
use isa::enc_tables::{Level1Entry, Level2Entry}; use isa::enc_tables::*;
use isa::encoding::RecipeSizing; use isa::encoding::RecipeSizing;
include!(concat!(env!("OUT_DIR"), "/encoding-arm64.rs")); include!(concat!(env!("OUT_DIR"), "/encoding-arm64.rs"));

View File

@@ -66,6 +66,7 @@ impl TargetIsa for Isa {
.and_then(|enclist_offset| { .and_then(|enclist_offset| {
Ok(Encodings::new(enclist_offset, Ok(Encodings::new(enclist_offset,
&enc_tables::ENCLISTS[..], &enc_tables::ENCLISTS[..],
&enc_tables::RECIPE_PREDICATES[..],
inst, inst,
enc_tables::check_instp, enc_tables::check_instp,
self.isa_flags.predicate_view())) self.isa_flags.predicate_view()))

View File

@@ -9,6 +9,13 @@ use isa::{Encoding, Legalize};
use settings::PredicateView; use settings::PredicateView;
use std::ops::Range; use std::ops::Range;
/// A recipe predicate.
///
/// This is a predicate function capable of testing ISA and instruction predicates simultaneously.
///
/// A None predicate is always satisfied.
pub type RecipePredicate = Option<fn(PredicateView, &InstructionData) -> bool>;
/// Legalization action to perform when no encoding can be found for an instruction. /// 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. /// This is an index into an ISA-specific table of legalization actions.
@@ -147,6 +154,7 @@ pub struct Encodings<'a> {
inst: &'a InstructionData, inst: &'a InstructionData,
instp: fn(&InstructionData, EncListEntry) -> bool, instp: fn(&InstructionData, EncListEntry) -> bool,
isa_predicates: PredicateView<'a>, isa_predicates: PredicateView<'a>,
recipe_predicates: &'static [RecipePredicate],
} }
impl<'a> Encodings<'a> { impl<'a> Encodings<'a> {
@@ -155,8 +163,9 @@ impl<'a> Encodings<'a> {
/// # Parameters /// # Parameters
/// ///
/// - `offset` an offset into encoding list returned by `lookup_enclist` function. /// - `offset` an offset into encoding list returned by `lookup_enclist` function.
/// - `inst` the current instruction.
/// - `enclist` a list of encoding entries. /// - `enclist` a list of encoding entries.
/// - `recipe_predicates` is a slice of recipe predicate functions.
/// - `inst` the current instruction.
/// - `instp` an instruction predicate number to be evaluated on the current instruction. /// - `instp` an instruction predicate number to be evaluated on the current instruction.
/// - `isa_predicate_bytes` an ISA flags as a slice of bytes to evaluate an ISA predicate number /// - `isa_predicate_bytes` an ISA flags as a slice of bytes to evaluate an ISA predicate number
/// on the current instruction. /// on the current instruction.
@@ -166,6 +175,7 @@ impl<'a> Encodings<'a> {
/// or `None`. /// or `None`.
pub fn new(offset: usize, pub fn new(offset: usize,
enclist: &'static [EncListEntry], enclist: &'static [EncListEntry],
recipe_predicates: &'static [RecipePredicate],
inst: &'a InstructionData, inst: &'a InstructionData,
instp: fn(&InstructionData, EncListEntry) -> bool, instp: fn(&InstructionData, EncListEntry) -> bool,
isa_predicates: PredicateView<'a>) isa_predicates: PredicateView<'a>)
@@ -176,6 +186,15 @@ impl<'a> Encodings<'a> {
inst, inst,
instp, instp,
isa_predicates, isa_predicates,
recipe_predicates,
}
}
/// Check if the predicate for `recipe` is satisfied.
fn check_recipe(&self, recipe: u16) -> bool {
match self.recipe_predicates[recipe as usize] {
Some(p) => p(self.isa_predicates, self.inst),
None => true,
} }
} }
} }
@@ -188,13 +207,13 @@ impl<'a> Iterator for Encodings<'a> {
let pred = self.enclist[self.offset]; let pred = self.enclist[self.offset];
if pred <= CODE_ALWAYS { if pred <= CODE_ALWAYS {
// This is an instruction predicate followed by recipe and encbits entries. // This is an instruction predicate followed by recipe and encbits entries.
self.offset += 3;
if pred == CODE_ALWAYS || (self.instp)(self.inst, pred) { if pred == CODE_ALWAYS || (self.instp)(self.inst, pred) {
let encoding = Encoding::new(self.enclist[self.offset + 1], let recipe = self.enclist[self.offset - 2];
self.enclist[self.offset + 2]); if self.check_recipe(recipe) {
self.offset += 3; let encoding = Encoding::new(recipe, self.enclist[self.offset - 1]);
return Some(encoding); return Some(encoding);
} else { }
self.offset += 3;
} }
} else { } else {
// This is an ISA predicate entry. // This is an ISA predicate entry.

View File

@@ -4,7 +4,7 @@ use ir::types;
use ir::{Opcode, InstructionData}; use ir::{Opcode, InstructionData};
use isa::EncInfo; use isa::EncInfo;
use isa::constraints::*; use isa::constraints::*;
use isa::enc_tables::{Level1Entry, Level2Entry}; use isa::enc_tables::*;
use isa::encoding::RecipeSizing; use isa::encoding::RecipeSizing;
use predicates; use predicates;
use super::registers::*; use super::registers::*;

View File

@@ -73,6 +73,7 @@ impl TargetIsa for Isa {
.and_then(|enclist_offset| { .and_then(|enclist_offset| {
Ok(Encodings::new(enclist_offset, Ok(Encodings::new(enclist_offset,
&enc_tables::ENCLISTS[..], &enc_tables::ENCLISTS[..],
&enc_tables::RECIPE_PREDICATES[..],
inst, inst,
enc_tables::check_instp, enc_tables::check_instp,
self.isa_flags.predicate_view())) self.isa_flags.predicate_view()))

View File

@@ -5,7 +5,7 @@ use ir::types;
use ir::{Opcode, InstructionData}; use ir::{Opcode, InstructionData};
use isa::EncInfo; use isa::EncInfo;
use isa::constraints::*; use isa::constraints::*;
use isa::enc_tables::{Level1Entry, Level2Entry}; use isa::enc_tables::*;
use isa::encoding::RecipeSizing; use isa::encoding::RecipeSizing;
use predicates; use predicates;
use super::registers::*; use super::registers::*;

View File

@@ -73,6 +73,7 @@ impl TargetIsa for Isa {
.and_then(|enclist_offset| { .and_then(|enclist_offset| {
Ok(Encodings::new(enclist_offset, Ok(Encodings::new(enclist_offset,
&enc_tables::ENCLISTS[..], &enc_tables::ENCLISTS[..],
&enc_tables::RECIPE_PREDICATES[..],
inst, inst,
enc_tables::check_instp, enc_tables::check_instp,
self.isa_flags.predicate_view())) self.isa_flags.predicate_view()))