Add better type inference and encapsulate it in its own file (#110)

* Add more rigorous type inference and encapsulate the type inferece code in its own file (ti.py).

Add constraints accumulation during type inference, to represent constraints that cannot be expressed
using bijective derivation functions between typevars.

Add testing for new type inference code.

* Additional annotations to appease mypy
This commit is contained in:
d1m0
2017-07-05 09:16:44 -07:00
committed by Jakob Stoklund Olesen
parent e63c581859
commit e35398842d
6 changed files with 1123 additions and 281 deletions

View File

@@ -100,8 +100,8 @@ class Var(Expr):
# 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.
# This one doesn't change. `self.typevar` above may be changed to
# another typevar by type inference.
self.original_typevar = None # type: TypeVar
def __str__(self):
@@ -180,16 +180,9 @@ class Var(Expr):
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 set_typevar(self, tv):
# type: (TypeVar) -> None
self.typevar = tv
def has_free_typevar(self):
# type: () -> bool
@@ -213,136 +206,6 @@ class Var(Expr):
"""
return self.typevar.rust_expr()
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):
"""