diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index 924ce8a109..2c737c2e7e 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -7,6 +7,7 @@ instructions. import re import importlib +from collections import namedtuple camel_re = re.compile('(^|_)([a-z])') @@ -148,6 +149,9 @@ class ScalarType(ValueType): def __repr__(self): return 'ScalarType({})'.format(self.name) + def rust_name(self): + return 'types::' + self.name.upper() + def by(self, lanes): """ Get a vector type with this type as the lane type. @@ -232,6 +236,19 @@ class BoolType(ScalarType): # Parametric polymorphism. +#: A `TypeSet` represents a set of types. We don't allow arbitrary subsets of +#: types, but use a parametrized approach instead. +#: This is represented as a named tuple so it can be used as a dictionary key. +TypeSet = namedtuple( + 'TypeSet', [ + 'allow_scalars', + 'allow_simd', + 'base', + 'all_ints', + 'all_floats', + 'all_bools']) + + class TypeVar(object): """ Type variables can be used in place of concrete types when defining @@ -257,10 +274,23 @@ class TypeVar(object): def __init__( self, name, doc, base=None, ints=False, floats=False, bools=False, - scalars=True, simd=False): + scalars=True, simd=False, + derived_func=None): self.name = name self.__doc__ = doc self.base = base + self.is_derived = isinstance(base, TypeVar) + if self.is_derived: + assert derived_func + self.derived_func = derived_func + else: + self.type_set = TypeSet( + allow_scalars=scalars, + allow_simd=simd, + base=base, + all_ints=ints, + all_floats=floats, + all_bools=bools) def __str__(self): return "`{}`".format(self.name) @@ -273,14 +303,18 @@ class TypeVar(object): When this type variable assumes a scalar type, the derived type will be the same scalar type. """ - return TypeVar("Lane type of " + self.name, '', base=self, simd=False) + return TypeVar( + "Lane type of " + self.name, '', + base=self, derived_func='Lane') def as_bool(self): """ Return a derived type variable that has the same vector geometry as this type variable, but with boolean lanes. Scalar types map to `b1`. """ - return TypeVar(self.name + " as boolean", '', base=self, bools=True) + return TypeVar( + self.name + " as boolean", '', + base=self, derived_func='AsBool') def operand_kind(self): # When a `TypeVar` object is used to describe the type of an `Operand` diff --git a/meta/cretonne/base.py b/meta/cretonne/base.py index 9f77fbf860..9e13377ed6 100644 --- a/meta/cretonne/base.py +++ b/meta/cretonne/base.py @@ -61,9 +61,9 @@ br_table = Instruction( 'br_table', r""" Indirect branch via jump table. - Use ``x`` as an unsigned index into the jump table ``JT``. If a jump table - entry is found, branch to the corresponding EBB. If no entry was found fall - through to the next instruction. + Use ``x`` as an unsigned index into the jump table ``JT``. If a jump + table entry is found, branch to the corresponding EBB. If no entry was + found fall through to the next instruction. Note that this branch instruction can't pass arguments to the targeted blocks. Split critical edges as needed to work around this. diff --git a/meta/gen_instr.py b/meta/gen_instr.py index 08cd81f1b0..1cd0ea8b4f 100644 --- a/meta/gen_instr.py +++ b/meta/gen_instr.py @@ -4,6 +4,7 @@ Generate sources with instruction info. import srcgen import constant_hash +from unique_table import UniqueTable, UniqueSeqTable import cretonne @@ -97,7 +98,8 @@ def gen_instruction_data_impl(fmt): fmt.doc_comment('Mutable reference to second result value, if any.') with fmt.indented( - "pub fn second_result_mut<'a>(&'a mut self) -> Option<&'a mut Value> {", '}'): + "pub fn second_result_mut<'a>(&'a mut self)" + + " -> Option<&'a mut Value> {", '}'): with fmt.indented('match *self {', '}'): for f in cretonne.InstructionFormat.all_formats: if not f.multiple_results: @@ -131,7 +133,11 @@ def collect_instr_groups(targets): def gen_opcodes(groups, fmt): - """Generate opcode enumerations.""" + """ + Generate opcode enumerations. + + Return a list of all instructions. + """ fmt.doc_comment('An instruction opcode.') fmt.doc_comment('') @@ -193,6 +199,125 @@ def gen_opcodes(groups, fmt): else: fmt.format('Opcode::{},', i.camel_name) fmt.line() + return instrs + + +def get_constraint(op, ctrl_typevar, type_sets): + """ + Get the value type constraint for an SSA value operand, where + `ctrl_typevar` is the controlling type variable. + + Each operand constraint is represented as a string, one of: + + - `Concrete(vt)`, where `vt` is a value type name. + - `Free(idx)` where `idx` is an index into `type_sets`. + - `Same`, `Lane`, `AsBool` for controlling typevar-derived constraints. + """ + t = op.typ + assert t.operand_kind() is cretonne.value + + # A concrete value type. + if isinstance(t, cretonne.ValueType): + return 'Concrete({})'.format(t.rust_name()) + + if t.free_typevar() is not ctrl_typevar: + assert not t.is_derived + return 'Free({})'.format(type_sets.add(t.type_set)) + + if t.is_derived: + assert t.base is ctrl_typevar, "Not derived directly from ctrl_typevar" + return t.derived_func + + assert t is ctrl_typevar + return 'Same' + + +def gen_type_constraints(fmt, instrs): + """ + Generate value type constraints for all instructions. + + - Emit a compact constant table of ValueTypeSet objects. + - Emit a compact constant table of OperandConstraint objects. + - Emit an opcode-indexed table of instruction constraints. + + """ + + # Table of TypeSet instances. + type_sets = UniqueTable() + + # Table of operand constraint sequences (as tuples). Each operand + # constraint is represented as a string, one of: + # - `Concrete(vt)`, where `vt` is a value type name. + # - `Free(idx)` where `idx` isan index into `type_sets`. + # - `Same`, `Lane`, `AsBool` for controlling typevar-derived constraints. + operand_seqs = UniqueSeqTable() + + # Preload table with constraints for typical binops. + operand_seqs.add(['Same'] * 3) + + # TypeSet indexes are encoded in 3 bits, with `111` reserved. + typeset_limit = 7 + + fmt.comment('Table of opcode constraints.') + with fmt.indented( + 'const OPCODE_CONSTRAINTS : [OpcodeConstraints; {}] = [' + .format(len(instrs)), '];'): + for i in instrs: + # Collect constraints for the value results, not including + # `variable_args` results which are always special cased. + constraints = list() + ctrl_typevar = None + ctrl_typeset = typeset_limit + if i.is_polymorphic: + ctrl_typevar = i.ctrl_typevar + ctrl_typeset = type_sets.add(ctrl_typevar.type_set) + for idx in i.value_results: + constraints.append( + get_constraint(i.outs[idx], ctrl_typevar, type_sets)) + for idx in i.format.value_operands: + constraints.append( + get_constraint(i.ins[idx], ctrl_typevar, type_sets)) + offset = operand_seqs.add(constraints) + fixed_results = len(i.value_results) + use_typevar_operand = i.is_polymorphic and i.use_typevar_operand + fmt.comment( + '{}: fixed_results={}, use_typevar_operand={}' + .format(i.camel_name, fixed_results, use_typevar_operand)) + fmt.comment('Constraints={}'.format(constraints)) + if i.is_polymorphic: + fmt.comment( + 'Polymorphic over {}'.format(ctrl_typevar.type_set)) + # Compute the bit field encoding, c.f. instructions.rs. + assert fixed_results < 8, "Bit field encoding too tight" + bits = (offset << 8) | (ctrl_typeset << 4) | fixed_results + if use_typevar_operand: + bits |= 8 + assert bits < 0x10000, "Constraint table too large for bit field" + fmt.line('OpcodeConstraints({:#06x}),'.format(bits)) + + fmt.comment('Table of value type sets.') + assert len(type_sets.table) <= typeset_limit, "Too many type sets" + with fmt.indented( + 'const TYPE_SETS : [ValueTypeSet; {}] = [' + .format(len(type_sets.table)), '];'): + for ts in type_sets.table: + with fmt.indented('ValueTypeSet {', '},'): + if ts.base: + fmt.line('base: {},'.format(ts.base.rust_name())) + else: + fmt.line('base: types::VOID,') + for field in ts._fields: + if field == 'base': + continue + fmt.line('{}: {},'.format( + field, str(getattr(ts, field)).lower())) + + fmt.comment('Table of operand constraint sequences.') + with fmt.indented( + 'const OPERAND_CONSTRAINTS : [OperandConstraint; {}] = [' + .format(len(operand_seqs.table)), '];'): + for c in operand_seqs.table: + fmt.line('OperandConstraint::{},'.format(c)) def generate(targets, out_dir): @@ -202,5 +327,6 @@ def generate(targets, out_dir): fmt = srcgen.Formatter() gen_formats(fmt) gen_instruction_data_impl(fmt) - gen_opcodes(groups, fmt) + instrs = gen_opcodes(groups, fmt) + gen_type_constraints(fmt, instrs) fmt.update_file('opcodes.rs', out_dir) diff --git a/meta/unique_table.py b/meta/unique_table.py new file mode 100644 index 0000000000..cbc45af200 --- /dev/null +++ b/meta/unique_table.py @@ -0,0 +1,68 @@ +""" +Generate a table of unique items. + +The `UniqueTable` class collects items into an array, removing duplicates. Each +item is mapped to its offset in the final array. + +This is a compression technique for compile-time generated tables. +""" + + +class UniqueTable: + """ + Collect items into the `table` list, removing duplicates. + """ + def __init__(self): + # List of items added in order. + self.table = list() + # Map item -> index. + self.index = dict() + + def add(self, item): + """ + Add a single item to the table if it isn't already there. + + Return the offset into `self.table` of the item. + """ + if item in self.index: + return self.index[item] + + idx = len(self.table) + self.index[item] = idx + self.table.append(item) + return idx + + +class UniqueSeqTable: + """ + Collect sequences into the `table` list, removing duplicates. + + Sequences don't have to be of the same length. + """ + def __init__(self): + self.table = list() + # Map seq -> index. + self.index = dict() + + def add(self, seq): + """ + Add a sequence of items to the table. If the table already contains the + items in `seq` in the same order, use those instead. + + Return the offset into `self.table` of the beginning of `seq`. + """ + if len(seq) == 0: + return 0 + seq = tuple(seq) + if seq in self.index: + return self.index[seq] + + idx = len(self.table) + self.table.extend(seq) + + # Add seq and all sub-sequences to `index`. + for length in range(1, len(seq) + 1): + for offset in range(len(seq) - length + 1): + self.index[seq[offset:offset+length]] = idx + offset + + return idx diff --git a/src/libcretonne/instructions.rs b/src/libcretonne/instructions.rs index cbc6b053dd..c6b353ae9c 100644 --- a/src/libcretonne/instructions.rs +++ b/src/libcretonne/instructions.rs @@ -11,7 +11,7 @@ use std::str::FromStr; use entities::*; use immediates::*; -use types::Type; +use types::{self, Type}; // Include code generated by `meta/gen_instr.py`. This file contains: // @@ -21,6 +21,12 @@ use types::Type; // - The private `fn opcode_name(Opcode) -> &'static str` function, and // - The hash table `const OPCODE_HASH_TABLE: [Opcode; N]`. // +// For value type constraints: +// +// - The `const OPCODE_CONSTRAINTS : [OpcodeConstraints; N]` table. +// - The `const TYPE_SETS : [ValueTypeSet; N]` table. +// - The `const OPERAND_CONSTRAINTS : [OperandConstraint; N]` table. +// include!(concat!(env!("OUT_DIR"), "/opcodes.rs")); impl Display for Opcode { @@ -38,6 +44,12 @@ impl Opcode { Some(OPCODE_FORMAT[self as usize - 1]) } } + + /// Get the constraint descriptor for this opcode. + /// Panic if this is called on `NotAnOpcode`. + pub fn constraints(self) -> OpcodeConstraints { + OPCODE_CONSTRAINTS[self as usize - 1] + } } // A primitive hash function for matching opcodes. @@ -259,6 +271,155 @@ impl InstructionData { } } + +/// Value type constraints for a given opcode. +/// +/// The `InstructionFormat` determines the constraints on most operands, but `Value` operands and +/// results are not determined by the format. Every `Opcode` has an associated +/// `OpcodeConstraints` object that provides the missing details. +/// +/// Since there can be a lot of opcodes, the `OpcodeConstraints` object is encoded as a bit field +/// by the `meta/gen_instr.py` script. +/// +/// The bit field bits are: +/// +/// Bits 0-2: +/// Number of fixed result values. This does not include `variable_args` results as are +/// produced by call instructions. +/// +/// Bit 3: +/// This opcode is polymorphic and the controlling type variable can be inferred from the +/// designated input operand. This is the `typevar_operand` index given to the +/// `InstructionFormat` meta language object. When bit 0 is not set, the controlling type +/// variable must be the first output value instead. +/// +/// Bits 4-7: +/// Permitted set of types for the controlling type variable as an index into `TYPE_SETS`. +/// +/// Bits 8-15: +/// Offset into `OPERAND_CONSTRAINT` table of the descriptors for this opcode. The first +/// `fixed_results()` entries describe the result constraints, then follows constraints for the +/// fixed `Value` input operands. The number of `Value` inputs isdetermined by the instruction +/// format. +/// +#[derive(Clone, Copy)] +pub struct OpcodeConstraints(u16); + +impl OpcodeConstraints { + /// Can the controlling type variable for this opcode be inferred from the designated value + /// input operand? + /// This also implies that this opcode is polymorphic. + pub fn use_typevar_operand(self) -> bool { + (self.0 & 0x8) != 0 + } + + /// Get the number of *fixed* result values produced by this opcode. + /// This does not include `variable_args` produced by calls. + pub fn fixed_results(self) -> usize { + (self.0 & 0x7) as usize + } + + /// Get the offset into `TYPE_SETS` for the controlling type variable. + /// Returns `None` if the instruction is not polymorphic. + fn typeset_offset(self) -> Option { + let offset = ((self.0 & 0xff) >> 4) as usize; + if offset < TYPE_SETS.len() { + Some(offset) + } else { + None + } + } + + /// Get the offset into OPERAND_CONSTRAINTS where the descriptors for this opcode begin. + fn constraint_offset(self) -> usize { + (self.0 >> 8) as usize + } + + /// Get the value type of result number `n`, having resolved the controlling type variable to + /// `ctrl_type`. + pub fn result_type(self, n: usize, ctrl_type: Type) -> Type { + assert!(n < self.fixed_results(), "Invalid result index"); + OPERAND_CONSTRAINTS[self.constraint_offset() + n] + .resolve(ctrl_type) + .expect("Result constraints can't be free") + } + + /// Get the typeset of allowed types for the controlling type variable in a polymorphic + /// instruction. + pub fn ctrl_typeset(self) -> Option { + self.typeset_offset().map(|offset| TYPE_SETS[offset]) + } +} + +/// A value type set describes the permitted set of types for a type variable. +#[derive(Clone, Copy)] +pub struct ValueTypeSet { + allow_scalars: bool, + allow_simd: bool, + + base: Type, + all_ints: bool, + all_floats: bool, + all_bools: bool, +} + +impl ValueTypeSet { + /// Is `scalar` part of the base type set? + /// + /// Note that the base type set does not have to be included in the type set proper. + fn is_base_type(&self, scalar: Type) -> bool { + scalar == self.base || (self.all_ints && scalar.is_int()) || + (self.all_floats && scalar.is_float()) || (self.all_bools && scalar.is_bool()) + } + + /// Does `typ` belong to this set? + pub fn contains(&self, typ: Type) -> bool { + let allowed = if typ.is_scalar() { + self.allow_scalars + } else { + self.allow_simd + }; + allowed && self.is_base_type(typ.lane_type()) + } +} + +/// Operand constraints. This describes the value type constraints on a single `Value` operand. +enum OperandConstraint { + /// This operand has a concrete value type. + Concrete(Type), + + /// This operand can vary freely within the given type set. + /// The type set is identified by its index into the TYPE_SETS constant table. + Free(u8), + + /// This operand is the same type as the controlling type variable. + Same, + + /// This operand is `ctrlType.lane_type()`. + Lane, + + /// This operand is `ctrlType.as_bool()`. + AsBool, +} + +impl OperandConstraint { + /// Resolve this operand constraint into a concrete value type, given the value of the + /// controlling type variable. + /// Returns `None` if this is a free operand which is independent of the controlling type + /// variable. + pub fn resolve(&self, ctrl_type: Type) -> Option { + use self::OperandConstraint::*; + match *self { + Concrete(t) => Some(t), + Free(_) => None, + Same => Some(ctrl_type), + Lane => Some(ctrl_type.lane_type()), + AsBool => Some(ctrl_type.as_bool()), + } + } +} + + #[cfg(test)] mod tests { use super::*; diff --git a/src/libcretonne/types.rs b/src/libcretonne/types.rs index 967879c14d..a4ff443541 100644 --- a/src/libcretonne/types.rs +++ b/src/libcretonne/types.rs @@ -83,6 +83,20 @@ impl Type { } } + /// Get a type with the same number of lanes as this type, but with the lanes replaces by + /// booleans of the same size. + pub fn as_bool(self) -> Type { + // Replace the low 4 bits with the boolean version, preserve the high 4 bits. + let lane = match self.lane_type() { + B8 | I8 => B8, + B16 | I16 => B16, + B32 | I32 | F32 => B32, + B64 | I64 | F64 => B64, + _ => B1, + }; + Type(lane.0 | (self.0 & 0xf0)) + } + /// Is this the VOID type? pub fn is_void(self) -> bool { self == VOID