Verify restrictions on polymorphism.
Add a typevar_operand argument to the InstructionFormat constructor which determines the operand used for inferring the controlling type variable. Identify polymorphic instructions when they are created, determine if the controlling type variable can be inferred from the typevar_operand, and verify the use of type variables in the other operands. Generate type variable summary in the documentation, including how the controlling type variable is inferred.
This commit is contained in:
@@ -18,13 +18,14 @@ from docutils.parsers.rst import directives
|
|||||||
from sphinx import addnodes
|
from sphinx import addnodes
|
||||||
from sphinx.directives import ObjectDescription
|
from sphinx.directives import ObjectDescription
|
||||||
from sphinx.domains import Domain, ObjType
|
from sphinx.domains import Domain, ObjType
|
||||||
from sphinx.locale import l_, _
|
from sphinx.locale import l_
|
||||||
from sphinx.roles import XRefRole
|
from sphinx.roles import XRefRole
|
||||||
from sphinx.util.docfields import Field, GroupedField, TypedField
|
from sphinx.util.docfields import Field, GroupedField, TypedField
|
||||||
from sphinx.util.nodes import make_refnode
|
from sphinx.util.nodes import make_refnode
|
||||||
|
|
||||||
import sphinx.ext.autodoc
|
import sphinx.ext.autodoc
|
||||||
|
|
||||||
|
|
||||||
class CtonObject(ObjectDescription):
|
class CtonObject(ObjectDescription):
|
||||||
"""
|
"""
|
||||||
Any kind of Cretonne IL object.
|
Any kind of Cretonne IL object.
|
||||||
@@ -68,10 +69,11 @@ class CtonObject(ObjectDescription):
|
|||||||
# Type variables are indicated as %T.
|
# Type variables are indicated as %T.
|
||||||
typevar = re.compile('(\%[A-Z])')
|
typevar = re.compile('(\%[A-Z])')
|
||||||
|
|
||||||
|
|
||||||
def parse_type(name, signode):
|
def parse_type(name, signode):
|
||||||
"""
|
"""
|
||||||
Parse a type with embedded type vars and append to signode.
|
Parse a type with embedded type vars and append to signode.
|
||||||
|
|
||||||
Return a a string that can be compiled into a regular expression matching
|
Return a a string that can be compiled into a regular expression matching
|
||||||
the type.
|
the type.
|
||||||
"""
|
"""
|
||||||
@@ -92,6 +94,7 @@ def parse_type(name, signode):
|
|||||||
re_str += re.escape(part)
|
re_str += re.escape(part)
|
||||||
return re_str
|
return re_str
|
||||||
|
|
||||||
|
|
||||||
class CtonType(CtonObject):
|
class CtonType(CtonObject):
|
||||||
"""A Cretonne IL type description."""
|
"""A Cretonne IL type description."""
|
||||||
|
|
||||||
@@ -103,7 +106,7 @@ class CtonType(CtonObject):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
name = sig.strip()
|
name = sig.strip()
|
||||||
re_str = parse_type(name, signode)
|
parse_type(name, signode)
|
||||||
return name
|
return name
|
||||||
|
|
||||||
def get_index_text(self, name):
|
def get_index_text(self, name):
|
||||||
@@ -112,12 +115,14 @@ class CtonType(CtonObject):
|
|||||||
sep_equal = re.compile('\s*=\s*')
|
sep_equal = re.compile('\s*=\s*')
|
||||||
sep_comma = re.compile('\s*,\s*')
|
sep_comma = re.compile('\s*,\s*')
|
||||||
|
|
||||||
|
|
||||||
def parse_params(s, signode):
|
def parse_params(s, signode):
|
||||||
for i,p in enumerate(sep_comma.split(s)):
|
for i, p in enumerate(sep_comma.split(s)):
|
||||||
if i != 0:
|
if i != 0:
|
||||||
signode += nodes.Text(', ')
|
signode += nodes.Text(', ')
|
||||||
signode += nodes.emphasis(p, p)
|
signode += nodes.emphasis(p, p)
|
||||||
|
|
||||||
|
|
||||||
class CtonInst(CtonObject):
|
class CtonInst(CtonObject):
|
||||||
"""A Cretonne IL instruction."""
|
"""A Cretonne IL instruction."""
|
||||||
|
|
||||||
@@ -128,6 +133,7 @@ class CtonInst(CtonObject):
|
|||||||
TypedField('result', label=l_('Results'),
|
TypedField('result', label=l_('Results'),
|
||||||
names=('out', 'result'),
|
names=('out', 'result'),
|
||||||
typerolename='type', typenames=('type',)),
|
typerolename='type', typenames=('type',)),
|
||||||
|
GroupedField('typevar', names=('typevar',), label=l_('Type Variables')),
|
||||||
GroupedField('flag', names=('flag',), label=l_('Flags')),
|
GroupedField('flag', names=('flag',), label=l_('Flags')),
|
||||||
Field('resulttype', label=l_('Result type'), has_arg=False,
|
Field('resulttype', label=l_('Result type'), has_arg=False,
|
||||||
names=('rtype',)),
|
names=('rtype',)),
|
||||||
@@ -164,24 +170,25 @@ class CtonInst(CtonObject):
|
|||||||
def get_index_text(self, name):
|
def get_index_text(self, name):
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
|
||||||
class CretonneDomain(Domain):
|
class CretonneDomain(Domain):
|
||||||
"""Cretonne domain for intermediate language objects."""
|
"""Cretonne domain for intermediate language objects."""
|
||||||
name = 'cton'
|
name = 'cton'
|
||||||
label = 'Cretonne'
|
label = 'Cretonne'
|
||||||
|
|
||||||
object_types = {
|
object_types = {
|
||||||
'type' : ObjType(l_('type'), 'type'),
|
'type': ObjType(l_('type'), 'type'),
|
||||||
'inst' : ObjType(l_('instruction'), 'inst')
|
'inst': ObjType(l_('instruction'), 'inst')
|
||||||
}
|
}
|
||||||
|
|
||||||
directives = {
|
directives = {
|
||||||
'type' : CtonType,
|
'type': CtonType,
|
||||||
'inst' : CtonInst,
|
'inst': CtonInst,
|
||||||
}
|
}
|
||||||
|
|
||||||
roles = {
|
roles = {
|
||||||
'type' : XRefRole(),
|
'type': XRefRole(),
|
||||||
'inst' : XRefRole(),
|
'inst': XRefRole(),
|
||||||
}
|
}
|
||||||
|
|
||||||
initial_data = {
|
initial_data = {
|
||||||
@@ -230,7 +237,7 @@ class TypeDocumenter(sphinx.ext.autodoc.Documenter):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def resolve_name(self, modname, parents, path, base):
|
def resolve_name(self, modname, parents, path, base):
|
||||||
return 'cretonne.types', [ base ]
|
return 'cretonne.types', [base]
|
||||||
|
|
||||||
def add_content(self, more_content, no_docstring=False):
|
def add_content(self, more_content, no_docstring=False):
|
||||||
super(TypeDocumenter, self).add_content(more_content, no_docstring)
|
super(TypeDocumenter, self).add_content(more_content, no_docstring)
|
||||||
@@ -254,7 +261,7 @@ class InstDocumenter(sphinx.ext.autodoc.Documenter):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def resolve_name(self, modname, parents, path, base):
|
def resolve_name(self, modname, parents, path, base):
|
||||||
return 'cretonne.base', [ base ]
|
return 'cretonne.base', [base]
|
||||||
|
|
||||||
def format_signature(self):
|
def format_signature(self):
|
||||||
inst = self.object
|
inst = self.object
|
||||||
@@ -285,9 +292,32 @@ class InstDocumenter(sphinx.ext.autodoc.Documenter):
|
|||||||
|
|
||||||
# Add inputs and outputs.
|
# Add inputs and outputs.
|
||||||
for op in self.object.ins:
|
for op in self.object.ins:
|
||||||
self.add_line(u':in {} {}: {}'.format(op.typ.name, op.name, op.get_doc()), sourcename)
|
self.add_line(u':in {} {}: {}'.format(
|
||||||
|
op.typ.name, op.name, op.get_doc()), sourcename)
|
||||||
for op in self.object.outs:
|
for op in self.object.outs:
|
||||||
self.add_line(u':out {} {}: {}'.format(op.typ.name, op.name, op.get_doc()), sourcename)
|
self.add_line(u':out {} {}: {}'.format(
|
||||||
|
op.typ.name, op.name, op.get_doc()), sourcename)
|
||||||
|
|
||||||
|
# Document type inference for polymorphic instructions.
|
||||||
|
if self.object.is_polymorphic:
|
||||||
|
if self.object.ctrl_typevar is not None:
|
||||||
|
if self.object.use_typevar_operand:
|
||||||
|
self.add_line(
|
||||||
|
u':typevar {}: inferred from {}'
|
||||||
|
.format(
|
||||||
|
self.object.ctrl_typevar.name,
|
||||||
|
self.object.ins[
|
||||||
|
self.object.format.typevar_operand]),
|
||||||
|
sourcename)
|
||||||
|
else:
|
||||||
|
self.add_line(
|
||||||
|
u':typevar {}: explicitly provided'
|
||||||
|
.format(self.object.ctrl_typevar.name),
|
||||||
|
sourcename)
|
||||||
|
for tv in self.object.other_typevars:
|
||||||
|
self.add_line(
|
||||||
|
u':typevar {}: from input operand'.format(tv.name),
|
||||||
|
sourcename)
|
||||||
|
|
||||||
|
|
||||||
def setup(app):
|
def setup(app):
|
||||||
@@ -295,4 +325,4 @@ def setup(app):
|
|||||||
app.add_autodocumenter(TypeDocumenter)
|
app.add_autodocumenter(TypeDocumenter)
|
||||||
app.add_autodocumenter(InstDocumenter)
|
app.add_autodocumenter(InstDocumenter)
|
||||||
|
|
||||||
return { 'version' : '0.1' }
|
return {'version': '0.1'}
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ representation depends on the input operand kinds and whether the instruction
|
|||||||
can produce multiple results.
|
can produce multiple results.
|
||||||
|
|
||||||
.. autoclass:: OperandKind
|
.. autoclass:: OperandKind
|
||||||
.. inheritance-diagram:: OperandKind ImmediateKind EntityRefkind
|
.. inheritance-diagram:: OperandKind ImmediateKind EntityRefKind
|
||||||
|
|
||||||
Since all SSA value operands are represented as a `Value` in Rust code, value
|
Since all SSA value operands are represented as a `Value` in Rust code, value
|
||||||
types don't affect the representation. Two special operand kinds are used to
|
types don't affect the representation. Two special operand kinds are used to
|
||||||
@@ -174,8 +174,13 @@ constraints, in practice more freedom than what is needed for normal instruction
|
|||||||
set architectures. In order to simplify the Rust representation of value type
|
set architectures. In order to simplify the Rust representation of value type
|
||||||
constraints, some restrictions are imposed on the use of type variables.
|
constraints, some restrictions are imposed on the use of type variables.
|
||||||
|
|
||||||
A polymorphic instruction has a single *controlling type variable*. The value
|
A polymorphic instruction has a single *controlling type variable*. For a given
|
||||||
types of instruction results must be one of the following:
|
opcode, this type variable must be the type of the first result or the type of
|
||||||
|
the input value operand designated by the `typevar_operand` argument to the
|
||||||
|
:py:class:`InstructionFormat` constructor. By default, this is the first value
|
||||||
|
operand, which works most of the time.
|
||||||
|
|
||||||
|
The value types of instruction results must be one of the following:
|
||||||
|
|
||||||
1. A concrete value type.
|
1. A concrete value type.
|
||||||
2. The controlling type variable.
|
2. The controlling type variable.
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ class OperandKind(object):
|
|||||||
"""
|
"""
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
def free_typevar(self):
|
||||||
|
# Return the free typevariable controlling the type of this operand.
|
||||||
|
return None
|
||||||
|
|
||||||
#: An SSA value operand. This is a value defined by another instruction.
|
#: An SSA value operand. This is a value defined by another instruction.
|
||||||
value = OperandKind(
|
value = OperandKind(
|
||||||
'value', """
|
'value', """
|
||||||
@@ -125,6 +129,9 @@ class ValueType(object):
|
|||||||
"""
|
"""
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
def free_typevar(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class ScalarType(ValueType):
|
class ScalarType(ValueType):
|
||||||
"""
|
"""
|
||||||
@@ -253,6 +260,10 @@ class TypeVar(object):
|
|||||||
scalars=True, simd=False):
|
scalars=True, simd=False):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.__doc__ = doc
|
self.__doc__ = doc
|
||||||
|
self.base = base
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "`{}`".format(self.name)
|
||||||
|
|
||||||
def lane(self):
|
def lane(self):
|
||||||
"""
|
"""
|
||||||
@@ -277,6 +288,11 @@ class TypeVar(object):
|
|||||||
# value.
|
# value.
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
def free_typevar(self):
|
||||||
|
if isinstance(self.base, TypeVar):
|
||||||
|
return self.base
|
||||||
|
else:
|
||||||
|
return self
|
||||||
|
|
||||||
# Defining instructions.
|
# Defining instructions.
|
||||||
|
|
||||||
@@ -360,6 +376,9 @@ class Operand(object):
|
|||||||
else:
|
else:
|
||||||
return self.typ.__doc__
|
return self.typ.__doc__
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "`{}`".format(self.name)
|
||||||
|
|
||||||
|
|
||||||
class InstructionFormat(object):
|
class InstructionFormat(object):
|
||||||
"""
|
"""
|
||||||
@@ -387,6 +406,9 @@ class InstructionFormat(object):
|
|||||||
:param boxed_storage: Set to `True` is this instruction format requires a
|
:param boxed_storage: Set to `True` is this instruction format requires a
|
||||||
`data: Box<...>` pointer to additional storage in its `InstructionData`
|
`data: Box<...>` pointer to additional storage in its `InstructionData`
|
||||||
variant.
|
variant.
|
||||||
|
:param typevar_operand: Index of the input operand that is used to infer
|
||||||
|
the controlling type variable. By default, this is the first `value`
|
||||||
|
operand.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Map (multiple_results, kind, kind, ...) -> InstructionFormat
|
# Map (multiple_results, kind, kind, ...) -> InstructionFormat
|
||||||
@@ -400,6 +422,20 @@ class InstructionFormat(object):
|
|||||||
self.kinds = kinds
|
self.kinds = kinds
|
||||||
self.multiple_results = kwargs.get('multiple_results', False)
|
self.multiple_results = kwargs.get('multiple_results', False)
|
||||||
self.boxed_storage = kwargs.get('boxed_storage', False)
|
self.boxed_storage = kwargs.get('boxed_storage', False)
|
||||||
|
|
||||||
|
# Which of self.kinds are `value`?
|
||||||
|
self.value_operands = tuple(
|
||||||
|
i for i, k in enumerate(self.kinds) if k is value)
|
||||||
|
|
||||||
|
# The typevar_operand argument must point to a 'value' operand.
|
||||||
|
self.typevar_operand = kwargs.get('typevar_operand', None)
|
||||||
|
if self.typevar_operand is not None:
|
||||||
|
assert self.kinds[self.typevar_operand] is value, \
|
||||||
|
"typevar_operand must indicate a 'value' operand"
|
||||||
|
elif len(self.value_operands) > 0:
|
||||||
|
# Default to the first 'value' operand, if there is one.
|
||||||
|
self.typevar_operand = self.value_operands[0]
|
||||||
|
|
||||||
# Compute a signature for the global registry.
|
# Compute a signature for the global registry.
|
||||||
sig = (self.multiple_results,) + kinds
|
sig = (self.multiple_results,) + kinds
|
||||||
if sig in InstructionFormat._registry:
|
if sig in InstructionFormat._registry:
|
||||||
@@ -453,7 +489,7 @@ class Instruction(object):
|
|||||||
:param ins: Tuple of input operands. This can be a mix of SSA value
|
:param ins: Tuple of input operands. This can be a mix of SSA value
|
||||||
operands and other operand kinds.
|
operands and other operand kinds.
|
||||||
:param outs: Tuple of output operands. The output operands must be SSA
|
:param outs: Tuple of output operands. The output operands must be SSA
|
||||||
values.
|
values or `variable_args`.
|
||||||
:param is_terminator: This is a terminator instruction.
|
:param is_terminator: This is a terminator instruction.
|
||||||
:param is_branch: This is a branch instruction.
|
:param is_branch: This is a branch instruction.
|
||||||
"""
|
"""
|
||||||
@@ -465,8 +501,99 @@ class Instruction(object):
|
|||||||
self.ins = self._to_operand_tuple(ins)
|
self.ins = self._to_operand_tuple(ins)
|
||||||
self.outs = self._to_operand_tuple(outs)
|
self.outs = self._to_operand_tuple(outs)
|
||||||
self.format = InstructionFormat.lookup(self.ins, self.outs)
|
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)
|
||||||
|
self._verify_polymorphic()
|
||||||
InstructionGroup.append(self)
|
InstructionGroup.append(self)
|
||||||
|
|
||||||
|
def _verify_polymorphic(self):
|
||||||
|
"""
|
||||||
|
Check if this instruction is polymorphic, and verify its use of type
|
||||||
|
variables.
|
||||||
|
"""
|
||||||
|
poly_ins = [
|
||||||
|
i for i in self.format.value_operands
|
||||||
|
if self.ins[i].typ.free_typevar()]
|
||||||
|
poly_outs = [
|
||||||
|
i for i, o in enumerate(self.outs)
|
||||||
|
if o.typ.free_typevar()]
|
||||||
|
self.is_polymorphic = len(poly_ins) > 0 or len(poly_outs) > 0
|
||||||
|
if not self.is_polymorphic:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Prefer to use the typevar_operand to infer the controlling typevar.
|
||||||
|
self.use_typevar_operand = False
|
||||||
|
typevar_error = None
|
||||||
|
if self.format.typevar_operand is not None:
|
||||||
|
try:
|
||||||
|
tv = self.ins[self.format.typevar_operand].typ
|
||||||
|
if tv is tv.free_typevar():
|
||||||
|
self.other_typevars = self._verify_ctrl_typevar(tv)
|
||||||
|
self.ctrl_typevar = tv
|
||||||
|
self.use_typevar_operand = True
|
||||||
|
except RuntimeError as e:
|
||||||
|
typevar_error = e
|
||||||
|
|
||||||
|
if not self.use_typevar_operand:
|
||||||
|
# The typevar_operand argument doesn't work. Can we infer from the
|
||||||
|
# first result instead?
|
||||||
|
if len(self.outs) == 0:
|
||||||
|
if typevar_error:
|
||||||
|
raise typevar_error
|
||||||
|
else:
|
||||||
|
raise RuntimeError(
|
||||||
|
"typevar_operand must be a free type variable")
|
||||||
|
tv = self.outs[0].typ
|
||||||
|
if tv is not tv.free_typevar():
|
||||||
|
raise RuntimeError("first result must be a free type variable")
|
||||||
|
self.other_typevars = self._verify_ctrl_typevar(tv)
|
||||||
|
self.ctrl_typevar = tv
|
||||||
|
|
||||||
|
def _verify_ctrl_typevar(self, ctrl_typevar):
|
||||||
|
"""
|
||||||
|
Verify that the use of TypeVars is consistent with `ctrl_typevar` as
|
||||||
|
the controlling type variable.
|
||||||
|
|
||||||
|
All polymorhic inputs must either be derived from `ctrl_typevar` or be
|
||||||
|
independent free type variables only used once.
|
||||||
|
|
||||||
|
All polymorphic results must be derived from `ctrl_typevar`.
|
||||||
|
|
||||||
|
Return list of other type variables used, or raise an error.
|
||||||
|
"""
|
||||||
|
other_tvs = []
|
||||||
|
# Check value inputs.
|
||||||
|
for opidx in self.format.value_operands:
|
||||||
|
typ = self.ins[opidx].typ
|
||||||
|
tv = typ.free_typevar()
|
||||||
|
# Non-polymorphic or derived form ctrl_typevar is OK.
|
||||||
|
if tv is None or tv is ctrl_typevar:
|
||||||
|
continue
|
||||||
|
# No other derived typevars allowed.
|
||||||
|
if typ is not tv:
|
||||||
|
raise RuntimeError(
|
||||||
|
"type variable {} must not be derived from {}"
|
||||||
|
.format(typ.name, tv.name))
|
||||||
|
# Other free type variables can only be used once each.
|
||||||
|
if tv in other_tvs:
|
||||||
|
raise RuntimeError(
|
||||||
|
"type variable {} can't be used more than once"
|
||||||
|
.format(tv.name))
|
||||||
|
other_tvs.append(tv)
|
||||||
|
|
||||||
|
# Check outputs.
|
||||||
|
for result in self.outs:
|
||||||
|
typ = result.typ
|
||||||
|
tv = typ.free_typevar()
|
||||||
|
# Non-polymorphic or derived from ctrl_typevar is OK.
|
||||||
|
if tv is None or tv is ctrl_typevar:
|
||||||
|
continue
|
||||||
|
raise RuntimeError(
|
||||||
|
"type variable in output not derived from ctrl_typevar")
|
||||||
|
|
||||||
|
return other_tvs
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _to_operand_tuple(x):
|
def _to_operand_tuple(x):
|
||||||
# Allow a single Operand instance instead of the awkward singleton
|
# Allow a single Operand instance instead of the awkward singleton
|
||||||
|
|||||||
@@ -151,6 +151,12 @@ def gen_opcodes(groups, fmt):
|
|||||||
fmt.doc_comment(
|
fmt.doc_comment(
|
||||||
'`{}{} {}`. ({})'
|
'`{}{} {}`. ({})'
|
||||||
.format(prefix, i.name, suffix, i.format.name))
|
.format(prefix, i.name, suffix, i.format.name))
|
||||||
|
# Document polymorphism.
|
||||||
|
if i.is_polymorphic:
|
||||||
|
if i.use_typevar_operand:
|
||||||
|
fmt.doc_comment(
|
||||||
|
'Type inferred from {}.'
|
||||||
|
.format(i.ins[i.format.typevar_operand]))
|
||||||
# Enum variant itself.
|
# Enum variant itself.
|
||||||
fmt.line(i.camel_name + ',')
|
fmt.line(i.camel_name + ',')
|
||||||
fmt.line()
|
fmt.line()
|
||||||
|
|||||||
Reference in New Issue
Block a user