forked from mirrors/gecko-dev
This is the first time we pin a specific Cranelift commit hash to use in Gecko. The target-lexicon hack is removed and instead we introduce a vendor patch for cranelift-codegen/cranelift-wasm themselves. Notable changes happen in top-level Cargo.toml, .cargo/config.in and js/src/wasm/cranelift/Cargo.toml; the rest has been generated by `mach vendor rust`. Differential Revision: https://phabricator.services.mozilla.com/D27316 --HG-- extra : moz-landing-system : lando
446 lines
16 KiB
Python
446 lines
16 KiB
Python
"""Classes for defining instructions."""
|
|
from __future__ import absolute_import
|
|
from . import camel_case
|
|
from .types import ValueType
|
|
from .operands import Operand
|
|
from .formats import InstructionFormat
|
|
|
|
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, VarAtomMap # noqa
|
|
from .typevar import TypeVar # noqa
|
|
from .ti import TypeConstraint # noqa
|
|
from .xform import XForm, Rtl
|
|
# List of operands for ins/outs:
|
|
OpList = Union[Sequence[Operand], Operand]
|
|
ConstrList = Union[Sequence[TypeConstraint], TypeConstraint]
|
|
MaybeBoundInst = Union['Instruction', 'BoundInstruction']
|
|
InstructionSemantics = Sequence[XForm]
|
|
SemDefCase = Union[Rtl, Tuple[Rtl, Sequence[TypeConstraint]], XForm]
|
|
except ImportError:
|
|
pass
|
|
|
|
|
|
class InstructionGroup(object):
|
|
"""
|
|
Every instruction must belong to exactly one instruction group. A given
|
|
target architecture can support instructions from multiple groups, and it
|
|
does not necessarily support all instructions in a group.
|
|
|
|
New instructions are automatically added to the currently open instruction
|
|
group.
|
|
"""
|
|
|
|
# The currently open instruction group.
|
|
_current = None # type: InstructionGroup
|
|
|
|
def open(self):
|
|
# type: () -> None
|
|
"""
|
|
Open this instruction group such that future new instructions are
|
|
added to this group.
|
|
"""
|
|
assert InstructionGroup._current is None, (
|
|
"Can't open {} since {} is already open"
|
|
.format(self, InstructionGroup._current))
|
|
InstructionGroup._current = self
|
|
|
|
def close(self):
|
|
# type: () -> None
|
|
"""
|
|
Close this instruction group. This function should be called before
|
|
opening another instruction group.
|
|
"""
|
|
assert InstructionGroup._current is self, (
|
|
"Can't close {}, the open instuction group is {}"
|
|
.format(self, InstructionGroup._current))
|
|
InstructionGroup._current = None
|
|
|
|
def __init__(self, name, doc):
|
|
# type: (str, str) -> None
|
|
self.name = name
|
|
self.__doc__ = doc
|
|
self.instructions = [] # type: List[Instruction]
|
|
self.open()
|
|
|
|
@staticmethod
|
|
def append(inst):
|
|
# type: (Instruction) -> None
|
|
assert InstructionGroup._current, \
|
|
"Open an instruction group before defining instructions."
|
|
InstructionGroup._current.instructions.append(inst)
|
|
|
|
|
|
class Instruction(object):
|
|
"""
|
|
The operands to the instruction are specified as two tuples: ``ins`` and
|
|
``outs``. Since the Python singleton tuple syntax is a bit awkward, it is
|
|
allowed to specify a singleton as just the operand itself, i.e., `ins=x`
|
|
and `ins=(x,)` are both allowed and mean the same thing.
|
|
|
|
:param name: Instruction mnemonic, also becomes opcode name.
|
|
:param doc: Documentation string.
|
|
:param ins: Tuple of input operands. This can be a mix of SSA value
|
|
operands and other operand kinds.
|
|
:param outs: Tuple of output operands. The output operands must be SSA
|
|
values or `variable_args`.
|
|
:param constraints: Tuple of instruction-specific TypeConstraints.
|
|
:param is_terminator: This is a terminator instruction.
|
|
:param is_branch: This is a branch instruction.
|
|
:param is_indirect_branch: This is an indirect branch instruction.
|
|
:param is_call: This is a call instruction.
|
|
:param is_return: This is a return instruction.
|
|
:param is_ghost: This is a ghost instruction, which has no encoding and no
|
|
other register allocation constraints.
|
|
:param can_trap: This instruction can trap.
|
|
:param can_load: This instruction can load from memory.
|
|
:param can_store: This instruction can store to memory.
|
|
:param other_side_effects: Instruction has other side effects.
|
|
"""
|
|
|
|
# Boolean instruction attributes that can be passed as keyword arguments to
|
|
# the constructor. Map attribute name to doc comment for generated Rust
|
|
# code.
|
|
ATTRIBS = {
|
|
'is_terminator': 'True for instructions that terminate the EBB.',
|
|
'is_branch': 'True for all branch or jump instructions.',
|
|
'is_indirect_branch':
|
|
'True for all indirect branch or jump instructions.',
|
|
'is_call': 'Is this a call instruction?',
|
|
'is_return': 'Is this a return instruction?',
|
|
'is_ghost': 'Is this a ghost instruction?',
|
|
'can_load': 'Can this instruction read from memory?',
|
|
'can_store': 'Can this instruction write to memory?',
|
|
'can_trap': 'Can this instruction cause a trap?',
|
|
'other_side_effects':
|
|
'Does this instruction have other side effects besides can_*',
|
|
'writes_cpu_flags': 'Does this instruction write to CPU flags?',
|
|
}
|
|
|
|
def __init__(self, name, doc, ins=(), outs=(), constraints=(), **kwargs):
|
|
# type: (str, str, OpList, OpList, ConstrList, **Any) -> None
|
|
self.name = name
|
|
self.camel_name = camel_case(name)
|
|
self.__doc__ = doc
|
|
self.ins = self._to_operand_tuple(ins)
|
|
self.outs = self._to_operand_tuple(outs)
|
|
self.constraints = self._to_constraint_tuple(constraints)
|
|
self.format = InstructionFormat.lookup(self.ins, self.outs)
|
|
self.semantics = None # type: InstructionSemantics
|
|
|
|
# Opcode number, assigned by gen_instr.py.
|
|
self.number = None # type: int
|
|
|
|
# Indexes into `self.outs` for value results.
|
|
# Other results are `variable_args`.
|
|
self.value_results = tuple(
|
|
i for i, o in enumerate(self.outs) if o.is_value())
|
|
# Indexes into `self.ins` for value operands.
|
|
self.value_opnums = tuple(
|
|
i for i, o in enumerate(self.ins) if o.is_value())
|
|
# Indexes into `self.ins` for non-value operands.
|
|
self.imm_opnums = tuple(
|
|
i for i, o in enumerate(self.ins) if o.is_immediate())
|
|
|
|
self._verify_polymorphic()
|
|
for attr in kwargs:
|
|
if attr not in Instruction.ATTRIBS:
|
|
raise AssertionError(
|
|
"unknown instruction attribute '" + attr + "'")
|
|
for attr in Instruction.ATTRIBS:
|
|
setattr(self, attr, not not kwargs.get(attr, False))
|
|
|
|
# Infer the 'writes_cpu_flags' field value.
|
|
if 'writes_cpu_flags' not in kwargs:
|
|
self.writes_cpu_flags = any(
|
|
out.is_cpu_flags() for out in self.outs)
|
|
|
|
InstructionGroup.append(self)
|
|
|
|
def __str__(self):
|
|
# type: () -> str
|
|
prefix = ', '.join(o.name for o in self.outs)
|
|
if prefix:
|
|
prefix = prefix + ' = '
|
|
suffix = ', '.join(o.name for o in self.ins)
|
|
return '{}{} {}'.format(prefix, self.name, suffix)
|
|
|
|
def snake_name(self):
|
|
# type: () -> str
|
|
"""
|
|
Get the snake_case name of this instruction.
|
|
|
|
Keywords in Rust and Python are altered by appending a '_'
|
|
"""
|
|
if self.name == 'return':
|
|
return 'return_'
|
|
else:
|
|
return self.name
|
|
|
|
def blurb(self):
|
|
# type: () -> str
|
|
"""Get the first line of the doc comment"""
|
|
for line in self.__doc__.split('\n'):
|
|
line = line.strip()
|
|
if line:
|
|
return line
|
|
return ""
|
|
|
|
def _verify_polymorphic(self):
|
|
# type: () -> None
|
|
"""
|
|
Check if this instruction is polymorphic, and verify its use of type
|
|
variables.
|
|
"""
|
|
poly_ins = [
|
|
i for i in self.value_opnums
|
|
if self.ins[i].typevar.free_typevar()]
|
|
poly_outs = [
|
|
i for i, o in enumerate(self.outs)
|
|
if o.is_value() and o.typevar.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
|
|
tv_op = self.format.typevar_operand
|
|
if tv_op is not None and tv_op < len(self.value_opnums):
|
|
try:
|
|
opnum = self.value_opnums[tv_op]
|
|
tv = self.ins[opnum].typevar
|
|
if tv is tv.free_typevar() or tv.singleton_type() is not None:
|
|
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].typevar
|
|
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):
|
|
# type: (TypeVar) -> List[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 = [] # type: List[TypeVar]
|
|
# Check value inputs.
|
|
for opnum in self.value_opnums:
|
|
typ = self.ins[opnum].typevar
|
|
tv = typ.free_typevar()
|
|
# Non-polymorphic or derived from 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 be derived from {}"
|
|
.format(self.ins[opnum], typ.name, ctrl_typevar))
|
|
# 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:
|
|
if not result.is_value():
|
|
continue
|
|
typ = result.typevar
|
|
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
|
|
|
|
def all_typevars(self):
|
|
# type: () -> List[TypeVar]
|
|
"""
|
|
Get a list of all type variables in the instruction.
|
|
"""
|
|
if self.is_polymorphic:
|
|
return [self.ctrl_typevar] + self.other_typevars
|
|
else:
|
|
return []
|
|
|
|
@staticmethod
|
|
def _to_operand_tuple(x):
|
|
# type: (Union[Sequence[Operand], Operand]) -> Tuple[Operand, ...]
|
|
# Allow a single Operand instance instead of the awkward singleton
|
|
# tuple syntax.
|
|
if isinstance(x, Operand):
|
|
y = (x,) # type: Tuple[Operand, ...]
|
|
else:
|
|
y = tuple(x)
|
|
for op in y:
|
|
assert isinstance(op, Operand)
|
|
return y
|
|
|
|
@staticmethod
|
|
def _to_constraint_tuple(x):
|
|
# type: (ConstrList) -> Tuple[TypeConstraint, ...]
|
|
"""
|
|
Allow a single TypeConstraint instance instead of the awkward singleton
|
|
tuple syntax.
|
|
"""
|
|
# import placed here to avoid circular dependency
|
|
from .ti import TypeConstraint # noqa
|
|
if isinstance(x, TypeConstraint):
|
|
y = (x,) # type: Tuple[TypeConstraint, ...]
|
|
else:
|
|
y = tuple(x)
|
|
for op in y:
|
|
assert isinstance(op, TypeConstraint)
|
|
return y
|
|
|
|
def bind(self, *args):
|
|
# type: (*ValueType) -> BoundInstruction
|
|
"""
|
|
Bind a polymorphic instruction to a concrete list of type variable
|
|
values.
|
|
"""
|
|
assert self.is_polymorphic
|
|
return BoundInstruction(self, args)
|
|
|
|
def __getattr__(self, name):
|
|
# type: (str) -> BoundInstruction
|
|
"""
|
|
Bind a polymorphic instruction to a single type variable with dot
|
|
syntax:
|
|
|
|
>>> iadd.i32
|
|
"""
|
|
assert name != 'any', 'Wildcard not allowed for ctrl_typevar'
|
|
return self.bind(ValueType.by_name(name))
|
|
|
|
def fully_bound(self):
|
|
# type: () -> Tuple[Instruction, Tuple[ValueType, ...]]
|
|
"""
|
|
Verify that all typevars have been bound, and return a
|
|
`(inst, typevars)` pair.
|
|
|
|
This version in `Instruction` itself allows non-polymorphic
|
|
instructions to duck-type as `BoundInstruction`\\s.
|
|
"""
|
|
assert not self.is_polymorphic, self
|
|
return (self, ())
|
|
|
|
def __call__(self, *args):
|
|
# type: (*Expr) -> Apply
|
|
"""
|
|
Create an `ast.Apply` AST node representing the application of this
|
|
instruction to the arguments.
|
|
"""
|
|
from .ast import Apply # noqa
|
|
return Apply(self, args)
|
|
|
|
def set_semantics(self, src, *dsts):
|
|
# type: (Union[Def, Apply], *SemDefCase) -> None
|
|
"""Set our semantics."""
|
|
from semantics import verify_semantics
|
|
from .xform import XForm, Rtl
|
|
|
|
sem = [] # type: List[XForm]
|
|
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],
|
|
constraints=dst[1]))
|
|
|
|
verify_semantics(self, Rtl(src), sem)
|
|
|
|
self.semantics = sem
|
|
|
|
|
|
class BoundInstruction(object):
|
|
"""
|
|
A polymorphic `Instruction` bound to concrete type variables.
|
|
"""
|
|
|
|
def __init__(self, inst, typevars):
|
|
# type: (Instruction, Tuple[ValueType, ...]) -> None
|
|
self.inst = inst
|
|
self.typevars = typevars
|
|
assert len(typevars) <= 1 + len(inst.other_typevars)
|
|
|
|
def __str__(self):
|
|
# type: () -> str
|
|
return '.'.join([self.inst.name, ] + list(map(str, self.typevars)))
|
|
|
|
def bind(self, *args):
|
|
# type: (*ValueType) -> BoundInstruction
|
|
"""
|
|
Bind additional typevars.
|
|
"""
|
|
return BoundInstruction(self.inst, self.typevars + args)
|
|
|
|
def __getattr__(self, name):
|
|
# type: (str) -> BoundInstruction
|
|
"""
|
|
Bind an additional typevar dot syntax:
|
|
|
|
>>> uext.i32.i8
|
|
"""
|
|
if name == 'any':
|
|
# This is a wild card bind represented as a None type variable.
|
|
return self.bind(None)
|
|
|
|
return self.bind(ValueType.by_name(name))
|
|
|
|
def fully_bound(self):
|
|
# type: () -> Tuple[Instruction, Tuple[ValueType, ...]]
|
|
"""
|
|
Verify that all typevars have been bound, and return a
|
|
`(inst, typevars)` pair.
|
|
"""
|
|
if len(self.typevars) < 1 + len(self.inst.other_typevars):
|
|
unb = ', '.join(
|
|
str(tv) for tv in
|
|
self.inst.other_typevars[len(self.typevars) - 1:])
|
|
raise AssertionError("Unbound typevar {} in {}".format(unb, self))
|
|
assert len(self.typevars) == 1 + len(self.inst.other_typevars)
|
|
return (self.inst, self.typevars)
|
|
|
|
def __call__(self, *args):
|
|
# type: (*Expr) -> Apply
|
|
"""
|
|
Create an `ast.Apply` AST node representing the application of this
|
|
instruction to the arguments.
|
|
"""
|
|
from .ast import Apply # noqa
|
|
return Apply(self, args)
|