The List and Dict types are no longer implicitly available. They must be imported from typing. Type annotations must appear before the doc comment in a function. Also fix type errors in these functions that weren't detected before.
407 lines
14 KiB
Python
407 lines
14 KiB
Python
"""
|
|
Abstract syntax trees.
|
|
|
|
This module defines classes that can be used to create abstract syntax trees
|
|
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, Sequence # noqa
|
|
except ImportError:
|
|
pass
|
|
|
|
|
|
class Def(object):
|
|
"""
|
|
An AST definition associates a set of variables with the values produced by
|
|
an expression.
|
|
|
|
Example:
|
|
|
|
>>> from base.instructions import iadd_cout, iconst
|
|
>>> x = Var('x')
|
|
>>> y = Var('y')
|
|
>>> x << iconst(4)
|
|
(Var(x),) << Apply(iconst, (4,))
|
|
>>> (x, y) << iadd_cout(4, 5)
|
|
(Var(x), Var(y)) << Apply(iadd_cout, (4, 5))
|
|
|
|
The `<<` operator is used to create variable definitions.
|
|
|
|
:param defs: Single variable or tuple of variables to be defined.
|
|
:param expr: Expression generating the values.
|
|
"""
|
|
|
|
def __init__(self, defs, expr):
|
|
# type: (Union[Var, Tuple[Var, ...]], Apply) -> None
|
|
if not isinstance(defs, tuple):
|
|
self.defs = (defs,) # type: Tuple[Var, ...]
|
|
else:
|
|
self.defs = defs
|
|
assert isinstance(expr, Apply)
|
|
self.expr = expr
|
|
|
|
def __repr__(self):
|
|
return "{} << {!r}".format(self.defs, self.expr)
|
|
|
|
def __str__(self):
|
|
if len(self.defs) == 1:
|
|
return "{!s} << {!s}".format(self.defs[0], self.expr)
|
|
else:
|
|
return "({}) << {!s}".format(
|
|
', '.join(map(str, self.defs)), self.expr)
|
|
|
|
|
|
class Expr(object):
|
|
"""
|
|
An AST expression.
|
|
"""
|
|
|
|
|
|
class Var(Expr):
|
|
"""
|
|
A free variable.
|
|
|
|
When variables are used in `XForms` with source and destination patterns,
|
|
they are classified as follows:
|
|
|
|
Input values
|
|
Uses in the source pattern with no preceding def. These may appear as
|
|
inputs in the destination pattern too, but no new inputs can be
|
|
introduced.
|
|
Output values
|
|
Variables that are defined in both the source and destination pattern.
|
|
These values may have uses outside the source pattern, and the
|
|
destination pattern must compute the same value.
|
|
Intermediate values
|
|
Values that are defined in the source pattern, but not in the
|
|
destination pattern. These may have uses outside the source pattern, so
|
|
the defining instruction can't be deleted immediately.
|
|
Temporary values
|
|
Values that are defined only in the destination pattern.
|
|
"""
|
|
|
|
def __init__(self, name):
|
|
# type: (str) -> None
|
|
self.name = name
|
|
# The `Def` defining this variable in a source pattern.
|
|
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
|
|
return self.name
|
|
|
|
def __repr__(self):
|
|
# type: () -> str
|
|
s = self.name
|
|
if self.src_def:
|
|
s += ", src"
|
|
if self.dst_def:
|
|
s += ", dst"
|
|
return "Var({})".format(s)
|
|
|
|
# Context bits for `set_def` indicating which pattern has defines of this
|
|
# var.
|
|
SRCCTX = 1
|
|
DSTCTX = 2
|
|
|
|
def set_def(self, context, d):
|
|
# type: (int, Def) -> None
|
|
"""
|
|
Set the `Def` that defines this variable in the given context.
|
|
|
|
The `context` must be one of `SRCCTX` or `DSTCTX`
|
|
"""
|
|
if context == self.SRCCTX:
|
|
self.src_def = d
|
|
else:
|
|
self.dst_def = d
|
|
|
|
def get_def(self, context):
|
|
# type: (int) -> Def
|
|
"""
|
|
Get the def of this variable in context.
|
|
|
|
The `context` must be one of `SRCCTX` or `DSTCTX`
|
|
"""
|
|
if context == self.SRCCTX:
|
|
return self.src_def
|
|
else:
|
|
return self.dst_def
|
|
|
|
def is_input(self):
|
|
# type: () -> bool
|
|
"""Is this an input value to the src pattern?"""
|
|
return self.src_def is None and self.dst_def is None
|
|
|
|
def is_output(self):
|
|
# type: () -> bool
|
|
"""Is this an output value, defined in both src and dst patterns?"""
|
|
return self.src_def is not None and self.dst_def is not None
|
|
|
|
def is_intermediate(self):
|
|
# type: () -> bool
|
|
"""Is this an intermediate value, defined only in the src pattern?"""
|
|
return self.src_def is not None and self.dst_def is None
|
|
|
|
def is_temp(self):
|
|
# type: () -> bool
|
|
"""Is this a temp value, defined only in the dst pattern?"""
|
|
return self.src_def is None and self.dst_def is not None
|
|
|
|
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 rust_type(self):
|
|
# type: () -> str
|
|
"""
|
|
Get a Rust expression that computes the type of this variable.
|
|
|
|
It is assumed that local variables exist corresponding to the free type
|
|
variables.
|
|
"""
|
|
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):
|
|
"""
|
|
Apply an instruction to arguments.
|
|
|
|
An `Apply` AST expression is created by using function call syntax on
|
|
instructions. This applies to both bound and unbound polymorphic
|
|
instructions:
|
|
|
|
>>> from base.instructions import jump, iadd
|
|
>>> jump('next', ())
|
|
Apply(jump, ('next', ()))
|
|
>>> iadd.i32('x', 'y')
|
|
Apply(iadd.i32, ('x', 'y'))
|
|
|
|
:param inst: The instruction being applied, an `Instruction` or
|
|
`BoundInstruction` instance.
|
|
:param args: Tuple of arguments.
|
|
"""
|
|
|
|
def __init__(self, inst, args):
|
|
# type: (instructions.MaybeBoundInst, Tuple[Expr, ...]) -> None # noqa
|
|
if isinstance(inst, instructions.BoundInstruction):
|
|
self.inst = inst.inst
|
|
self.typevars = inst.typevars
|
|
else:
|
|
assert isinstance(inst, instructions.Instruction)
|
|
self.inst = inst
|
|
self.typevars = ()
|
|
self.args = args
|
|
assert len(self.inst.ins) == len(args)
|
|
|
|
def __rlshift__(self, other):
|
|
# type: (Union[Var, Tuple[Var, ...]]) -> Def
|
|
"""
|
|
Define variables using `var << expr` or `(v1, v2) << expr`.
|
|
"""
|
|
return Def(other, self)
|
|
|
|
def instname(self):
|
|
i = self.inst.name
|
|
for t in self.typevars:
|
|
i += '.{}'.format(t)
|
|
return i
|
|
|
|
def __repr__(self):
|
|
return "Apply({}, {})".format(self.instname(), self.args)
|
|
|
|
def __str__(self):
|
|
args = ', '.join(map(str, self.args))
|
|
return '{}({})'.format(self.instname(), args)
|
|
|
|
def rust_builder(self, defs=None):
|
|
# type: (Sequence[Var]) -> str
|
|
"""
|
|
Return a Rust Builder method call for instantiating this instruction
|
|
application.
|
|
|
|
The `defs` argument should be a list of variables defined by this
|
|
instruction. It is used to construct a result type if necessary.
|
|
"""
|
|
args = ', '.join(map(str, self.args))
|
|
# Do we need to pass an explicit type argument?
|
|
if self.inst.is_polymorphic and not self.inst.use_typevar_operand:
|
|
args = defs[0].rust_type() + ', ' + args
|
|
method = self.inst.snake_name()
|
|
return '{}({})'.format(method, args)
|