* Add Atom and Literal base classes to CDSL Ast. Change substitution() and copy() on Def/Apply/Rtl to support substituting Var->Union[Var, Literal]. Check in Apply() constructor kinds of passed in Literals respect instruction signature
* Change verify_semantics to check all possible instantiations of enumerated immediates (needed to descrive icmp). Add all bitvector comparison primitives and bvite; Change set_semantics to optionally accept XForms; Add semantics for icmp; Fix typing errors in semantics/{smtlib, elaborate, __init__}.py after the change of VarMap->VarAtomMap
* Forgot macros.py
* Nit obscured by testing with mypy enabled present.
* Typo
This commit is contained in:
committed by
Jakob Stoklund Olesen
parent
591f6c1632
commit
66da171050
@@ -11,23 +11,23 @@ from .predicates import IsEqual, And, TypePredicate
|
||||
|
||||
try:
|
||||
from typing import Union, Tuple, Sequence, TYPE_CHECKING, Dict, List # noqa
|
||||
from typing import Optional, Set # noqa
|
||||
from typing import Optional, Set, Any # noqa
|
||||
if TYPE_CHECKING:
|
||||
from .operands import ImmediateKind # noqa
|
||||
from .predicates import PredNode # noqa
|
||||
VarMap = Dict["Var", "Var"]
|
||||
VarAtomMap = Dict["Var", "Atom"]
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def replace_var(arg, m):
|
||||
# type: (Expr, VarMap) -> Expr
|
||||
# type: (Expr, VarAtomMap) -> Expr
|
||||
"""
|
||||
Given a var v return either m[v] or a new variable v' (and remember
|
||||
m[v]=v'). Otherwise return the argument unchanged
|
||||
"""
|
||||
if isinstance(arg, Var):
|
||||
new_arg = m.get(arg, Var(arg.name)) # type: Var
|
||||
new_arg = m.get(arg, Var(arg.name)) # type: Atom
|
||||
m[arg] = new_arg
|
||||
return new_arg
|
||||
return arg
|
||||
@@ -76,7 +76,7 @@ class Def(object):
|
||||
', '.join(map(str, self.defs)), self.expr)
|
||||
|
||||
def copy(self, m):
|
||||
# type: (VarMap) -> Def
|
||||
# type: (VarAtomMap) -> Def
|
||||
"""
|
||||
Return a copy of this Def with vars replaced with fresh variables,
|
||||
in accordance with the map m. Update m as neccessary.
|
||||
@@ -106,7 +106,7 @@ class Def(object):
|
||||
return self.definitions().union(self.uses())
|
||||
|
||||
def substitution(self, other, s):
|
||||
# type: (Def, VarMap) -> Optional[VarMap]
|
||||
# type: (Def, VarAtomMap) -> Optional[VarAtomMap]
|
||||
"""
|
||||
If the Defs self and other agree structurally, return a variable
|
||||
substitution to transform self to other. Otherwise return None. Two
|
||||
@@ -133,7 +133,13 @@ class Expr(object):
|
||||
"""
|
||||
|
||||
|
||||
class Var(Expr):
|
||||
class Atom(Expr):
|
||||
"""
|
||||
An Atom in the DSL is either a literal or a Var
|
||||
"""
|
||||
|
||||
|
||||
class Var(Atom):
|
||||
"""
|
||||
A free variable.
|
||||
|
||||
@@ -304,6 +310,16 @@ class Apply(Expr):
|
||||
self.args = args
|
||||
assert len(self.inst.ins) == len(args)
|
||||
|
||||
# Check that the kinds of Literals arguments match the expected Operand
|
||||
for op_idx in self.inst.imm_opnums:
|
||||
arg = self.args[op_idx]
|
||||
op = self.inst.ins[op_idx]
|
||||
|
||||
if isinstance(arg, Literal):
|
||||
assert arg.kind == op.kind, \
|
||||
"Passing literal {} to field of wrong kind {}."\
|
||||
.format(arg, op.kind)
|
||||
|
||||
def __rlshift__(self, other):
|
||||
# type: (Union[Var, Tuple[Var, ...]]) -> Def
|
||||
"""
|
||||
@@ -377,7 +393,7 @@ class Apply(Expr):
|
||||
return pred
|
||||
|
||||
def copy(self, m):
|
||||
# type: (VarMap) -> Apply
|
||||
# type: (VarAtomMap) -> Apply
|
||||
"""
|
||||
Return a copy of this Expr with vars replaced with fresh variables,
|
||||
in accordance with the map m. Update m as neccessary.
|
||||
@@ -396,15 +412,12 @@ class Apply(Expr):
|
||||
return res
|
||||
|
||||
def substitution(self, other, s):
|
||||
# type: (Apply, VarMap) -> Optional[VarMap]
|
||||
# type: (Apply, VarAtomMap) -> Optional[VarAtomMap]
|
||||
"""
|
||||
If the application self and other agree structurally, return a variable
|
||||
substitution to transform self to other. Otherwise return None. Two
|
||||
applications agree structurally if:
|
||||
1) They are over the same instruction
|
||||
2) Every Var v in self, maps to a single Var w in other. I.e for
|
||||
each use of v in self, w is used in the corresponding place in
|
||||
other.
|
||||
If there is a substituion from Var->Atom that converts self to other,
|
||||
return it, otherwise return None. Note that this is strictly weaker
|
||||
than unification (see TestXForm.test_subst_enum_bad_var_const for
|
||||
example).
|
||||
"""
|
||||
if self.inst != other.inst:
|
||||
return None
|
||||
@@ -413,37 +426,62 @@ class Apply(Expr):
|
||||
assert (len(self.args) == len(other.args))
|
||||
|
||||
for (self_a, other_a) in zip(self.args, other.args):
|
||||
if (isinstance(self_a, Var)):
|
||||
if not isinstance(other_a, Var):
|
||||
return None
|
||||
assert isinstance(self_a, Atom) and isinstance(other_a, Atom)
|
||||
|
||||
if (isinstance(self_a, Var)):
|
||||
if (self_a not in s):
|
||||
s[self_a] = other_a
|
||||
else:
|
||||
if (s[self_a] != other_a):
|
||||
return None
|
||||
elif isinstance(self_a, ConstantInt):
|
||||
if not isinstance(other_a, ConstantInt):
|
||||
return None
|
||||
assert self_a.kind == other_a.kind
|
||||
if (self_a.value != other_a.value):
|
||||
return None
|
||||
elif isinstance(other_a, Var):
|
||||
assert isinstance(self_a, Literal)
|
||||
if (other_a not in s):
|
||||
s[other_a] = self_a
|
||||
else:
|
||||
if s[other_a] != self_a:
|
||||
return None
|
||||
else:
|
||||
assert isinstance(self_a, Enumerator)
|
||||
|
||||
if not isinstance(other_a, Enumerator):
|
||||
# Currently don't support substitutions Var->Enumerator
|
||||
return None
|
||||
|
||||
assert (isinstance(self_a, Literal) and
|
||||
isinstance(other_a, Literal))
|
||||
# Guaranteed by self.inst == other.inst
|
||||
assert self_a.kind == other_a.kind
|
||||
|
||||
if (self_a.value != other_a.value):
|
||||
return None
|
||||
|
||||
return s
|
||||
|
||||
|
||||
class ConstantInt(Expr):
|
||||
class Literal(Atom):
|
||||
"""
|
||||
Base Class for all literal expressions in the DSL.
|
||||
"""
|
||||
def __init__(self, kind, value):
|
||||
# type: (ImmediateKind, Any) -> None
|
||||
self.kind = kind
|
||||
self.value = value
|
||||
|
||||
def __eq__(self, other):
|
||||
# type: (Any) -> bool
|
||||
if not isinstance(other, Literal):
|
||||
return False
|
||||
|
||||
if self.kind != other.kind:
|
||||
return False
|
||||
|
||||
# Can't just compare value here, as comparison Any <> Any returns Any
|
||||
return repr(self) == repr(other)
|
||||
|
||||
def __ne__(self, other):
|
||||
# type: (Any) -> bool
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return '{}.{}'.format(self.kind, self.value)
|
||||
|
||||
|
||||
class ConstantInt(Literal):
|
||||
"""
|
||||
A value of an integer immediate operand.
|
||||
|
||||
@@ -454,8 +492,7 @@ class ConstantInt(Expr):
|
||||
|
||||
def __init__(self, kind, value):
|
||||
# type: (ImmediateKind, int) -> None
|
||||
self.kind = kind
|
||||
self.value = value
|
||||
super(ConstantInt, self).__init__(kind, value)
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
@@ -464,12 +501,8 @@ class ConstantInt(Expr):
|
||||
"""
|
||||
return str(self.value)
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return '{}({})'.format(self.kind, self.value)
|
||||
|
||||
|
||||
class Enumerator(Expr):
|
||||
class Enumerator(Literal):
|
||||
"""
|
||||
A value of an enumerated immediate operand.
|
||||
|
||||
@@ -486,8 +519,7 @@ class Enumerator(Expr):
|
||||
|
||||
def __init__(self, kind, value):
|
||||
# type: (ImmediateKind, str) -> None
|
||||
self.kind = kind
|
||||
self.value = value
|
||||
super(Enumerator, self).__init__(kind, value)
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
@@ -495,7 +527,3 @@ class Enumerator(Expr):
|
||||
Get the Rust expression form of this enumerator.
|
||||
"""
|
||||
return self.kind.rust_enumerator(self.value)
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return '{}.{}'.format(self.kind, self.value)
|
||||
|
||||
@@ -9,7 +9,7 @@ try:
|
||||
from typing import Union, Sequence, List, Tuple, Any, TYPE_CHECKING # noqa
|
||||
from typing import Dict # noqa
|
||||
if TYPE_CHECKING:
|
||||
from .ast import Expr, Apply, Var, Def # noqa
|
||||
from .ast import Expr, Apply, Var, Def, VarAtomMap # noqa
|
||||
from .typevar import TypeVar # noqa
|
||||
from .ti import TypeConstraint # noqa
|
||||
from .xform import XForm, Rtl
|
||||
@@ -18,7 +18,7 @@ try:
|
||||
ConstrList = Union[Sequence[TypeConstraint], TypeConstraint]
|
||||
MaybeBoundInst = Union['Instruction', 'BoundInstruction']
|
||||
InstructionSemantics = Sequence[XForm]
|
||||
RtlCase = Union[Rtl, Tuple[Rtl, Sequence[TypeConstraint]]]
|
||||
SemDefCase = Union[Rtl, Tuple[Rtl, Sequence[TypeConstraint]], XForm]
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
@@ -349,7 +349,7 @@ class Instruction(object):
|
||||
return Apply(self, args)
|
||||
|
||||
def set_semantics(self, src, *dsts):
|
||||
# type: (Union[Def, Apply], *RtlCase) -> None
|
||||
# type: (Union[Def, Apply], *SemDefCase) -> None
|
||||
"""Set our semantics."""
|
||||
from semantics import verify_semantics
|
||||
from .xform import XForm, Rtl
|
||||
@@ -358,6 +358,11 @@ class Instruction(object):
|
||||
for dst in dsts:
|
||||
if isinstance(dst, Rtl):
|
||||
sem.append(XForm(Rtl(src).copy({}), dst))
|
||||
elif isinstance(dst, XForm):
|
||||
sem.append(XForm(
|
||||
dst.src.copy({}),
|
||||
dst.dst.copy({}),
|
||||
dst.constraints))
|
||||
else:
|
||||
assert isinstance(dst, tuple)
|
||||
sem.append(XForm(Rtl(src).copy({}), dst[0],
|
||||
|
||||
@@ -5,10 +5,10 @@ from .types import ValueType
|
||||
from .typevar import TypeVar
|
||||
|
||||
try:
|
||||
from typing import Union, Dict, TYPE_CHECKING # noqa
|
||||
from typing import Union, Dict, TYPE_CHECKING, Iterable # noqa
|
||||
OperandSpec = Union['OperandKind', ValueType, TypeVar]
|
||||
if TYPE_CHECKING:
|
||||
from .ast import Enumerator, ConstantInt # noqa
|
||||
from .ast import Enumerator, ConstantInt, Literal # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
@@ -128,6 +128,17 @@ class ImmediateKind(OperandKind):
|
||||
"""
|
||||
return '{}::{}'.format(self.rust_type, self.values[value])
|
||||
|
||||
def is_enumerable(self):
|
||||
# type: () -> bool
|
||||
return self.values is not None
|
||||
|
||||
def possible_values(self):
|
||||
# type: () -> Iterable[Literal]
|
||||
from cdsl.ast import Enumerator # noqa
|
||||
assert self.is_enumerable()
|
||||
for v in self.values.keys():
|
||||
yield Enumerator(self, v)
|
||||
|
||||
|
||||
# Instances of entity reference operand types are provided in the
|
||||
# `cretonne.entities` module.
|
||||
|
||||
@@ -80,15 +80,52 @@ class TestXForm(TestCase):
|
||||
dst = Rtl(b << icmp(intcc.eq, z, u))
|
||||
assert src.substitution(dst, {}) == {a: b, x: z, y: u}
|
||||
|
||||
def test_subst_enum_bad(self):
|
||||
def test_subst_enum_var_const(self):
|
||||
src = Rtl(a << icmp(CC1, x, y))
|
||||
dst = Rtl(b << icmp(intcc.eq, z, u))
|
||||
assert src.substitution(dst, {}) is None
|
||||
assert src.substitution(dst, {}) == {CC1: intcc.eq, x: z, y: u, a: b},\
|
||||
"{} != {}".format(src.substitution(dst, {}),
|
||||
{CC1: intcc.eq, x: z, y: u, a: b})
|
||||
|
||||
src = Rtl(a << icmp(intcc.eq, x, y))
|
||||
dst = Rtl(b << icmp(CC1, z, u))
|
||||
assert src.substitution(dst, {}) is None
|
||||
assert src.substitution(dst, {}) == {CC1: intcc.eq, x: z, y: u, a: b}
|
||||
|
||||
def test_subst_enum_bad(self):
|
||||
src = Rtl(a << icmp(intcc.eq, x, y))
|
||||
dst = Rtl(b << icmp(intcc.sge, z, u))
|
||||
assert src.substitution(dst, {}) is None
|
||||
|
||||
def test_subst_enum_bad_var_const(self):
|
||||
a1 = Var('a1')
|
||||
x1 = Var('x1')
|
||||
y1 = Var('y1')
|
||||
|
||||
b1 = Var('b1')
|
||||
z1 = Var('z1')
|
||||
u1 = Var('u1')
|
||||
|
||||
# Var mapping to 2 different constants
|
||||
src = Rtl(a << icmp(CC1, x, y),
|
||||
a1 << icmp(CC1, x1, y1))
|
||||
dst = Rtl(b << icmp(intcc.eq, z, u),
|
||||
b1 << icmp(intcc.sge, z1, u1))
|
||||
|
||||
assert src.substitution(dst, {}) is None
|
||||
|
||||
# 2 different constants mapping to the same var
|
||||
src = Rtl(a << icmp(intcc.eq, x, y),
|
||||
a1 << icmp(intcc.sge, x1, y1))
|
||||
dst = Rtl(b << icmp(CC1, z, u),
|
||||
b1 << icmp(CC1, z1, u1))
|
||||
|
||||
assert src.substitution(dst, {}) is None
|
||||
|
||||
# Var mapping to var and constant - note that full unification would
|
||||
# have allowed this.
|
||||
src = Rtl(a << icmp(CC1, x, y),
|
||||
a1 << icmp(CC1, x1, y1))
|
||||
dst = Rtl(b << icmp(CC2, z, u),
|
||||
b1 << icmp(intcc.sge, z1, u1))
|
||||
|
||||
assert src.substitution(dst, {}) is None
|
||||
|
||||
@@ -3,16 +3,16 @@ Instruction transformations.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from .ast import Def, Var, Apply
|
||||
from .ti import ti_xform, TypeEnv, get_type_env
|
||||
from .ti import ti_xform, TypeEnv, get_type_env, TypeConstraint
|
||||
from functools import reduce
|
||||
|
||||
try:
|
||||
from typing import Union, Iterator, Sequence, Iterable, List, Dict # noqa
|
||||
from typing import Optional, Set # noqa
|
||||
from .ast import Expr, VarMap # noqa
|
||||
from .ast import Expr, VarAtomMap # noqa
|
||||
from .isa import TargetISA # noqa
|
||||
from .ti import TypeConstraint # noqa
|
||||
from .typevar import TypeVar # noqa
|
||||
from .instructions import ConstrList # noqa
|
||||
DefApply = Union[Def, Apply]
|
||||
except ImportError:
|
||||
pass
|
||||
@@ -47,7 +47,7 @@ class Rtl(object):
|
||||
self.rtl = tuple(map(canonicalize_defapply, args))
|
||||
|
||||
def copy(self, m):
|
||||
# type: (VarMap) -> Rtl
|
||||
# type: (VarAtomMap) -> Rtl
|
||||
"""
|
||||
Return a copy of this rtl with all Vars substituted with copies or
|
||||
according to m. Update m as neccessary.
|
||||
@@ -85,7 +85,7 @@ class Rtl(object):
|
||||
return reduce(flow_f, reversed(self.rtl), set([]))
|
||||
|
||||
def substitution(self, other, s):
|
||||
# type: (Rtl, VarMap) -> Optional[VarMap]
|
||||
# type: (Rtl, VarAtomMap) -> Optional[VarAtomMap]
|
||||
"""
|
||||
If the Rtl self agrees structurally with the Rtl other, return a
|
||||
substitution to transform self to other. Two Rtls agree structurally if
|
||||
@@ -132,6 +132,10 @@ class Rtl(object):
|
||||
assert typing[v].singleton_type() is not None
|
||||
v.set_typevar(typing[v])
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
return "\n".join(map(str, self.rtl))
|
||||
|
||||
|
||||
class XForm(object):
|
||||
"""
|
||||
@@ -162,7 +166,7 @@ class XForm(object):
|
||||
"""
|
||||
|
||||
def __init__(self, src, dst, constraints=None):
|
||||
# type: (Rtl, Rtl, Optional[Sequence[TypeConstraint]]) -> None
|
||||
# type: (Rtl, Rtl, Optional[ConstrList]) -> None
|
||||
self.src = src
|
||||
self.dst = dst
|
||||
# Variables that are inputs to the source pattern.
|
||||
@@ -203,10 +207,18 @@ class XForm(object):
|
||||
return tv
|
||||
return symtab[tv.name[len("typeof_"):]].get_typevar()
|
||||
|
||||
self.constraints = [] # type: List[TypeConstraint]
|
||||
if constraints is not None:
|
||||
for c in constraints:
|
||||
if isinstance(constraints, TypeConstraint):
|
||||
constr_list = [constraints] # type: Sequence[TypeConstraint]
|
||||
else:
|
||||
constr_list = constraints
|
||||
|
||||
for c in constr_list:
|
||||
type_m = {tv: interp_tv(tv) for tv in c.tvs()}
|
||||
self.ti.add_constraint(c.translate(type_m))
|
||||
inner_c = c.translate(type_m)
|
||||
self.constraints.append(inner_c)
|
||||
self.ti.add_constraint(inner_c)
|
||||
|
||||
# Sanity: The set of inferred free typevars should be a subset of the
|
||||
# TVs corresponding to Vars appearing in src
|
||||
@@ -333,7 +345,7 @@ class XForm(object):
|
||||
defs are renamed with '.suffix' appended to their old name.
|
||||
"""
|
||||
assert r.is_concrete()
|
||||
s = self.src.substitution(r, {}) # type: VarMap
|
||||
s = self.src.substitution(r, {}) # type: VarAtomMap
|
||||
assert s is not None
|
||||
|
||||
if (suffix is not None):
|
||||
|
||||
Reference in New Issue
Block a user