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:
@@ -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
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
Reference in New Issue
Block a user