Move ast, xform, and legalize modules.
- cdsl.ast defines classes representing abstract syntax trees. - cdsl.xform defines classes for instruction transformations. - base.legalize defines legalization patterns.
This commit is contained in:
216
lib/cretonne/meta/cdsl/ast.py
Normal file
216
lib/cretonne/meta/cdsl/ast.py
Normal file
@@ -0,0 +1,216 @@
|
||||
"""
|
||||
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
|
||||
|
||||
try:
|
||||
from typing import Union, Tuple # 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
|
||||
|
||||
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 not self.src_def and not self.dst_def
|
||||
|
||||
def is_output(self):
|
||||
"""Is this an output value, defined in both src and dst patterns?"""
|
||||
# type: () -> bool
|
||||
return self.src_def and self.dst_def
|
||||
|
||||
def is_intermediate(self):
|
||||
"""Is this an intermediate value, defined only in the src pattern?"""
|
||||
# type: () -> bool
|
||||
return self.src_def and not self.dst_def
|
||||
|
||||
def is_temp(self):
|
||||
"""Is this a temp value, defined only in the dst pattern?"""
|
||||
# type: () -> bool
|
||||
return not self.src_def and self.dst_def
|
||||
|
||||
|
||||
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):
|
||||
# type: () -> str
|
||||
"""
|
||||
Return a Rust Builder method call for instantiating this instruction
|
||||
application.
|
||||
"""
|
||||
args = ', '.join(map(str, self.args))
|
||||
method = self.inst.snake_name()
|
||||
return '{}({})'.format(method, args)
|
||||
@@ -259,7 +259,7 @@ class Instruction(object):
|
||||
Create an `ast.Apply` AST node representing the application of this
|
||||
instruction to the arguments.
|
||||
"""
|
||||
from cretonne.ast import Apply
|
||||
from .ast import Apply
|
||||
return Apply(self, args)
|
||||
|
||||
|
||||
@@ -312,5 +312,5 @@ class BoundInstruction(object):
|
||||
Create an `ast.Apply` AST node representing the application of this
|
||||
instruction to the arguments.
|
||||
"""
|
||||
from cretonne.ast import Apply
|
||||
from .ast import Apply
|
||||
return Apply(self, args)
|
||||
|
||||
28
lib/cretonne/meta/cdsl/test_ast.py
Normal file
28
lib/cretonne/meta/cdsl/test_ast.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from __future__ import absolute_import
|
||||
from unittest import TestCase
|
||||
from doctest import DocTestSuite
|
||||
from . import ast
|
||||
from base.instructions import jump, iadd
|
||||
|
||||
|
||||
def load_tests(loader, tests, ignore):
|
||||
tests.addTests(DocTestSuite(ast))
|
||||
return tests
|
||||
|
||||
|
||||
x = 'x'
|
||||
y = 'y'
|
||||
a = 'a'
|
||||
|
||||
|
||||
class TestPatterns(TestCase):
|
||||
def test_apply(self):
|
||||
i = jump(x, y)
|
||||
self.assertEqual(repr(i), "Apply(jump, ('x', 'y'))")
|
||||
|
||||
i = iadd.i32(x, y)
|
||||
self.assertEqual(repr(i), "Apply(iadd.i32, ('x', 'y'))")
|
||||
|
||||
def test_single_ins(self):
|
||||
pat = a << iadd.i32(x, y)
|
||||
self.assertEqual(repr(pat), "('a',) << Apply(iadd.i32, ('x', 'y'))")
|
||||
59
lib/cretonne/meta/cdsl/test_xform.py
Normal file
59
lib/cretonne/meta/cdsl/test_xform.py
Normal file
@@ -0,0 +1,59 @@
|
||||
from __future__ import absolute_import
|
||||
from unittest import TestCase
|
||||
from doctest import DocTestSuite
|
||||
from base.instructions import iadd, iadd_imm, iconst
|
||||
from . import xform
|
||||
from .ast import Var
|
||||
from .xform import Rtl, XForm
|
||||
|
||||
|
||||
def load_tests(loader, tests, ignore):
|
||||
tests.addTests(DocTestSuite(xform))
|
||||
return tests
|
||||
|
||||
|
||||
x = Var('x')
|
||||
y = Var('y')
|
||||
a = Var('a')
|
||||
c = Var('c')
|
||||
|
||||
|
||||
class TestXForm(TestCase):
|
||||
def test_macro_pattern(self):
|
||||
src = Rtl(a << iadd_imm(x, y))
|
||||
dst = Rtl(
|
||||
c << iconst(y),
|
||||
a << iadd(x, c))
|
||||
XForm(src, dst)
|
||||
|
||||
def test_def_input(self):
|
||||
# Src pattern has a def which is an input in dst.
|
||||
src = Rtl(a << iadd_imm(x, 1))
|
||||
dst = Rtl(y << iadd_imm(a, 1))
|
||||
with self.assertRaisesRegexp(
|
||||
AssertionError,
|
||||
"'a' used as both input and def"):
|
||||
XForm(src, dst)
|
||||
|
||||
def test_input_def(self):
|
||||
# Converse of the above.
|
||||
src = Rtl(y << iadd_imm(a, 1))
|
||||
dst = Rtl(a << iadd_imm(x, 1))
|
||||
with self.assertRaisesRegexp(
|
||||
AssertionError,
|
||||
"'a' used as both input and def"):
|
||||
XForm(src, dst)
|
||||
|
||||
def test_extra_input(self):
|
||||
src = Rtl(a << iadd_imm(x, 1))
|
||||
dst = Rtl(a << iadd(x, y))
|
||||
with self.assertRaisesRegexp(AssertionError, "extra inputs in dst"):
|
||||
XForm(src, dst)
|
||||
|
||||
def test_double_def(self):
|
||||
src = Rtl(
|
||||
a << iadd_imm(x, 1),
|
||||
a << iadd(x, y))
|
||||
dst = Rtl(a << iadd(x, y))
|
||||
with self.assertRaisesRegexp(AssertionError, "'a' multiply defined"):
|
||||
XForm(src, dst)
|
||||
226
lib/cretonne/meta/cdsl/xform.py
Normal file
226
lib/cretonne/meta/cdsl/xform.py
Normal file
@@ -0,0 +1,226 @@
|
||||
"""
|
||||
Instruction transformations.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from .ast import Def, Var, Apply
|
||||
|
||||
try:
|
||||
from typing import Union, Iterator, Sequence, Iterable # noqa
|
||||
from .ast import Expr # noqa
|
||||
DefApply = Union[Def, Apply]
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def canonicalize_defapply(node):
|
||||
# type: (DefApply) -> Def
|
||||
"""
|
||||
Canonicalize a `Def` or `Apply` node into a `Def`.
|
||||
|
||||
An `Apply` becomes a `Def` with an empty list of defs.
|
||||
"""
|
||||
if isinstance(node, Apply):
|
||||
return Def((), node)
|
||||
else:
|
||||
return node
|
||||
|
||||
|
||||
class Rtl(object):
|
||||
"""
|
||||
Register Transfer Language list.
|
||||
|
||||
An RTL object contains a list of register assignments in the form of `Def`
|
||||
objects.
|
||||
|
||||
An RTL list can represent both a source pattern to be matched, or a
|
||||
destination pattern to be inserted.
|
||||
"""
|
||||
|
||||
def __init__(self, *args):
|
||||
# type: (*DefApply) -> None
|
||||
self.rtl = tuple(map(canonicalize_defapply, args))
|
||||
|
||||
def __iter__(self):
|
||||
# type: () -> Iterator[Def]
|
||||
return iter(self.rtl)
|
||||
|
||||
|
||||
class XForm(object):
|
||||
"""
|
||||
An instruction transformation consists of a source and destination pattern.
|
||||
|
||||
Patterns are expressed in *register transfer language* as tuples of
|
||||
`ast.Def` or `ast.Expr` nodes.
|
||||
|
||||
A legalization pattern must have a source pattern containing only a single
|
||||
instruction.
|
||||
|
||||
>>> from base.instructions import iconst, iadd, iadd_imm
|
||||
>>> a = Var('a')
|
||||
>>> c = Var('c')
|
||||
>>> v = Var('v')
|
||||
>>> x = Var('x')
|
||||
>>> XForm(
|
||||
... Rtl(c << iconst(v),
|
||||
... a << iadd(x, c)),
|
||||
... Rtl(a << iadd_imm(x, v)))
|
||||
XForm(inputs=[Var(v), Var(x)], defs=[Var(c, src), Var(a, src, dst)],
|
||||
c << iconst(v)
|
||||
a << iadd(x, c)
|
||||
=>
|
||||
a << iadd_imm(x, v)
|
||||
)
|
||||
"""
|
||||
|
||||
def __init__(self, src, dst):
|
||||
# type: (Rtl, Rtl) -> None
|
||||
self.src = src
|
||||
self.dst = dst
|
||||
# Variables that are inputs to the source pattern.
|
||||
self.inputs = list() # type: List[Var]
|
||||
# Variables defined in either src or dst.
|
||||
self.defs = list() # type: List[Var]
|
||||
|
||||
# Rewrite variables in src and dst RTL lists to our own copies.
|
||||
# Map name -> private Var.
|
||||
symtab = dict() # type: Dict[str, Var]
|
||||
self._rewrite_rtl(src, symtab, Var.SRCCTX)
|
||||
num_src_inputs = len(self.inputs)
|
||||
self._rewrite_rtl(dst, symtab, Var.DSTCTX)
|
||||
|
||||
# Check for inconsistently used inputs.
|
||||
for i in self.inputs:
|
||||
if not i.is_input():
|
||||
raise AssertionError(
|
||||
"'{}' used as both input and def".format(i))
|
||||
|
||||
# Check for spurious inputs in dst.
|
||||
if len(self.inputs) > num_src_inputs:
|
||||
raise AssertionError(
|
||||
"extra inputs in dst RTL: {}".format(
|
||||
self.inputs[num_src_inputs:]))
|
||||
|
||||
def __repr__(self):
|
||||
s = "XForm(inputs={}, defs={},\n ".format(self.inputs, self.defs)
|
||||
s += '\n '.join(str(n) for n in self.src)
|
||||
s += '\n=>\n '
|
||||
s += '\n '.join(str(n) for n in self.dst)
|
||||
s += '\n)'
|
||||
return s
|
||||
|
||||
def _rewrite_rtl(self, rtl, symtab, context):
|
||||
# type: (Rtl, Dict[str, Var], int) -> None
|
||||
for line in rtl.rtl:
|
||||
if isinstance(line, Def):
|
||||
line.defs = tuple(
|
||||
self._rewrite_defs(line, symtab, context))
|
||||
expr = line.expr
|
||||
else:
|
||||
expr = line
|
||||
self._rewrite_expr(expr, symtab, context)
|
||||
|
||||
def _rewrite_expr(self, expr, symtab, context):
|
||||
# type: (Apply, Dict[str, Var], int) -> None
|
||||
"""
|
||||
Find all uses of variables in `expr` and replace them with our own
|
||||
local symbols.
|
||||
"""
|
||||
|
||||
# Accept a whole expression tree.
|
||||
stack = [expr]
|
||||
while len(stack) > 0:
|
||||
expr = stack.pop()
|
||||
expr.args = tuple(
|
||||
self._rewrite_uses(expr, stack, symtab, context))
|
||||
|
||||
def _rewrite_defs(self, line, symtab, context):
|
||||
# type: (Def, Dict[str, Var], int) -> Iterable[Var]
|
||||
"""
|
||||
Given a tuple of symbols defined in a Def, rewrite them to local
|
||||
symbols. Yield the new locals.
|
||||
"""
|
||||
for sym in line.defs:
|
||||
name = str(sym)
|
||||
if name in symtab:
|
||||
var = symtab[name]
|
||||
if var.get_def(context):
|
||||
raise AssertionError("'{}' multiply defined".format(name))
|
||||
else:
|
||||
var = Var(name)
|
||||
symtab[name] = var
|
||||
self.defs.append(var)
|
||||
var.set_def(context, line)
|
||||
yield var
|
||||
|
||||
def _rewrite_uses(self, expr, stack, symtab, context):
|
||||
# type: (Apply, List[Apply], Dict[str, Var], int) -> Iterable[Expr]
|
||||
"""
|
||||
Given an `Apply` expr, rewrite all uses in its arguments to local
|
||||
variables. Yield a sequence of new arguments.
|
||||
|
||||
Append any `Apply` arguments to `stack`.
|
||||
"""
|
||||
for arg, operand in zip(expr.args, expr.inst.ins):
|
||||
# Nested instructions are allowed. Visit recursively.
|
||||
if isinstance(arg, Apply):
|
||||
stack.append(arg)
|
||||
yield arg
|
||||
continue
|
||||
if not isinstance(arg, Var):
|
||||
assert not operand.is_value(), "Value arg must be `Var`"
|
||||
yield arg
|
||||
continue
|
||||
# This is supposed to be a symbolic value reference.
|
||||
name = str(arg)
|
||||
if name in symtab:
|
||||
var = symtab[name]
|
||||
# The variable must be used consistently as a def or input.
|
||||
if not var.is_input() and not var.get_def(context):
|
||||
raise AssertionError(
|
||||
"'{}' used as both input and def"
|
||||
.format(name))
|
||||
else:
|
||||
# First time use of variable.
|
||||
var = Var(name)
|
||||
symtab[name] = var
|
||||
self.inputs.append(var)
|
||||
yield var
|
||||
|
||||
def verify_legalize(self):
|
||||
# type: () -> None
|
||||
"""
|
||||
Verify that this is a valid legalization XForm.
|
||||
|
||||
- The source pattern must describe a single instruction.
|
||||
- All values defined in the output pattern must be defined in the
|
||||
destination pattern.
|
||||
"""
|
||||
assert len(self.src.rtl) == 1, "Legalize needs single instruction."
|
||||
for d in self.src.rtl[0].defs:
|
||||
if not d.is_output():
|
||||
raise AssertionError(
|
||||
'{} not defined in dest pattern'.format(d))
|
||||
|
||||
|
||||
class XFormGroup(object):
|
||||
"""
|
||||
A group of related transformations.
|
||||
"""
|
||||
|
||||
def __init__(self, name, doc):
|
||||
# type: (str, str) -> None
|
||||
self.xforms = list() # type: List[XForm]
|
||||
self.name = name
|
||||
self.__doc__ = doc
|
||||
|
||||
def legalize(self, src, dst):
|
||||
# type: (Union[Def, Apply], Rtl) -> None
|
||||
"""
|
||||
Add a legalization pattern to this group.
|
||||
|
||||
:param src: Single `Def` or `Apply` to be legalized.
|
||||
:param dst: `Rtl` list of replacement instructions.
|
||||
"""
|
||||
xform = XForm(Rtl(src), dst)
|
||||
xform.verify_legalize()
|
||||
self.xforms.append(xform)
|
||||
Reference in New Issue
Block a user