diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index d318b27ef3..a5fe761bce 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -10,6 +10,7 @@ import math import importlib from collections import OrderedDict from .predicates import And +from .ast import Apply camel_re = re.compile('(^|_)([a-z])') @@ -603,6 +604,12 @@ class Operand(object): def __str__(self): return "`{}`".format(self.name) + def is_value(self): + """ + Is this an SSA value operand? + """ + return self.kind is value + class InstructionFormat(object): """ @@ -779,10 +786,13 @@ class Instruction(object): self.format = InstructionFormat.lookup(self.ins, self.outs) # Indexes into outs for value results. Others are `variable_args`. self.value_results = tuple( - i for i, o in enumerate(self.outs) if o.kind is value) + i for i, o in enumerate(self.outs) if o.is_value()) self._verify_polymorphic() InstructionGroup.append(self) + def __str__(self): + return self.name + def _verify_polymorphic(self): """ Check if this instruction is polymorphic, and verify its use of type @@ -910,6 +920,13 @@ class Instruction(object): assert not self.is_polymorphic, self return (self, ()) + def __call__(self, *args): + """ + Create an `ast.Apply` AST node representing the application of this + instruction to the arguments. + """ + return Apply(self, args) + class BoundInstruction(object): """ @@ -951,6 +968,13 @@ class BoundInstruction(object): assert len(self.typevars) == 1 + len(self.inst.other_typevars) return (self.inst, self.typevars) + def __call__(self, *args): + """ + Create an `ast.Apply` AST node representing the application of this + instruction to the arguments. + """ + return Apply(self, args) + # Defining target ISAs. diff --git a/meta/cretonne/ast.py b/meta/cretonne/ast.py new file mode 100644 index 0000000000..8c9c8576cb --- /dev/null +++ b/meta/cretonne/ast.py @@ -0,0 +1,122 @@ +""" +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 + + +class Def(object): + """ + An AST definition associates a set of variables with the values produced by + an expression. + + Example: + + >>> from .base 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): + if not isinstance(defs, tuple): + defs = (defs,) + assert isinstance(expr, Expr) + self.defs = defs + 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(self.defs), self.expr) + + +class Expr(object): + """ + An AST expression. + """ + + def __rlshift__(self, other): + """ + Define variables using `var << expr` or `(v1, v2) << expr`. + """ + return Def(other, self) + + +class Var(Expr): + """ + A free variable. + """ + + def __init__(self, name): + self.name = name + # Bitmask of contexts where this variable is defined. + # See XForm._rewrite_defs(). + self.defctx = 0 + + def __str__(self): + return self.name + + def __repr__(self): + s = self.name + if self.defctx: + s += ", d={:02b}".format(self.defctx) + return "Var({})".format(s) + + +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 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): + from . import BoundInstruction + if isinstance(inst, BoundInstruction): + self.inst = inst.inst + self.typevars = inst.typevars + else: + self.inst = inst + self.typevars = () + self.args = args + assert len(self.inst.ins) == len(args) + + 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) diff --git a/meta/cretonne/test_ast.py b/meta/cretonne/test_ast.py new file mode 100644 index 0000000000..71791db3f6 --- /dev/null +++ b/meta/cretonne/test_ast.py @@ -0,0 +1,28 @@ +from __future__ import absolute_import +from unittest import TestCase +from doctest import DocTestSuite +from . import ast +from .base 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'))") diff --git a/meta/cretonne/test_xform.py b/meta/cretonne/test_xform.py new file mode 100644 index 0000000000..b472a8f458 --- /dev/null +++ b/meta/cretonne/test_xform.py @@ -0,0 +1,59 @@ +from __future__ import absolute_import +from unittest import TestCase +from doctest import DocTestSuite +from . import xform +from .base import iadd, iadd_imm, iconst +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) diff --git a/meta/cretonne/xform.py b/meta/cretonne/xform.py new file mode 100644 index 0000000000..16a9eae870 --- /dev/null +++ b/meta/cretonne/xform.py @@ -0,0 +1,182 @@ +""" +Instruction transformations. +""" +from __future__ import absolute_import +from .ast import Def, Var, Apply + + +SRCCTX = 1 +DSTCTX = 2 + + +class Rtl(object): + """ + Register Transfer Language list. + + An RTL object contains a list of register assignments in the form of `Def` + objects and/or Apply objects for side-effecting instructions. + + An RTL list can represent both a source pattern to be matched, or a + destination pattern to be inserted. + """ + + def __init__(self, *args): + self.rtl = args + + def __iter__(self): + 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 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, d=01), Var(a, d=11)], + c << iconst(v) + a << iadd(x, c) + => + a << iadd_imm(x, v) + ) + """ + + def __init__(self, src, dst): + self.src = src + self.dst = dst + # Variables that are inputs to the source pattern. + self.inputs = list() + # Variables defined in either src or dst. + self.defs = list() + + # Rewrite variables in src and dst RTL lists to our own copies. + # Map name -> private Var. + symtab = dict() + self._rewrite_rtl(src, symtab, SRCCTX) + num_src_inputs = len(self.inputs) + self._rewrite_rtl(dst, symtab, DSTCTX) + + # Check for inconsistently used inputs. + for i in self.inputs: + if i.defctx: + 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): + for line in rtl: + if isinstance(line, Def): + line.defs = tuple( + self._rewrite_defs(line.defs, symtab, context)) + expr = line.expr + else: + expr = line + self._rewrite_expr(expr, symtab, context) + + def _rewrite_expr(self, expr, symtab, context): + """ + 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, defs, symtab, context): + """ + Given a tuple of symbols defined in a Def, rewrite them to local + symbols. Yield the new locals. + """ + for sym in defs: + name = str(sym) + if name in symtab: + var = symtab[name] + if var.defctx & context: + raise AssertionError("'{}' multiply defined".format(name)) + else: + var = Var(name) + symtab[name] = var + self.defs.append(var) + var.defctx |= context + yield var + + def _rewrite_uses(self, expr, stack, symtab, context): + """ + 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.push(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 consistenty as a def or input. + if var.defctx and (var.defctx & context) == 0: + 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 + + +class XFormGroup(object): + """ + A group of related transformations. + """ + + def __init__(self): + self.xforms = list() + + def legalize(self, src, dst): + """ + Add a legalization pattern to this group. + + :param src: Single `Def` or `Apply` to be legalized. + :param dst: `Rtl` list of replacement instructions. + """ + self.xforms.append(XForm(Rtl(src), dst))