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.
This commit is contained in:
Jakob Stoklund Olesen
2016-09-27 13:31:31 -07:00
parent b06668aa8a
commit d45b011fa2
4 changed files with 240 additions and 93 deletions

View File

@@ -5,20 +5,127 @@ Cretonne instructions and instruction transformations can be specified to be
polymorphic by using type variables. polymorphic by using type variables.
""" """
from __future__ import absolute_import from __future__ import absolute_import
from collections import namedtuple import math
from . import value from . import value
#: A `TypeSet` represents a set of types. We don't allow arbitrary subsets of
#: types, but use a parametrized approach instead. MAX_LANES = 256
#: This is represented as a named tuple so it can be used as a dictionary key. MAX_BITS = 64
TypeSet = namedtuple(
'TypeSet', [
'allow_scalars', def is_power_of_two(x):
'allow_simd', return x > 0 and x & (x-1) == 0
'base',
'all_ints',
'all_floats', def int_log2(x):
'all_bools']) 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): class TypeVar(object):
@@ -33,37 +140,45 @@ class TypeVar(object):
:param name: Short name of type variable used in instruction descriptions. :param name: Short name of type variable used in instruction descriptions.
:param doc: Documentation string. :param doc: Documentation string.
:param base: Single base type or list of base types. Use this to specify an :param ints: Allow all integer base types, or `(min, max)` bit-range.
exact set of base types if the general categories below are not good :param floats: Allow all floating point base types, or `(min, max)`
enough. bit-range.
:param ints: Allow all integer base types. :param bools: Allow all boolean base types, or `(min, max)` bit-range.
:param floats: Allow all floating point base types.
:param bools: Allow all boolean base types.
:param scalars: Allow type variable to assume scalar types. :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__( def __init__(
self, name, doc, base=None, self, name, doc,
ints=False, floats=False, bools=False, ints=False, floats=False, bools=False,
scalars=True, simd=False, scalars=True, simd=False,
derived_func=None): base=None, derived_func=None):
self.name = name self.name = name
self.__doc__ = doc self.__doc__ = doc
self.base = base
self.is_derived = isinstance(base, TypeVar) self.is_derived = isinstance(base, TypeVar)
if self.is_derived: if base:
assert self.is_derived
assert derived_func assert derived_func
self.base = base
self.derived_func = derived_func self.derived_func = derived_func
self.name = '{}({})'.format(derived_func, base.name) self.name = '{}({})'.format(derived_func, base.name)
else: 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( self.type_set = TypeSet(
allow_scalars=scalars, lanes=(min_lanes, max_lanes),
allow_simd=simd, ints=ints,
base=base, floats=floats,
all_ints=ints, bools=bools)
all_floats=floats,
all_bools=bools)
def __str__(self): def __str__(self):
return "`{}`".format(self.name) return "`{}`".format(self.name)
@@ -92,7 +207,7 @@ class TypeVar(object):
return value return value
def free_typevar(self): def free_typevar(self):
if isinstance(self.base, TypeVar): if self.is_derived:
return self.base return self.base
else: else:
return self return self

View File

@@ -344,15 +344,7 @@ def gen_type_constraints(fmt, instrs):
.format(len(type_sets.table)), '];'): .format(len(type_sets.table)), '];'):
for ts in type_sets.table: for ts in type_sets.table:
with fmt.indented('ValueTypeSet {', '},'): with fmt.indented('ValueTypeSet {', '},'):
if ts.base: ts.emit_fields(fmt)
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.') fmt.comment('Table of operand constraint sequences.')
with fmt.indented( with fmt.indented(

View File

@@ -480,13 +480,14 @@ impl OpcodeConstraints {
/// A value type set describes the permitted set of types for a type variable. /// A value type set describes the permitted set of types for a type variable.
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct ValueTypeSet { pub struct ValueTypeSet {
allow_scalars: bool, min_lanes: u8,
allow_simd: bool, max_lanes: u8,
min_int: u8,
base: Type, max_int: u8,
all_ints: bool, min_float: u8,
all_floats: bool, max_float: u8,
all_bools: bool, min_bool: u8,
max_bool: u8,
} }
impl ValueTypeSet { 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. /// 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 { fn is_base_type(&self, scalar: Type) -> bool {
scalar == self.base || (self.all_ints && scalar.is_int()) || let l2b = scalar.log2_lane_bits();
(self.all_floats && scalar.is_float()) || (self.all_bools && scalar.is_bool()) 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? /// Does `typ` belong to this set?
pub fn contains(&self, typ: Type) -> bool { pub fn contains(&self, typ: Type) -> bool {
let allowed = if typ.is_scalar() { let l2l = typ.log2_lane_count();
self.allow_scalars self.min_lanes <= l2l && l2l < self.max_lanes && self.is_base_type(typ.lane_type())
} else {
self.allow_simd
};
allowed && self.is_base_type(typ.lane_type())
} }
/// Get an example member of this type set. /// Get an example member of this type set.
/// ///
/// This is used for error messages to avoid suggesting invalid types. /// This is used for error messages to avoid suggesting invalid types.
pub fn example(&self) -> Type { pub fn example(&self) -> Type {
if self.base != types::VOID { let t = if self.max_int > 5 {
return self.base;
}
let t = if self.all_ints {
types::I32 types::I32
} else if self.all_floats { } else if self.max_float > 5 {
types::F32 types::F32
} else if self.allow_scalars { } else if self.max_bool > 5 {
types::B1
} else {
types::B32 types::B32
};
if self.allow_scalars {
t
} else { } else {
t.by(4).unwrap() types::B1
} };
t.by(1 << self.min_lanes).unwrap()
} }
} }
@@ -611,43 +608,74 @@ mod tests {
use ir::types::*; use ir::types::*;
let vts = ValueTypeSet { let vts = ValueTypeSet {
allow_scalars: true, min_lanes: 0,
allow_simd: true, max_lanes: 8,
base: VOID, min_int: 3,
all_ints: true, max_int: 7,
all_floats: false, min_float: 0,
all_bools: true, 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"); assert_eq!(vts.example().to_string(), "i32");
let vts = ValueTypeSet { let vts = ValueTypeSet {
allow_scalars: true, min_lanes: 0,
allow_simd: true, max_lanes: 8,
base: VOID, min_int: 0,
all_ints: false, max_int: 0,
all_floats: true, min_float: 5,
all_bools: true, max_float: 7,
min_bool: 3,
max_bool: 7,
}; };
assert_eq!(vts.example().to_string(), "f32"); assert_eq!(vts.example().to_string(), "f32");
let vts = ValueTypeSet { let vts = ValueTypeSet {
allow_scalars: false, min_lanes: 1,
allow_simd: true, max_lanes: 8,
base: VOID, min_int: 0,
all_ints: false, max_int: 0,
all_floats: true, min_float: 5,
all_bools: true, 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 { let vts = ValueTypeSet {
allow_scalars: false, min_lanes: 2,
allow_simd: true, max_lanes: 8,
base: VOID, min_int: 0,
all_ints: false, max_int: 0,
all_floats: false, min_float: 0,
all_bools: true, max_float: 0,
min_bool: 3,
max_bool: 7,
}; };
assert!(!vts.contains(B32X2));
assert!(vts.contains(B32X4));
assert_eq!(vts.example().to_string(), "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));
} }
} }

View File

@@ -43,6 +43,18 @@ impl Type {
Type(self.0 & 0x0f) 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. /// Get the number of bits in a lane.
pub fn lane_bits(self) -> u8 { pub fn lane_bits(self) -> u8 {
match self.lane_type() { match self.lane_type() {