Use a more compact encoding list representation.

Encodings has a 16-bit "recipe" field, but even Intel only has 57
recipes currently, so it is unlikely that we will ever need to full
range. Use this to represent encoding lists more compactly.

Change the encoding list to a format that:

- Doesn't need a predicate entry before every encoding entry.
- Doesn't need a terminator after the list for each instruction.
- Supports multiple "stop codes" for configurable guidance of the
  legalizer.

The encoding scheme has these limits:

- 2*NR + NS <= 0x1000
- INSTP + ISAP <= 0x1000

Where:

- NR is the number of recipes in an ISA,
- NS is the number of stop codes (legalization actions).
- INSTP is the number of instruction predicates.
- ISAP is the number of discrete ISA predicates.
This commit is contained in:
Jakob Stoklund Olesen
2017-07-25 09:37:10 -07:00
parent 345d6754f5
commit 22d49c6510
2 changed files with 298 additions and 119 deletions

View File

@@ -39,13 +39,13 @@ types, so many of the level 2 tables will be cold.
An encoding list is a non-empty sequence of list entries. Each entry has An encoding list is a non-empty sequence of list entries. Each entry has
one of these forms: one of these forms:
1. Instruction predicate, encoding recipe, and encoding bits. If the 1. Recipe + bits. Use this encoding if the recipe predicate is satisfied.
instruction predicate is true, use this recipe and bits. 2. Recipe + bits, final entry. Use this encoding if the recipe predicate is
2. ISA predicate and skip-count. If the ISA predicate is false, skip the next satisfied. Otherwise, stop with the default legalization code.
*skip-count* entries in the list. If the skip count is zero, stop 3. Stop with legalization code.
completely. 4. Predicate + skip count. Test predicate and skip N entries if it is false.
3. Stop. End of list marker. If this is reached, the instruction does not have 4. Predicate + stop. Test predicate and stop with the default legalization code
a legal encoding. if it is false.
The instruction predicate is also used to distinguish between polymorphic The instruction predicate is also used to distinguish between polymorphic
instructions with different types for secondary type variables. instructions with different types for secondary type variables.
@@ -56,9 +56,10 @@ from constant_hash import compute_quadratic
from unique_table import UniqueSeqTable from unique_table import UniqueSeqTable
from collections import OrderedDict, defaultdict from collections import OrderedDict, defaultdict
import math import math
import itertools from itertools import groupby
from cdsl.registers import RegClass, Register, Stack from cdsl.registers import RegClass, Register, Stack
from cdsl.predicates import FieldPredicate from cdsl.predicates import FieldPredicate
from cdsl.settings import SettingGroup
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
@@ -173,42 +174,228 @@ def emit_recipe_predicates(recipes, fmt):
fmt.format('Some({}),', pname[p]) fmt.format('Some({}),', pname[p])
# The u16 values in an encoding list entry are interpreted as follows:
#
# NR = len(all_recipes)
#
# entry < 2*NR
# Try Encoding(entry/2, next_entry) if the recipe predicate is satisfied.
# If bit 0 is set, stop with the default legalization code.
# If bit 0 is clear, keep going down the list.
# entry < PRED_START
# Stop with legalization code `entry - 2*NR`.
#
# Remaining entries are interpreted as (skip, pred) pairs, where:
#
# skip = (entry - PRED_START) >> PRED_BITS
# pred = (entry - PRED_START) & PRED_MASK
#
# If the predicate is satisfied, keep going. Otherwise skip over the next
# `skip` entries. If skip == 0, stop with the default legalization code.
#
# The `pred` predicate number is interpreted as an instruction predicate if it
# is in range, otherwise an ISA predicate.
class Encoder:
"""
Encoder for the list format above.
Two parameters are needed:
:param NR: Number of recipes.
:param NI: Number of instruction predicates.
"""
def __init__(self, isa):
# type: (TargetISA) -> None
self.isa = isa
self.NR = len(isa.all_recipes)
self.NI = len(isa.all_instps)
# u16 encoding list words.
self.words = list() # type: List[int]
# Documentation comments: Index into `words` + comment.
self.docs = list() # type: List[Tuple[int, str]]
# Encoding lists are represented as u16 arrays. # Encoding lists are represented as u16 arrays.
CODE_BITS = 16 CODE_BITS = 16
# Beginning of the predicate code words.
PRED_START = 0x1000
# Number of bits used to hold a predicate number (instruction + ISA
# predicates.
PRED_BITS = 12 PRED_BITS = 12
# Mask for extracting the predicate number.
PRED_MASK = (1 << PRED_BITS) - 1 PRED_MASK = (1 << PRED_BITS) - 1
# 0..CODE_ALWAYS means: Check instruction predicate and use the next two def max_skip(self):
# entries as a (recipe, encbits) pair if true. CODE_ALWAYS is the always-true # type: () -> int
# predicate, smaller numbers refer to instruction predicates. """The maximum number of entries that a predicate can skip."""
CODE_ALWAYS = PRED_MASK return (1 << (self.CODE_BITS - self.PRED_BITS)) - 1
# Codes above CODE_ALWAYS indicate an ISA predicate to be tested. def recipe(self, enc, final):
# `x & PRED_MASK` is the ISA predicate number to test. # type: (Encoding, bool) -> None
# `(x >> PRED_BITS)*3` is the number of u16 table entries to skip if the ISA """Add a recipe+bits entry to the list."""
# predicate is false. (The factor of three corresponds to the (inst-pred, offset = len(self.words)
# recipe, encbits) triples. code = 2 * enc.recipe.number
#
# Finally, CODE_FAIL indicates the end of the list.
CODE_FAIL = (1 << CODE_BITS) - 1
def seq_doc(enc):
# type: (Encoding) -> Tuple[Tuple[int, int, int], str]
"""
Return a tuple containing u16 representations of the instruction predicate
an recipe / encbits.
Also return a doc string.
"""
if enc.instp:
p = enc.instp.number
doc = '--> {} when {}'.format(enc, enc.instp)
else:
p = CODE_ALWAYS
doc = '--> {}'.format(enc) doc = '--> {}'.format(enc)
assert p <= CODE_ALWAYS if final:
return ((p, enc.recipe.number, enc.encbits), doc) code += 1
doc += ' and stop'
assert(code < self.PRED_START)
self.words.extend((code, enc.encbits))
self.docs.append((offset, doc))
def _pred(self, pred, skip, n):
# type: (PredNode, int, int) -> None
"""Add a predicate entry."""
assert n <= self.PRED_MASK
code = n | (skip << self.PRED_BITS)
code += self.PRED_START
assert code < (1 << self.CODE_BITS)
if skip == 0:
doc = 'stop'
else:
doc = 'skip ' + str(skip)
doc = '{} unless {}'.format(doc, pred)
self.docs.append((len(self.words), doc))
self.words.append(code)
def instp(self, pred, skip):
# type: (PredNode, int) -> None
"""Add an instruction predicate entry."""
self._pred(pred, skip, pred.number)
def isap(self, pred, skip):
# type: (PredNode, int) -> None
"""Add an ISA predicate entry."""
n = self.isa.settings.predicate_number[pred]
# ISA predicates follow the instruction predicates.
self._pred(pred, skip, self.NI + n)
class EncNode(object):
"""
An abstract node in the encoder tree for an instruction.
This tree is used to simplify the predicates guarding recipe+bits entries.
"""
def size(self):
# type: () -> int
"""Get the number of list entries needed to encode this tree."""
raise NotImplementedError('EncNode.size() is abstract')
def encode(self, encoder, final):
# type: (Encoder, bool) -> None
"""Encode this tree."""
raise NotImplementedError('EncNode.encode() is abstract')
def optimize(self):
# type: () -> EncNode
"""Transform this encoder tree into something simpler."""
return self
def predicate(self):
# type: () -> PredNode
"""Get the predicate guarding this tree, or `None` for always"""
return None
class EncPred(EncNode):
"""
An encoder tree node which asserts a predicate on its child nodes.
A `None` predicate is always satisfied.
"""
def __init__(self, pred, children):
# type: (PredNode, List[EncNode]) -> None
self.pred = pred
self.children = children
def size(self):
# type: () -> int
s = 1 if self.pred else 0
s += sum(c.size() for c in self.children)
return s
def encode(self, encoder, final):
# type: (Encoder, bool) -> None
if self.pred:
skip = 0 if final else self.size() - 1
ctx = self.pred.predicate_context()
if isinstance(ctx, SettingGroup):
encoder.isap(self.pred, skip)
else:
encoder.instp(self.pred, skip)
final_idx = len(self.children) - 1 if final else -1
for idx, node in enumerate(self.children):
node.encode(encoder, idx == final_idx)
def predicate(self):
# type: () -> PredNode
return self.pred
def optimize(self):
# type: () -> EncNode
"""
Optimize a predicate node in the tree by combining child nodes that
have identical predicates.
"""
cnodes = list() # type: List[EncNode]
for pred, niter in groupby(
map(lambda c: c.optimize(), self.children),
key=lambda c: c.predicate()):
nodes = list(niter)
if pred is None or len(nodes) <= 1:
cnodes.extend(nodes)
continue
# We have multiple children with identical predicates.
# Group them all into `n0`.
n0 = nodes[0]
assert isinstance(n0, EncPred)
for n in nodes[1:]:
assert isinstance(n, EncPred)
n0.children.extend(n.children)
cnodes.append(n0)
# Finally strip a redundant grouping node.
if self.pred is None and len(cnodes) == 1:
return cnodes[0]
else:
self.children = cnodes
return self
class EncLeaf(EncNode):
"""
A leaf in the encoder tree.
This represents a single `Encoding`, without its predicates (they are
represented in the tree by parent nodes.
"""
def __init__(self, encoding):
# type: (Encoding) -> None
self.encoding = encoding
def size(self):
# type: () -> int
# recipe + bits.
return 2
def encode(self, encoder, final):
# type: (Encoder, bool) -> None
encoder.recipe(self.encoding, final)
class EncList(object): class EncList(object):
@@ -239,25 +426,23 @@ class EncList(object):
name += ' ({})'.format(self.encodings[0].cpumode) name += ' ({})'.format(self.encodings[0].cpumode)
return name return name
def by_isap(self): def encoder_tree(self):
# type: () -> Iterable[Tuple[PredNode, Tuple[Encoding, ...]]] # type: () -> EncNode
""" """
Group the encodings by ISA predicate without reordering them. Generate an optimized encoder tree for this list. The tree represents
all of the encodings with parent nodes for the predicates that need
checking.
"""
forest = list() # type: List[EncNode]
for enc in self.encodings:
n = EncLeaf(enc) # type: EncNode
if enc.instp:
n = EncPred(enc.instp, [n])
if enc.isap:
n = EncPred(enc.isap, [n])
forest.append(n)
Yield a sequence of `(isap, (encs...))` tuples where `isap` is the ISA return EncPred(None, forest).optimize()
predicate or `None`, and `(encs...)` is a tuple of encodings that all
have the same ISA predicate.
"""
maxlen = CODE_FAIL >> PRED_BITS
for isap, groupi in itertools.groupby(
self.encodings, lambda enc: enc.isap):
group = tuple(groupi)
# This probably never happens, but we can't express more than
# maxlen encodings per isap.
while len(group) > maxlen:
yield (isap, group[0:maxlen])
group = group[maxlen:]
yield (isap, group)
def encode(self, seq_table, doc_table, isa): def encode(self, seq_table, doc_table, isa):
# type: (UniqueSeqTable, DefaultDict[int, List[str]], TargetISA) -> None # noqa # type: (UniqueSeqTable, DefaultDict[int, List[str]], TargetISA) -> None # noqa
@@ -269,34 +454,20 @@ class EncList(object):
Adds comment lines to `doc_table` keyed by seq_table offsets. Adds comment lines to `doc_table` keyed by seq_table offsets.
""" """
words = list() # type: List[int] # Use an encoder object to hold the parameters.
docs = list() # type: List[Tuple[int, str]] encoder = Encoder(isa)
tree = self.encoder_tree()
tree.encode(encoder, True)
# Group our encodings by isap. self.offset = seq_table.add(encoder.words)
for isap, group in self.by_isap():
if isap:
# We have an ISA predicate covering `glen` encodings.
pnum = isa.settings.predicate_number[isap]
glen = len(group)
doc = 'skip {}x3 unless {}'.format(glen, isap)
docs.append((len(words), doc))
words.append((glen << PRED_BITS) | pnum)
for enc in group:
seq, doc = seq_doc(enc)
docs.append((len(words), doc))
words.extend(seq)
# Terminate the list.
words.append(CODE_FAIL)
self.offset = seq_table.add(words)
# Add doc comments. # Add doc comments.
doc_table[self.offset].append( doc_table[self.offset].append(
'{:06x}: {}'.format(self.offset, self.name())) '{:06x}: {}'.format(self.offset, self.name()))
for pos, doc in docs: for pos, doc in encoder.docs:
doc_table[self.offset + pos].append(doc) doc_table[self.offset + pos].append(doc)
doc_table[self.offset + len(encoder.words)].insert(
0, 'end of: {}'.format(self.name()))
class Level2Table(object): class Level2Table(object):

View File

@@ -143,22 +143,19 @@ pub fn lookup_enclist<OffT1, OffT2>(ctrl_typevar: Type,
/// Encoding lists are represented as sequences of u16 words. /// Encoding lists are represented as sequences of u16 words.
pub type EncListEntry = u16; pub type EncListEntry = u16;
/// Number of bits used to represent a predicate. c.f. `meta.gen_encoding.py`. /// Number of bits used to represent a predicate. c.f. `meta/gen_encoding.py`.
const PRED_BITS: u8 = 12; const PRED_BITS: u8 = 12;
const PRED_MASK: EncListEntry = (1 << PRED_BITS) - 1; const PRED_MASK: usize = (1 << PRED_BITS) - 1;
/// First code word representing a predicate check. c.f. `meta/gen_encoding.py`.
/// The match-always instruction predicate. c.f. `meta.gen_encoding.py`. const PRED_START: usize = 0x1000;
const CODE_ALWAYS: EncListEntry = PRED_MASK;
/// The encoding list terminator.
const CODE_FAIL: EncListEntry = 0xffff;
/// An iterator over legal encodings for the instruction. /// An iterator over legal encodings for the instruction.
pub struct Encodings<'a> { pub struct Encodings<'a> {
// Current offset into `enclist`, or out of bounds after we've reached the end.
offset: usize, offset: usize,
enclist: &'static [EncListEntry],
inst: &'a InstructionData, inst: &'a InstructionData,
isa_predicates: PredicateView<'a>, isa_predicates: PredicateView<'a>,
enclist: &'static [EncListEntry],
recipe_predicates: &'static [RecipePredicate], recipe_predicates: &'static [RecipePredicate],
inst_predicates: &'static [InstPredicate], inst_predicates: &'static [InstPredicate],
} }
@@ -166,16 +163,6 @@ pub struct Encodings<'a> {
impl<'a> Encodings<'a> { impl<'a> Encodings<'a> {
/// Creates a new instance of `Encodings`. /// Creates a new instance of `Encodings`.
/// ///
/// # Parameters
///
/// - `offset` an offset into encoding list returned by `lookup_enclist` function.
/// - `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.
/// - `isa_predicate_bytes` an ISA flags as a slice of bytes to evaluate an ISA predicate number
/// on the current instruction.
///
/// This iterator provides search for encodings that applies to the given instruction. The /// This iterator provides search for encodings that applies to the given instruction. The
/// encoding lists are laid out such that first call to `next` returns valid entry in the list /// encoding lists are laid out such that first call to `next` returns valid entry in the list
/// or `None`. /// or `None`.
@@ -196,42 +183,63 @@ impl<'a> Encodings<'a> {
} }
} }
/// Check if the predicate for `recipe` is satisfied. /// Check if the `rpred` recipe predicate s satisfied.
fn check_recipe(&self, recipe: u16) -> bool { fn check_recipe(&self, rpred: RecipePredicate) -> bool {
match self.recipe_predicates[recipe as usize] { match rpred {
Some(p) => p(self.isa_predicates, self.inst), Some(p) => p(self.isa_predicates, self.inst),
None => true, None => true,
} }
} }
/// Check an instruction or isa predicate.
fn check_pred(&self, pred: usize) -> bool {
if let Some(&p) = self.inst_predicates.get(pred) {
p(self.inst)
} else {
let pred = pred - self.inst_predicates.len();
self.isa_predicates.test(pred)
}
}
} }
impl<'a> Iterator for Encodings<'a> { impl<'a> Iterator for Encodings<'a> {
type Item = Encoding; type Item = Encoding;
fn next(&mut self) -> Option<Encoding> { fn next(&mut self) -> Option<Encoding> {
while self.enclist[self.offset] != CODE_FAIL { while let Some(entryref) = self.enclist.get(self.offset) {
let pred = self.enclist[self.offset]; let entry = *entryref as usize;
if pred <= CODE_ALWAYS {
// This is an instruction predicate followed by recipe and encbits entries. // Check for "recipe+bits".
self.offset += 3; let recipe = entry >> 1;
let satisfied = match self.inst_predicates.get(pred as usize) { if let Some(&rpred) = self.recipe_predicates.get(recipe) {
Some(p) => p(self.inst), let bits = self.offset + 1;
None => true, if entry & 1 == 0 {
}; self.offset += 2; // Next entry.
if satisfied {
let recipe = self.enclist[self.offset - 2];
if self.check_recipe(recipe) {
let encoding = Encoding::new(recipe, self.enclist[self.offset - 1]);
return Some(encoding);
}
}
} else { } else {
// This is an ISA predicate entry. self.offset = !0; // Stop.
self.offset += 1;
if !self.isa_predicates.test((pred & PRED_MASK) as usize) {
// ISA predicate failed, skip the next N entries.
self.offset += 3 * (pred >> PRED_BITS) as usize;
} }
if self.check_recipe(rpred) {
return Some(Encoding::new(recipe as u16, self.enclist[bits]));
}
continue;
}
// Check for "stop with legalize".
if entry < PRED_START {
unimplemented!();
}
// Finally, this must be a predicate entry.
let pred_entry = entry - PRED_START;
let skip = pred_entry >> PRED_BITS;
let pred = pred_entry & PRED_MASK;
if self.check_pred(pred) {
self.offset += 1;
} else if skip == 0 {
self.offset = !0 // This means stop.
} else {
self.offset += 1 + skip;
} }
} }
None None