From d45b011fa215c5129f90bea89c2ce06d92b2b973 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 27 Sep 2016 13:31:31 -0700 Subject: [PATCH] Represent type sets with ranges. Allow limits on the smallest and largest integer type in the set, the highest and lowest number of lanes etc. --- meta/cretonne/typevar.py | 175 ++++++++++++++++++++++++----- meta/gen_instr.py | 10 +- src/libcretonne/ir/instructions.rs | 136 +++++++++++++--------- src/libcretonne/ir/types.rs | 12 ++ 4 files changed, 240 insertions(+), 93 deletions(-) diff --git a/meta/cretonne/typevar.py b/meta/cretonne/typevar.py index ec2693829b..7ee7182ad7 100644 --- a/meta/cretonne/typevar.py +++ b/meta/cretonne/typevar.py @@ -5,20 +5,127 @@ Cretonne instructions and instruction transformations can be specified to be polymorphic by using type variables. """ from __future__ import absolute_import -from collections import namedtuple +import math from . import value -#: 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']) + +MAX_LANES = 256 +MAX_BITS = 64 + + +def is_power_of_two(x): + return x > 0 and x & (x-1) == 0 + + +def int_log2(x): + return int(math.log(x, 2)) + + +class TypeSet(object): + """ + A set of types. + + We don't allow arbitrary subsets of types, but use a parametrized approach + instead. + + Objects of this class can be used as dictionary keys. + + Parametrized type sets are specified in terms of ranges: + + - The permitted range of vector lanes, where 1 indicates a scalar type. + - The permitted range of integer types. + - The permitted range of floating point types, and + - The permitted range of boolean types. + + The ranges are inclusive from smallest bit-width to largest bit-width. + + :param lanes: `(min, max)` inclusive range of permitted vector lane counts. + :param ints: `(min, max)` inclusive range of permitted scalar integer + widths. + :param floats: `(min, max)` inclusive range of permitted scalar floating + point widths. + :param bools: `(min, max)` inclusive range of permitted scalar boolean + widths. + """ + + def __init__(self, lanes, ints=None, floats=None, bools=None): + self.min_lanes, self.max_lanes = lanes + assert is_power_of_two(self.min_lanes) + assert is_power_of_two(self.max_lanes) + assert self.max_lanes <= MAX_LANES + + if ints: + if ints is True: + ints = (8, MAX_BITS) + self.min_int, self.max_int = ints + assert is_power_of_two(self.min_int) + assert is_power_of_two(self.max_int) + assert self.max_int <= MAX_BITS + else: + self.min_int = None + self.max_int = None + + if floats: + if floats is True: + floats = (32, 64) + self.min_float, self.max_float = floats + assert is_power_of_two(self.min_float) + assert self.min_float >= 32 + assert is_power_of_two(self.max_float) + assert self.max_float <= 64 + else: + self.min_float = None + self.max_float = None + + if bools: + if bools is True: + bools = (1, MAX_BITS) + self.min_bool, self.max_bool = bools + assert is_power_of_two(self.min_bool) + assert is_power_of_two(self.max_bool) + assert self.max_bool <= MAX_BITS + else: + self.min_bool = None + self.max_bool = None + + def typeset_key(self): + """Key tuple used for hashing and equality.""" + return (self.min_lanes, self.max_lanes, + self.min_int, self.max_int, + self.min_float, self.max_float, + self.min_bool, self.max_bool) + + def __hash__(self): + return hash(self.typeset_key()) + + def __eq__(self, other): + return self.typeset_key() == other.typeset_key() + + def __repr__(self): + s = 'TypeSet(lanes=({}, {})'.format(self.min_lanes, self.max_lanes) + if self.min_int is not None: + s += ', ints=({}, {})'.format(self.min_int, self.max_int) + if self.min_float is not None: + s += ', floats=({}, {})'.format(self.min_float, self.max_float) + if self.min_bool is not None: + s += ', bools=({}, {})'.format(self.min_bool, self.max_bool) + return s + ')' + + def emit_fields(self, fmt): + """Emit field initializers for this typeset.""" + fmt.comment(repr(self)) + fields = ('lanes', 'int', 'float', 'bool') + for field in fields: + min_val = getattr(self, 'min_' + field) + max_val = getattr(self, 'max_' + field) + if min_val is None: + fmt.line('min_{}: 0,'.format(field)) + fmt.line('max_{}: 0,'.format(field)) + else: + fmt.line('min_{}: {},'.format( + field, int_log2(min_val))) + fmt.line('max_{}: {},'.format( + field, int_log2(max_val) + 1)) class TypeVar(object): @@ -33,37 +140,45 @@ class TypeVar(object): :param name: Short name of type variable used in instruction descriptions. :param doc: Documentation string. - :param base: Single base type or list of base types. Use this to specify an - exact set of base types if the general categories below are not good - enough. - :param ints: Allow all integer base types. - :param floats: Allow all floating point base types. - :param bools: Allow all boolean base types. + :param ints: Allow all integer base types, or `(min, max)` bit-range. + :param floats: Allow all floating point base types, or `(min, max)` + bit-range. + :param bools: Allow all boolean base types, or `(min, max)` bit-range. :param scalars: Allow type variable to assume scalar types. - :param simd: Allow type variable to assume vector types. + :param simd: Allow type variable to assume vector types, or `(min, max)` + lane count range. """ def __init__( - self, name, doc, base=None, + self, name, doc, ints=False, floats=False, bools=False, scalars=True, simd=False, - derived_func=None): + base=None, derived_func=None): self.name = name self.__doc__ = doc - self.base = base self.is_derived = isinstance(base, TypeVar) - if self.is_derived: + if base: + assert self.is_derived assert derived_func + self.base = base self.derived_func = derived_func self.name = '{}({})'.format(derived_func, base.name) else: + min_lanes = 1 if scalars else 2 + if simd: + if simd is True: + max_lanes = MAX_LANES + else: + min_lanes, max_lanes = simd + assert not scalars or min_lanes <= 2 + else: + max_lanes = 1 + self.type_set = TypeSet( - allow_scalars=scalars, - allow_simd=simd, - base=base, - all_ints=ints, - all_floats=floats, - all_bools=bools) + lanes=(min_lanes, max_lanes), + ints=ints, + floats=floats, + bools=bools) def __str__(self): return "`{}`".format(self.name) @@ -92,7 +207,7 @@ class TypeVar(object): return value def free_typevar(self): - if isinstance(self.base, TypeVar): + if self.is_derived: return self.base else: return self diff --git a/meta/gen_instr.py b/meta/gen_instr.py index 6c67077cb0..cc636cf83a 100644 --- a/meta/gen_instr.py +++ b/meta/gen_instr.py @@ -344,15 +344,7 @@ def gen_type_constraints(fmt, instrs): .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())) + ts.emit_fields(fmt) fmt.comment('Table of operand constraint sequences.') with fmt.indented( diff --git a/src/libcretonne/ir/instructions.rs b/src/libcretonne/ir/instructions.rs index bfc1fe65f3..afd6ba319f 100644 --- a/src/libcretonne/ir/instructions.rs +++ b/src/libcretonne/ir/instructions.rs @@ -480,13 +480,14 @@ impl OpcodeConstraints { /// 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, + min_lanes: u8, + max_lanes: u8, + min_int: u8, + max_int: u8, + min_float: u8, + max_float: u8, + min_bool: u8, + max_bool: u8, } impl ValueTypeSet { @@ -494,42 +495,38 @@ impl ValueTypeSet { /// /// 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()) + let l2b = scalar.log2_lane_bits(); + if scalar.is_int() { + self.min_int <= l2b && l2b < self.max_int + } else if scalar.is_float() { + self.min_float <= l2b && l2b < self.max_float + } else if scalar.is_bool() { + self.min_bool <= l2b && l2b < self.max_bool + } else { + false + } } /// 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()) + let l2l = typ.log2_lane_count(); + self.min_lanes <= l2l && l2l < self.max_lanes && self.is_base_type(typ.lane_type()) } /// Get an example member of this type set. /// /// This is used for error messages to avoid suggesting invalid types. pub fn example(&self) -> Type { - if self.base != types::VOID { - return self.base; - } - let t = if self.all_ints { + let t = if self.max_int > 5 { types::I32 - } else if self.all_floats { + } else if self.max_float > 5 { types::F32 - } else if self.allow_scalars { - types::B1 - } else { + } else if self.max_bool > 5 { types::B32 - }; - - if self.allow_scalars { - t } else { - t.by(4).unwrap() - } + types::B1 + }; + t.by(1 << self.min_lanes).unwrap() } } @@ -611,43 +608,74 @@ mod tests { use ir::types::*; let vts = ValueTypeSet { - allow_scalars: true, - allow_simd: true, - base: VOID, - all_ints: true, - all_floats: false, - all_bools: true, + min_lanes: 0, + max_lanes: 8, + min_int: 3, + max_int: 7, + min_float: 0, + max_float: 0, + min_bool: 3, + max_bool: 7, }; + assert!(vts.contains(I32)); + assert!(vts.contains(I64)); + assert!(vts.contains(I32X4)); + assert!(!vts.contains(F32)); + assert!(!vts.contains(B1)); + assert!(vts.contains(B8)); + assert!(vts.contains(B64)); assert_eq!(vts.example().to_string(), "i32"); let vts = ValueTypeSet { - allow_scalars: true, - allow_simd: true, - base: VOID, - all_ints: false, - all_floats: true, - all_bools: true, + min_lanes: 0, + max_lanes: 8, + min_int: 0, + max_int: 0, + min_float: 5, + max_float: 7, + min_bool: 3, + max_bool: 7, }; assert_eq!(vts.example().to_string(), "f32"); let vts = ValueTypeSet { - allow_scalars: false, - allow_simd: true, - base: VOID, - all_ints: false, - all_floats: true, - all_bools: true, + min_lanes: 1, + max_lanes: 8, + min_int: 0, + max_int: 0, + min_float: 5, + max_float: 7, + min_bool: 3, + max_bool: 7, }; - assert_eq!(vts.example().to_string(), "f32x4"); + assert_eq!(vts.example().to_string(), "f32x2"); let vts = ValueTypeSet { - allow_scalars: false, - allow_simd: true, - base: VOID, - all_ints: false, - all_floats: false, - all_bools: true, + min_lanes: 2, + max_lanes: 8, + min_int: 0, + max_int: 0, + min_float: 0, + max_float: 0, + min_bool: 3, + max_bool: 7, }; + assert!(!vts.contains(B32X2)); + assert!(vts.contains(B32X4)); assert_eq!(vts.example().to_string(), "b32x4"); + + let vts = ValueTypeSet { + // TypeSet(lanes=(1, 256), ints=(8, 64)) + min_lanes: 0, + max_lanes: 9, + min_int: 3, + max_int: 7, + min_float: 0, + max_float: 0, + min_bool: 0, + max_bool: 0, + }; + assert!(vts.contains(I32)); + assert!(vts.contains(I32X4)); } } diff --git a/src/libcretonne/ir/types.rs b/src/libcretonne/ir/types.rs index f4e0a6b75f..1eb546bde6 100644 --- a/src/libcretonne/ir/types.rs +++ b/src/libcretonne/ir/types.rs @@ -43,6 +43,18 @@ impl Type { Type(self.0 & 0x0f) } + /// Get log2 of the number of bits in a lane. + pub fn log2_lane_bits(self) -> u8 { + match self.lane_type() { + B1 => 0, + B8 | I8 => 3, + B16 | I16 => 4, + B32 | I32 | F32 => 5, + B64 | I64 | F64 => 6, + _ => 0, + } + } + /// Get the number of bits in a lane. pub fn lane_bits(self) -> u8 { match self.lane_type() {