Infer type constraints on patterns.
Each instruction used in a pattern has constraints on the types of its operands. These constraints are expressed as symbolic type variables. Compute type variables for each variable used in a transformation pattern. Some are free type variables, and some are derived from the free type variables. The type variables associated with variables can be used for computing the result types of replacement instructions that don't support simple forward type inference from their inputs. The type sets computed by this patch are conservatively too large, so they can't yet be used to type check patterns.
This commit is contained in:
@@ -6,6 +6,7 @@ for patern matching an rewriting of cretonne instructions.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from . import instructions
|
||||
from .typevar import TypeVar
|
||||
|
||||
try:
|
||||
from typing import Union, Tuple # noqa
|
||||
@@ -90,6 +91,12 @@ class Var(Expr):
|
||||
self.src_def = None # type: Def
|
||||
# The `Def` defining this variable in a destination pattern.
|
||||
self.dst_def = None # type: Def
|
||||
# TypeVar representing the type of this variable.
|
||||
self.typevar = None # type: TypeVar
|
||||
# The original 'typeof(x)' type variable that was created for this Var.
|
||||
# This one doesn't change. `self.typevar` above may be joined with
|
||||
# other typevars.
|
||||
self.original_typevar = None # type: TypeVar
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
@@ -153,6 +160,173 @@ class Var(Expr):
|
||||
# type: () -> bool
|
||||
return not self.src_def and self.dst_def
|
||||
|
||||
def get_typevar(self):
|
||||
# type: () -> TypeVar
|
||||
"""Get the type variable representing the type of this variable."""
|
||||
if not self.typevar:
|
||||
# Create a TypeVar allowing all types.
|
||||
tv = TypeVar(
|
||||
'typeof_{}'.format(self),
|
||||
'Type of the pattern variable `{}`'.format(self),
|
||||
ints=True, floats=True, bools=True,
|
||||
scalars=True, simd=True)
|
||||
self.original_typevar = tv
|
||||
self.typevar = tv
|
||||
return self.typevar
|
||||
|
||||
def link_typevar(self, base, derived_func):
|
||||
# type: (TypeVar, str) -> None
|
||||
"""
|
||||
Link the type variable on this Var to the type variable `base` using
|
||||
`derived_func`.
|
||||
"""
|
||||
self.original_typevar = None
|
||||
self.typevar.change_to_derived(base, derived_func)
|
||||
# Possibly eliminate redundant SAMEAS links.
|
||||
self.typevar = self.typevar.strip_sameas()
|
||||
|
||||
def has_free_typevar(self):
|
||||
# type: () -> bool
|
||||
"""
|
||||
Check if this variable has a free type variable.
|
||||
|
||||
If not, the type of this variable is computed from the type of another
|
||||
variable.
|
||||
"""
|
||||
if not self.typevar or self.typevar.is_derived:
|
||||
return False
|
||||
return self.typevar is self.original_typevar
|
||||
|
||||
def constrain_typevar(self, sym_typevar, sym_ctrl, ctrl_var):
|
||||
# type: (TypeVar, TypeVar, Var) -> None
|
||||
"""
|
||||
Constrain the set of allowed types for this variable.
|
||||
|
||||
Merge type variables for the involved variables to minimize the set for
|
||||
free type variables.
|
||||
|
||||
Suppose we're looking at an instruction defined like this:
|
||||
|
||||
c = Operand('c', TxN.as_bool())
|
||||
x = Operand('x', TxN)
|
||||
y = Operand('y', TxN)
|
||||
a = Operand('a', TxN)
|
||||
vselect = Instruction('vselect', ins=(c, x, y), outs=a)
|
||||
|
||||
And suppose the instruction is used in a pattern like this:
|
||||
|
||||
v0 << vselect(v1, v2, v3)
|
||||
|
||||
We want to reconcile the types of the variables v0-v3 with the
|
||||
constraints from the definition of vselect. This means that v0, v2, and
|
||||
v3 must all have the same type, and v1 must have the type
|
||||
`typeof(v2).as_bool()`.
|
||||
|
||||
The types are reconciled by calling this function once for each
|
||||
input/output operand on the instruction in the pattern with these
|
||||
arguments.
|
||||
|
||||
:param sym_typevar: Symbolic type variable constraining this variable
|
||||
in the definition of the instruction.
|
||||
:param sym_ctrl: Controlling type variable of `sym_typevar` in the
|
||||
definition of the instruction.
|
||||
:param ctrl_var: Variable determining the type of `sym_ctrl`.
|
||||
|
||||
When processing `v1` as used in the pattern above, we would get:
|
||||
|
||||
- self: v1
|
||||
- sym_typevar: TxN.as_bool()
|
||||
- sym_ctrl: TxN
|
||||
- ctrl_var: v2
|
||||
|
||||
Here, 'v2' represents the controlling variable because of how the
|
||||
`Ternary` instruction format is defined with `typevar_operand=1`.
|
||||
"""
|
||||
# First check if sym_typevar is tied to the controlling type variable
|
||||
# in the instruction definition. We also allow free type variables on
|
||||
# instruction inputs that can't be tied to anything else.
|
||||
#
|
||||
# This also covers non-polymorphic instructions and other cases where
|
||||
# we don't have a Var representing the controlling type variable.
|
||||
sym_free_var = sym_typevar.free_typevar()
|
||||
if not sym_free_var or sym_free_var is not sym_ctrl or not ctrl_var:
|
||||
# Just constrain our type to be compatible with the required
|
||||
# typeset.
|
||||
self.get_typevar().constrain_types(sym_typevar)
|
||||
return
|
||||
|
||||
# Now sym_typevar is known to be tied to (or identical to) the
|
||||
# controlling type variable.
|
||||
|
||||
if not self.typevar:
|
||||
# If this variable is not yet constrained, just infer its type and
|
||||
# link it to the controlling type variable.
|
||||
if not sym_typevar.is_derived:
|
||||
assert sym_typevar is sym_ctrl
|
||||
# Identity mapping.
|
||||
# Note that `self == ctrl_var` is both possible and common.
|
||||
self.typevar = ctrl_var.get_typevar()
|
||||
else:
|
||||
assert self is not ctrl_var, (
|
||||
'Impossible type constraints for {}: {}'
|
||||
.format(self, sym_typevar))
|
||||
# Create a derived type variable identical to sym_typevar, but
|
||||
# with a different base.
|
||||
self.typevar = TypeVar.derived(
|
||||
ctrl_var.get_typevar(),
|
||||
sym_typevar.derived_func)
|
||||
# Match the type set constraints of the instruction.
|
||||
self.typevar.constrain_types(sym_typevar)
|
||||
return
|
||||
|
||||
# We already have a self.typevar describing our constraints. We need to
|
||||
# reconcile with the additional constraints.
|
||||
|
||||
# It's likely that ctrl_var and self already share a type
|
||||
# variable. (Often because `ctrl_var == self`).
|
||||
if ctrl_var.typevar == self.typevar:
|
||||
return
|
||||
|
||||
if not sym_typevar.is_derived:
|
||||
assert sym_typevar is sym_ctrl
|
||||
# sym_typevar is a direct use of sym_ctrl, so we need to reconcile
|
||||
# self with ctrl_var.
|
||||
assert not sym_typevar.is_derived
|
||||
self.typevar.constrain_types(sym_typevar)
|
||||
|
||||
# It's possible that ctrl_var has not yet been assigned a type
|
||||
# variable.
|
||||
if not ctrl_var.typevar:
|
||||
ctrl_var.typevar = self.typevar
|
||||
return
|
||||
|
||||
# We can also bind variables with a free type variable to another
|
||||
# variable. Prefer to do this to temps because they aren't allowed
|
||||
# to be free,
|
||||
if self.is_temp() and self.has_free_typevar():
|
||||
self.link_typevar(ctrl_var.typevar, TypeVar.SAMEAS)
|
||||
return
|
||||
if ctrl_var.is_temp() and ctrl_var.has_free_typevar():
|
||||
ctrl_var.link_typevar(self.typevar, TypeVar.SAMEAS)
|
||||
return
|
||||
if self.has_free_typevar():
|
||||
self.link_typevar(ctrl_var.typevar, TypeVar.SAMEAS)
|
||||
return
|
||||
if ctrl_var.has_free_typevar():
|
||||
ctrl_var.link_typevar(self.typevar, TypeVar.SAMEAS)
|
||||
return
|
||||
|
||||
# TODO: Other cases are harder to handle.
|
||||
#
|
||||
# - If either variable is an independent free type variable, it
|
||||
# should be changed to be linked to the other.
|
||||
# - If both variable are free, we should pick one to link to the
|
||||
# other. In particular, if one is a temp, it should be linked.
|
||||
else:
|
||||
# sym_typevar is derived from sym_ctrl.
|
||||
# TODO: Other cases are harder to handle.
|
||||
pass
|
||||
|
||||
|
||||
class Apply(Expr):
|
||||
"""
|
||||
|
||||
@@ -292,6 +292,25 @@ class TypeVar(object):
|
||||
# type: () -> str
|
||||
return "`{}`".format(self.name)
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
if self.is_derived:
|
||||
return (
|
||||
'TypeVar({}, base={}, derived_func={})'
|
||||
.format(self.name, self.base, self.derived_func))
|
||||
else:
|
||||
return (
|
||||
'TypeVar({}, {})'
|
||||
.format(self.name, self.type_set))
|
||||
|
||||
def __eq__(self, other):
|
||||
if self.is_derived and other.is_derived:
|
||||
return (
|
||||
self.derived_func == other.derived_func and
|
||||
self.base == other.base)
|
||||
else:
|
||||
return self is other
|
||||
|
||||
# Supported functions for derived type variables.
|
||||
SAMEAS = 'SameAs'
|
||||
LANEOF = 'LaneOf'
|
||||
|
||||
@@ -100,6 +100,10 @@ class XForm(object):
|
||||
"extra inputs in dst RTL: {}".format(
|
||||
self.inputs[num_src_inputs:]))
|
||||
|
||||
self._infer_types(self.src)
|
||||
self._infer_types(self.dst)
|
||||
self._collect_typevars()
|
||||
|
||||
def __repr__(self):
|
||||
s = "XForm(inputs={}, defs={},\n ".format(self.inputs, self.defs)
|
||||
s += '\n '.join(str(n) for n in self.src)
|
||||
@@ -201,6 +205,63 @@ class XForm(object):
|
||||
raise AssertionError(
|
||||
'{} not defined in dest pattern'.format(d))
|
||||
|
||||
def _infer_types(self, rtl):
|
||||
# type: (Rtl) -> None
|
||||
"""Assign type variables to all value variables used in `rtl`."""
|
||||
for d in rtl.rtl:
|
||||
inst = d.expr.inst
|
||||
|
||||
# Get the Var corresponding to the controlling type variable.
|
||||
ctrl_var = None # type: Var
|
||||
if inst.is_polymorphic:
|
||||
if inst.use_typevar_operand:
|
||||
# Should this be an assertion instead?
|
||||
# Should all value operands be required to be Vars?
|
||||
arg = d.expr.args[inst.format.typevar_operand]
|
||||
if isinstance(arg, Var):
|
||||
ctrl_var = arg
|
||||
else:
|
||||
ctrl_var = d.defs[inst.value_results[0]]
|
||||
|
||||
# Reconcile arguments with the requirements of `inst`.
|
||||
for opnum in inst.format.value_operands:
|
||||
inst_tv = inst.ins[opnum].typevar
|
||||
v = d.expr.args[opnum]
|
||||
if isinstance(v, Var):
|
||||
v.constrain_typevar(inst_tv, inst.ctrl_typevar, ctrl_var)
|
||||
|
||||
# Reconcile results with the requirements of `inst`.
|
||||
for resnum in inst.value_results:
|
||||
inst_tv = inst.outs[resnum].typevar
|
||||
v = d.defs[resnum]
|
||||
v.constrain_typevar(inst_tv, inst.ctrl_typevar, ctrl_var)
|
||||
|
||||
def _collect_typevars(self):
|
||||
# type: () -> None
|
||||
"""
|
||||
Collect a list of variables whose type can be used to infer the types
|
||||
of all expressions.
|
||||
|
||||
This should be called after `_infer_types()` above has computed type
|
||||
variables for all the used vars.
|
||||
"""
|
||||
fvars = list(v for v in self.inputs if v.has_free_typevar())
|
||||
fvars += list(v for v in self.defs if v.has_free_typevar())
|
||||
self.free_typevars = fvars
|
||||
|
||||
# When substituting a pattern, we know the types of all variables that
|
||||
# appear on the source side: inut, output, and intermediate values.
|
||||
# However, temporary values which appear only on the destination side
|
||||
# must have their type computed somehow.
|
||||
#
|
||||
# Some variables have a fixed type which appears as a type variable
|
||||
# with a singleton_type field set. That's allowed for temps too.
|
||||
for v in fvars:
|
||||
if v.is_temp() and not v.typevar.singleton_type:
|
||||
raise AssertionError(
|
||||
"Cannot determine type of temp '{}' in xform:\n{}"
|
||||
.format(v, self))
|
||||
|
||||
|
||||
class XFormGroup(object):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user