Define AST nodes and instruction transformations.

Enable syntax: iadd(x, y) which creates an Apply node.
Enable syntax: z << iadd(x, y) which creates a Def node.

Add an XForm class which represents source and destination patterns as
RTL lists.
This commit is contained in:
Jakob Stoklund Olesen
2016-09-22 12:59:31 -07:00
parent 4db11d1ae7
commit 620f46202f
5 changed files with 416 additions and 1 deletions

View File

@@ -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.

122
meta/cretonne/ast.py Normal file
View File

@@ -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)

28
meta/cretonne/test_ast.py Normal file
View File

@@ -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'))")

View File

@@ -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)

182
meta/cretonne/xform.py Normal file
View File

@@ -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))