diff --git a/lib/cretonne/meta/cdsl/ast.py b/lib/cretonne/meta/cdsl/ast.py index 853437e4e5..d8b3b7b875 100644 --- a/lib/cretonne/meta/cdsl/ast.py +++ b/lib/cretonne/meta/cdsl/ast.py @@ -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): """ diff --git a/lib/cretonne/meta/cdsl/typevar.py b/lib/cretonne/meta/cdsl/typevar.py index e5bf8f42a3..30986c5db8 100644 --- a/lib/cretonne/meta/cdsl/typevar.py +++ b/lib/cretonne/meta/cdsl/typevar.py @@ -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' diff --git a/lib/cretonne/meta/cdsl/xform.py b/lib/cretonne/meta/cdsl/xform.py index 164747fac9..ee6710a79f 100644 --- a/lib/cretonne/meta/cdsl/xform.py +++ b/lib/cretonne/meta/cdsl/xform.py @@ -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): """