moved crates in lib/ to src/, renamed crates, modified some files' text (#660)
moved crates in lib/ to src/, renamed crates, modified some files' text (#660)
This commit is contained in:
1
cranelift/codegen/meta-python/base/__init__.py
Normal file
1
cranelift/codegen/meta-python/base/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Definitions for the base Cranelift language."""
|
||||
38
cranelift/codegen/meta-python/base/entities.py
Normal file
38
cranelift/codegen/meta-python/base/entities.py
Normal file
@@ -0,0 +1,38 @@
|
||||
"""
|
||||
The `cranelift.entities` module predefines all the Cranelift entity reference
|
||||
operand types. There are corresponding definitions in the `cranelift.entities`
|
||||
Rust module.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.operands import EntityRefKind
|
||||
|
||||
|
||||
#: A reference to an extended basic block in the same function.
|
||||
#: This is primarliy used in control flow instructions.
|
||||
ebb = EntityRefKind(
|
||||
'ebb', 'An extended basic block in the same function.',
|
||||
default_member='destination')
|
||||
|
||||
#: A reference to a stack slot declared in the function preamble.
|
||||
stack_slot = EntityRefKind('stack_slot', 'A stack slot.')
|
||||
|
||||
#: A reference to a global value.
|
||||
global_value = EntityRefKind('global_value', 'A global value.')
|
||||
|
||||
#: A reference to a function sugnature declared in the function preamble.
|
||||
#: This is used to provide the call signature in a call_indirect instruction.
|
||||
sig_ref = EntityRefKind('sig_ref', 'A function signature.')
|
||||
|
||||
#: A reference to an external function declared in the function preamble.
|
||||
#: This is used to provide the callee and signature in a call instruction.
|
||||
func_ref = EntityRefKind('func_ref', 'An external function.')
|
||||
|
||||
#: A reference to a jump table declared in the function preamble.
|
||||
jump_table = EntityRefKind(
|
||||
'jump_table', 'A jump table.', default_member='table')
|
||||
|
||||
#: A reference to a heap declared in the function preamble.
|
||||
heap = EntityRefKind('heap', 'A heap.')
|
||||
|
||||
#: A reference to a table declared in the function preamble.
|
||||
table = EntityRefKind('table', 'A table.')
|
||||
89
cranelift/codegen/meta-python/base/formats.py
Normal file
89
cranelift/codegen/meta-python/base/formats.py
Normal file
@@ -0,0 +1,89 @@
|
||||
"""
|
||||
The cranelift.formats defines all instruction formats.
|
||||
|
||||
Every instruction format has a corresponding `InstructionData` variant in the
|
||||
Rust representation of Cranelift IR, so all instruction formats must be defined
|
||||
in this module.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.formats import InstructionFormat
|
||||
from cdsl.operands import VALUE, VARIABLE_ARGS
|
||||
from .immediates import imm64, uimm8, uimm32, ieee32, ieee64, offset32
|
||||
from .immediates import boolean, intcc, floatcc, memflags, regunit, trapcode
|
||||
from . import entities
|
||||
from .entities import ebb, sig_ref, func_ref, stack_slot, heap, table
|
||||
|
||||
Unary = InstructionFormat(VALUE)
|
||||
UnaryImm = InstructionFormat(imm64)
|
||||
UnaryIeee32 = InstructionFormat(ieee32)
|
||||
UnaryIeee64 = InstructionFormat(ieee64)
|
||||
UnaryBool = InstructionFormat(boolean)
|
||||
UnaryGlobalValue = InstructionFormat(entities.global_value)
|
||||
|
||||
Binary = InstructionFormat(VALUE, VALUE)
|
||||
BinaryImm = InstructionFormat(VALUE, imm64)
|
||||
|
||||
# The select instructions are controlled by the second VALUE operand.
|
||||
# The first VALUE operand is the controlling flag which has a derived type.
|
||||
# The fma instruction has the same constraint on all inputs.
|
||||
Ternary = InstructionFormat(VALUE, VALUE, VALUE, typevar_operand=1)
|
||||
|
||||
# Catch-all for instructions with many outputs and inputs and no immediate
|
||||
# operands.
|
||||
MultiAry = InstructionFormat(VARIABLE_ARGS)
|
||||
|
||||
NullAry = InstructionFormat()
|
||||
|
||||
InsertLane = InstructionFormat(VALUE, ('lane', uimm8), VALUE)
|
||||
ExtractLane = InstructionFormat(VALUE, ('lane', uimm8))
|
||||
|
||||
IntCompare = InstructionFormat(intcc, VALUE, VALUE)
|
||||
IntCompareImm = InstructionFormat(intcc, VALUE, imm64)
|
||||
IntCond = InstructionFormat(intcc, VALUE)
|
||||
FloatCompare = InstructionFormat(floatcc, VALUE, VALUE)
|
||||
FloatCond = InstructionFormat(floatcc, VALUE)
|
||||
|
||||
IntSelect = InstructionFormat(intcc, VALUE, VALUE, VALUE)
|
||||
|
||||
Jump = InstructionFormat(ebb, VARIABLE_ARGS)
|
||||
Branch = InstructionFormat(VALUE, ebb, VARIABLE_ARGS)
|
||||
BranchInt = InstructionFormat(intcc, VALUE, ebb, VARIABLE_ARGS)
|
||||
BranchFloat = InstructionFormat(floatcc, VALUE, ebb, VARIABLE_ARGS)
|
||||
BranchIcmp = InstructionFormat(intcc, VALUE, VALUE, ebb, VARIABLE_ARGS)
|
||||
BranchTable = InstructionFormat(VALUE, ebb, entities.jump_table)
|
||||
BranchTableEntry = InstructionFormat(VALUE, VALUE, uimm8, entities.jump_table)
|
||||
BranchTableBase = InstructionFormat(entities.jump_table)
|
||||
IndirectJump = InstructionFormat(VALUE, entities.jump_table)
|
||||
|
||||
Call = InstructionFormat(func_ref, VARIABLE_ARGS)
|
||||
CallIndirect = InstructionFormat(sig_ref, VALUE, VARIABLE_ARGS)
|
||||
FuncAddr = InstructionFormat(func_ref)
|
||||
|
||||
Load = InstructionFormat(memflags, VALUE, offset32)
|
||||
LoadComplex = InstructionFormat(memflags, VARIABLE_ARGS, offset32)
|
||||
Store = InstructionFormat(memflags, VALUE, VALUE, offset32)
|
||||
StoreComplex = InstructionFormat(memflags, VALUE, VARIABLE_ARGS, offset32)
|
||||
|
||||
StackLoad = InstructionFormat(stack_slot, offset32)
|
||||
StackStore = InstructionFormat(VALUE, stack_slot, offset32)
|
||||
|
||||
# Accessing a WebAssembly heap.
|
||||
HeapAddr = InstructionFormat(heap, VALUE, uimm32)
|
||||
|
||||
# Accessing a WebAssembly table.
|
||||
TableAddr = InstructionFormat(table, VALUE, offset32)
|
||||
|
||||
RegMove = InstructionFormat(VALUE, ('src', regunit), ('dst', regunit))
|
||||
CopySpecial = InstructionFormat(('src', regunit), ('dst', regunit))
|
||||
RegSpill = InstructionFormat(
|
||||
VALUE, ('src', regunit), ('dst', entities.stack_slot))
|
||||
RegFill = InstructionFormat(
|
||||
VALUE, ('src', entities.stack_slot), ('dst', regunit))
|
||||
|
||||
Trap = InstructionFormat(trapcode)
|
||||
CondTrap = InstructionFormat(VALUE, trapcode)
|
||||
IntCondTrap = InstructionFormat(intcc, VALUE, trapcode)
|
||||
FloatCondTrap = InstructionFormat(floatcc, VALUE, trapcode)
|
||||
|
||||
# Finally extract the names of global values in this module.
|
||||
InstructionFormat.extract_names(globals())
|
||||
123
cranelift/codegen/meta-python/base/immediates.py
Normal file
123
cranelift/codegen/meta-python/base/immediates.py
Normal file
@@ -0,0 +1,123 @@
|
||||
"""
|
||||
The `cranelift.immediates` module predefines all the Cranelift immediate
|
||||
operand types.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.operands import ImmediateKind
|
||||
|
||||
#: A 64-bit immediate integer operand.
|
||||
#:
|
||||
#: This type of immediate integer can interact with SSA values with any
|
||||
#: :py:class:`cranelift.IntType` type.
|
||||
imm64 = ImmediateKind('imm64', 'A 64-bit immediate integer.')
|
||||
|
||||
#: An unsigned 8-bit immediate integer operand.
|
||||
#:
|
||||
#: This small operand is used to indicate lane indexes in SIMD vectors and
|
||||
#: immediate bit counts on shift instructions.
|
||||
uimm8 = ImmediateKind('uimm8', 'An 8-bit immediate unsigned integer.')
|
||||
|
||||
#: An unsigned 32-bit immediate integer operand.
|
||||
uimm32 = ImmediateKind('uimm32', 'A 32-bit immediate unsigned integer.')
|
||||
|
||||
#: A 32-bit immediate signed offset.
|
||||
#:
|
||||
#: This is used to represent an immediate address offset in load/store
|
||||
#: instructions.
|
||||
offset32 = ImmediateKind(
|
||||
'offset32',
|
||||
'A 32-bit immediate signed offset.',
|
||||
default_member='offset')
|
||||
|
||||
#: A 32-bit immediate floating point operand.
|
||||
#:
|
||||
#: IEEE 754-2008 binary32 interchange format.
|
||||
ieee32 = ImmediateKind('ieee32', 'A 32-bit immediate floating point number.')
|
||||
|
||||
#: A 64-bit immediate floating point operand.
|
||||
#:
|
||||
#: IEEE 754-2008 binary64 interchange format.
|
||||
ieee64 = ImmediateKind('ieee64', 'A 64-bit immediate floating point number.')
|
||||
|
||||
#: An immediate boolean operand.
|
||||
#:
|
||||
#: This type of immediate boolean can interact with SSA values with any
|
||||
#: :py:class:`cranelift.BoolType` type.
|
||||
boolean = ImmediateKind('bool', 'An immediate boolean.',
|
||||
rust_type='bool')
|
||||
|
||||
#: A condition code for comparing integer values.
|
||||
#:
|
||||
#: This enumerated operand kind is used for the :clif:inst:`icmp` instruction
|
||||
#: and corresponds to the `condcodes::IntCC` Rust type.
|
||||
intcc = ImmediateKind(
|
||||
'intcc',
|
||||
'An integer comparison condition code.',
|
||||
default_member='cond',
|
||||
rust_type='ir::condcodes::IntCC',
|
||||
values={
|
||||
'eq': 'Equal',
|
||||
'ne': 'NotEqual',
|
||||
'sge': 'SignedGreaterThanOrEqual',
|
||||
'sgt': 'SignedGreaterThan',
|
||||
'sle': 'SignedLessThanOrEqual',
|
||||
'slt': 'SignedLessThan',
|
||||
'uge': 'UnsignedGreaterThanOrEqual',
|
||||
'ugt': 'UnsignedGreaterThan',
|
||||
'ule': 'UnsignedLessThanOrEqual',
|
||||
'ult': 'UnsignedLessThan',
|
||||
})
|
||||
|
||||
#: A condition code for comparing floating point values.
|
||||
#:
|
||||
#: This enumerated operand kind is used for the :clif:inst:`fcmp` instruction
|
||||
#: and corresponds to the `condcodes::FloatCC` Rust type.
|
||||
floatcc = ImmediateKind(
|
||||
'floatcc',
|
||||
'A floating point comparison condition code.',
|
||||
default_member='cond',
|
||||
rust_type='ir::condcodes::FloatCC',
|
||||
values={
|
||||
'ord': 'Ordered',
|
||||
'uno': 'Unordered',
|
||||
'eq': 'Equal',
|
||||
'ne': 'NotEqual',
|
||||
'one': 'OrderedNotEqual',
|
||||
'ueq': 'UnorderedOrEqual',
|
||||
'lt': 'LessThan',
|
||||
'le': 'LessThanOrEqual',
|
||||
'gt': 'GreaterThan',
|
||||
'ge': 'GreaterThanOrEqual',
|
||||
'ult': 'UnorderedOrLessThan',
|
||||
'ule': 'UnorderedOrLessThanOrEqual',
|
||||
'ugt': 'UnorderedOrGreaterThan',
|
||||
'uge': 'UnorderedOrGreaterThanOrEqual',
|
||||
})
|
||||
|
||||
#: Flags for memory operations like :clif:inst:`load` and :clif:inst:`store`.
|
||||
memflags = ImmediateKind(
|
||||
'memflags',
|
||||
'Memory operation flags',
|
||||
default_member='flags', rust_type='ir::MemFlags')
|
||||
|
||||
#: A register unit in the current target ISA.
|
||||
regunit = ImmediateKind(
|
||||
'regunit',
|
||||
'A register unit in the target ISA',
|
||||
rust_type='isa::RegUnit')
|
||||
|
||||
#: A trap code indicating the reason for trapping.
|
||||
#:
|
||||
#: The Rust enum type also has a `User(u16)` variant for user-provided trap
|
||||
#: codes.
|
||||
trapcode = ImmediateKind(
|
||||
'trapcode',
|
||||
'A trap reason code.',
|
||||
default_member='code',
|
||||
rust_type='ir::TrapCode',
|
||||
values={
|
||||
"stk_ovf": 'StackOverflow',
|
||||
"heap_oob": 'HeapOutOfBounds',
|
||||
"int_ovf": 'IntegerOverflow',
|
||||
"int_divz": 'IntegerDivisionByZero',
|
||||
})
|
||||
2038
cranelift/codegen/meta-python/base/instructions.py
Normal file
2038
cranelift/codegen/meta-python/base/instructions.py
Normal file
File diff suppressed because it is too large
Load Diff
708
cranelift/codegen/meta-python/base/legalize.py
Normal file
708
cranelift/codegen/meta-python/base/legalize.py
Normal file
@@ -0,0 +1,708 @@
|
||||
"""
|
||||
Patterns for legalizing the `base` instruction set.
|
||||
|
||||
The base Cranelift instruction set is 'fat', and many instructions don't have
|
||||
legal representations in a given target ISA. This module defines legalization
|
||||
patterns that describe how base instructions can be transformed to other base
|
||||
instructions that are legal.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from .immediates import intcc, imm64, ieee32, ieee64
|
||||
from . import instructions as insts
|
||||
from . import types
|
||||
from .instructions import uextend, sextend, ireduce
|
||||
from .instructions import iadd, iadd_cout, iadd_cin, iadd_carry, iadd_imm
|
||||
from .instructions import isub, isub_bin, isub_bout, isub_borrow, irsub_imm
|
||||
from .instructions import imul, imul_imm
|
||||
from .instructions import sdiv, sdiv_imm, udiv, udiv_imm
|
||||
from .instructions import srem, srem_imm, urem, urem_imm
|
||||
from .instructions import band, bor, bxor, isplit, iconcat
|
||||
from .instructions import bnot, band_not, bor_not, bxor_not
|
||||
from .instructions import band_imm, bor_imm, bxor_imm
|
||||
from .instructions import icmp, icmp_imm, ifcmp, ifcmp_imm
|
||||
from .instructions import iconst, bint, select
|
||||
from .instructions import ishl, ishl_imm, sshr, sshr_imm, ushr, ushr_imm
|
||||
from .instructions import rotl, rotl_imm, rotr, rotr_imm
|
||||
from .instructions import f32const, f64const
|
||||
from .instructions import store, load
|
||||
from .instructions import br_table
|
||||
from .instructions import bitrev
|
||||
from cdsl.ast import Var
|
||||
from cdsl.xform import Rtl, XFormGroup
|
||||
|
||||
try:
|
||||
from typing import TYPE_CHECKING # noqa
|
||||
if TYPE_CHECKING:
|
||||
from cdsl.instructions import Instruction # noqa
|
||||
except ImportError:
|
||||
TYPE_CHECKING = False
|
||||
|
||||
|
||||
narrow = XFormGroup('narrow', """
|
||||
Legalize instructions by narrowing.
|
||||
|
||||
The transformations in the 'narrow' group work by expressing
|
||||
instructions in terms of smaller types. Operations on vector types are
|
||||
expressed in terms of vector types with fewer lanes, and integer
|
||||
operations are expressed in terms of smaller integer types.
|
||||
""")
|
||||
|
||||
widen = XFormGroup('widen', """
|
||||
Legalize instructions by widening.
|
||||
|
||||
The transformations in the 'widen' group work by expressing
|
||||
instructions in terms of larger types.
|
||||
""")
|
||||
|
||||
expand = XFormGroup('expand', """
|
||||
Legalize instructions by expansion.
|
||||
|
||||
Rewrite instructions in terms of other instructions, generally
|
||||
operating on the same types as the original instructions.
|
||||
""")
|
||||
|
||||
expand_flags = XFormGroup('expand_flags', """
|
||||
Instruction expansions for architectures with flags.
|
||||
|
||||
Expand some instructions using CPU flags, then fall back to the normal
|
||||
expansions. Not all architectures support CPU flags, so these patterns
|
||||
are kept separate.
|
||||
""", chain=expand)
|
||||
|
||||
|
||||
# Custom expansions for memory objects.
|
||||
expand.custom_legalize(insts.global_value, 'expand_global_value')
|
||||
expand.custom_legalize(insts.heap_addr, 'expand_heap_addr')
|
||||
expand.custom_legalize(insts.table_addr, 'expand_table_addr')
|
||||
|
||||
# Custom expansions for calls.
|
||||
expand.custom_legalize(insts.call, 'expand_call')
|
||||
|
||||
# Custom expansions that need to change the CFG.
|
||||
# TODO: Add sufficient XForm syntax that we don't need to hand-code these.
|
||||
expand.custom_legalize(insts.trapz, 'expand_cond_trap')
|
||||
expand.custom_legalize(insts.trapnz, 'expand_cond_trap')
|
||||
expand.custom_legalize(insts.br_table, 'expand_br_table')
|
||||
expand.custom_legalize(insts.select, 'expand_select')
|
||||
|
||||
# Custom expansions for floating point constants.
|
||||
# These expansions require bit-casting or creating constant pool entries.
|
||||
expand.custom_legalize(insts.f32const, 'expand_fconst')
|
||||
expand.custom_legalize(insts.f64const, 'expand_fconst')
|
||||
|
||||
# Custom expansions for stack memory accesses.
|
||||
expand.custom_legalize(insts.stack_load, 'expand_stack_load')
|
||||
expand.custom_legalize(insts.stack_store, 'expand_stack_store')
|
||||
|
||||
x = Var('x')
|
||||
y = Var('y')
|
||||
z = Var('z')
|
||||
a = Var('a')
|
||||
a1 = Var('a1')
|
||||
a2 = Var('a2')
|
||||
a3 = Var('a3')
|
||||
a4 = Var('a4')
|
||||
b = Var('b')
|
||||
b1 = Var('b1')
|
||||
b2 = Var('b2')
|
||||
b3 = Var('b3')
|
||||
b4 = Var('b4')
|
||||
b_in = Var('b_in')
|
||||
b_int = Var('b_int')
|
||||
c = Var('c')
|
||||
c1 = Var('c1')
|
||||
c2 = Var('c2')
|
||||
c3 = Var('c3')
|
||||
c4 = Var('c4')
|
||||
c_in = Var('c_in')
|
||||
c_int = Var('c_int')
|
||||
d = Var('d')
|
||||
d1 = Var('d1')
|
||||
d2 = Var('d2')
|
||||
d3 = Var('d3')
|
||||
d4 = Var('d4')
|
||||
e = Var('e')
|
||||
e1 = Var('e1')
|
||||
e2 = Var('e2')
|
||||
e3 = Var('e3')
|
||||
e4 = Var('e4')
|
||||
f = Var('f')
|
||||
f1 = Var('f1')
|
||||
f2 = Var('f2')
|
||||
xl = Var('xl')
|
||||
xh = Var('xh')
|
||||
yl = Var('yl')
|
||||
yh = Var('yh')
|
||||
al = Var('al')
|
||||
ah = Var('ah')
|
||||
cc = Var('cc')
|
||||
ptr = Var('ptr')
|
||||
flags = Var('flags')
|
||||
offset = Var('off')
|
||||
ss = Var('ss')
|
||||
|
||||
narrow.legalize(
|
||||
a << iadd(x, y),
|
||||
Rtl(
|
||||
(xl, xh) << isplit(x),
|
||||
(yl, yh) << isplit(y),
|
||||
(al, c) << iadd_cout(xl, yl),
|
||||
ah << iadd_cin(xh, yh, c),
|
||||
a << iconcat(al, ah)
|
||||
))
|
||||
|
||||
narrow.legalize(
|
||||
a << isub(x, y),
|
||||
Rtl(
|
||||
(xl, xh) << isplit(x),
|
||||
(yl, yh) << isplit(y),
|
||||
(al, b) << isub_bout(xl, yl),
|
||||
ah << isub_bin(xh, yh, b),
|
||||
a << iconcat(al, ah)
|
||||
))
|
||||
|
||||
for bitop in [band, bor, bxor]:
|
||||
narrow.legalize(
|
||||
a << bitop(x, y),
|
||||
Rtl(
|
||||
(xl, xh) << isplit(x),
|
||||
(yl, yh) << isplit(y),
|
||||
al << bitop(xl, yl),
|
||||
ah << bitop(xh, yh),
|
||||
a << iconcat(al, ah)
|
||||
))
|
||||
|
||||
narrow.legalize(
|
||||
a << select(c, x, y),
|
||||
Rtl(
|
||||
(xl, xh) << isplit(x),
|
||||
(yl, yh) << isplit(y),
|
||||
al << select(c, xl, yl),
|
||||
ah << select(c, xh, yh),
|
||||
a << iconcat(al, ah)
|
||||
))
|
||||
|
||||
|
||||
def widen_one_arg(signed, op):
|
||||
# type: (bool, Instruction) -> None
|
||||
for int_ty in [types.i8, types.i16]:
|
||||
if signed:
|
||||
widen.legalize(
|
||||
a << op.bind(int_ty)(b),
|
||||
Rtl(
|
||||
x << sextend.i32(b),
|
||||
z << op.i32(x),
|
||||
a << ireduce.bind(int_ty)(z)
|
||||
))
|
||||
else:
|
||||
widen.legalize(
|
||||
a << op.bind(int_ty)(b),
|
||||
Rtl(
|
||||
x << uextend.i32(b),
|
||||
z << op.i32(x),
|
||||
a << ireduce.bind(int_ty)(z)
|
||||
))
|
||||
|
||||
|
||||
def widen_two_arg(signed, op):
|
||||
# type: (bool, Instruction) -> None
|
||||
for int_ty in [types.i8, types.i16]:
|
||||
if signed:
|
||||
widen.legalize(
|
||||
a << op.bind(int_ty)(b, c),
|
||||
Rtl(
|
||||
x << sextend.i32(b),
|
||||
y << sextend.i32(c),
|
||||
z << op.i32(x, y),
|
||||
a << ireduce.bind(int_ty)(z)
|
||||
))
|
||||
else:
|
||||
widen.legalize(
|
||||
a << op.bind(int_ty)(b, c),
|
||||
Rtl(
|
||||
x << uextend.i32(b),
|
||||
y << uextend.i32(c),
|
||||
z << op.i32(x, y),
|
||||
a << ireduce.bind(int_ty)(z)
|
||||
))
|
||||
|
||||
|
||||
def widen_imm(signed, op):
|
||||
# type: (bool, Instruction) -> None
|
||||
for int_ty in [types.i8, types.i16]:
|
||||
if signed:
|
||||
widen.legalize(
|
||||
a << op.bind(int_ty)(b, c),
|
||||
Rtl(
|
||||
x << sextend.i32(b),
|
||||
z << op.i32(x, c),
|
||||
a << ireduce.bind(int_ty)(z)
|
||||
))
|
||||
else:
|
||||
widen.legalize(
|
||||
a << op.bind(int_ty)(b, c),
|
||||
Rtl(
|
||||
x << uextend.i32(b),
|
||||
z << op.i32(x, c),
|
||||
a << ireduce.bind(int_ty)(z)
|
||||
))
|
||||
|
||||
|
||||
# int ops
|
||||
for binop in [iadd, isub, imul, udiv, urem]:
|
||||
widen_two_arg(False, binop)
|
||||
|
||||
for binop in [sdiv, srem]:
|
||||
widen_two_arg(True, binop)
|
||||
|
||||
for binop in [iadd_imm, imul_imm, udiv_imm, urem_imm]:
|
||||
widen_imm(False, binop)
|
||||
|
||||
for binop in [sdiv_imm, srem_imm]:
|
||||
widen_imm(True, binop)
|
||||
|
||||
widen_imm(False, irsub_imm)
|
||||
|
||||
# bit ops
|
||||
widen_one_arg(False, bnot)
|
||||
|
||||
for binop in [band, bor, bxor, band_not, bor_not, bxor_not]:
|
||||
widen_two_arg(False, binop)
|
||||
|
||||
for binop in [band_imm, bor_imm, bxor_imm]:
|
||||
widen_imm(False, binop)
|
||||
|
||||
widen_one_arg(False, insts.popcnt)
|
||||
|
||||
for (int_ty, num) in [(types.i8, 24), (types.i16, 16)]:
|
||||
widen.legalize(
|
||||
a << insts.clz.bind(int_ty)(b),
|
||||
Rtl(
|
||||
c << uextend.i32(b),
|
||||
d << insts.clz.i32(c),
|
||||
e << iadd_imm(d, imm64(-num)),
|
||||
a << ireduce.bind(int_ty)(e)
|
||||
))
|
||||
|
||||
widen.legalize(
|
||||
a << insts.cls.bind(int_ty)(b),
|
||||
Rtl(
|
||||
c << sextend.i32(b),
|
||||
d << insts.cls.i32(c),
|
||||
e << iadd_imm(d, imm64(-num)),
|
||||
a << ireduce.bind(int_ty)(e)
|
||||
))
|
||||
|
||||
for (int_ty, num) in [(types.i8, 1 << 8), (types.i16, 1 << 16)]:
|
||||
widen.legalize(
|
||||
a << insts.ctz.bind(int_ty)(b),
|
||||
Rtl(
|
||||
c << uextend.i32(b),
|
||||
# When `b` is zero, returns the size of x in bits.
|
||||
d << bor_imm(c, imm64(num)),
|
||||
e << insts.ctz.i32(d),
|
||||
a << ireduce.bind(int_ty)(e)
|
||||
))
|
||||
|
||||
# iconst
|
||||
for int_ty in [types.i8, types.i16]:
|
||||
widen.legalize(
|
||||
a << iconst.bind(int_ty)(b),
|
||||
Rtl(
|
||||
c << iconst.i32(b),
|
||||
a << ireduce.bind(int_ty)(c)
|
||||
))
|
||||
|
||||
widen.legalize(
|
||||
a << uextend.i16.i8(b),
|
||||
Rtl(
|
||||
c << uextend.i32(b),
|
||||
a << ireduce(c)
|
||||
))
|
||||
|
||||
widen.legalize(
|
||||
a << sextend.i16.i8(b),
|
||||
Rtl(
|
||||
c << sextend.i32(b),
|
||||
a << ireduce(c)
|
||||
))
|
||||
|
||||
|
||||
widen.legalize(
|
||||
store.i8(flags, a, ptr, offset),
|
||||
Rtl(
|
||||
b << uextend.i32(a),
|
||||
insts.istore8(flags, b, ptr, offset)
|
||||
))
|
||||
|
||||
widen.legalize(
|
||||
store.i16(flags, a, ptr, offset),
|
||||
Rtl(
|
||||
b << uextend.i32(a),
|
||||
insts.istore16(flags, b, ptr, offset)
|
||||
))
|
||||
|
||||
widen.legalize(
|
||||
a << load.i8(flags, ptr, offset),
|
||||
Rtl(
|
||||
b << insts.uload8.i32(flags, ptr, offset),
|
||||
a << ireduce(b)
|
||||
))
|
||||
|
||||
widen.legalize(
|
||||
a << load.i16(flags, ptr, offset),
|
||||
Rtl(
|
||||
b << insts.uload16.i32(flags, ptr, offset),
|
||||
a << ireduce(b)
|
||||
))
|
||||
|
||||
for int_ty in [types.i8, types.i16]:
|
||||
widen.legalize(
|
||||
br_table.bind(int_ty)(x, y, z),
|
||||
Rtl(
|
||||
b << uextend.i32(x),
|
||||
br_table(b, y, z),
|
||||
)
|
||||
)
|
||||
|
||||
for int_ty in [types.i8, types.i16]:
|
||||
widen.legalize(
|
||||
a << insts.bint.bind(int_ty)(b),
|
||||
Rtl(
|
||||
x << insts.bint.i32(b),
|
||||
a << ireduce.bind(int_ty)(x)
|
||||
)
|
||||
)
|
||||
|
||||
for int_ty in [types.i8, types.i16]:
|
||||
for op in [ushr_imm, ishl_imm]:
|
||||
widen.legalize(
|
||||
a << op.bind(int_ty)(b, c),
|
||||
Rtl(
|
||||
x << uextend.i32(b),
|
||||
z << op.i32(x, c),
|
||||
a << ireduce.bind(int_ty)(z)
|
||||
))
|
||||
|
||||
widen.legalize(
|
||||
a << ishl.bind(int_ty)(b, c),
|
||||
Rtl(
|
||||
x << uextend.i32(b),
|
||||
z << ishl.i32(x, c),
|
||||
a << ireduce.bind(int_ty)(z)
|
||||
))
|
||||
|
||||
widen.legalize(
|
||||
a << ushr.bind(int_ty)(b, c),
|
||||
Rtl(
|
||||
x << uextend.i32(b),
|
||||
z << ushr.i32(x, c),
|
||||
a << ireduce.bind(int_ty)(z)
|
||||
))
|
||||
|
||||
widen.legalize(
|
||||
a << sshr.bind(int_ty)(b, c),
|
||||
Rtl(
|
||||
x << sextend.i32(b),
|
||||
z << sshr.i32(x, c),
|
||||
a << ireduce.bind(int_ty)(z)
|
||||
))
|
||||
|
||||
for w_cc in [
|
||||
intcc.eq, intcc.ne, intcc.ugt, intcc.ult, intcc.uge, intcc.ule
|
||||
]:
|
||||
widen.legalize(
|
||||
a << insts.icmp_imm.bind(int_ty)(w_cc, b, c),
|
||||
Rtl(
|
||||
x << uextend.i32(b),
|
||||
a << insts.icmp_imm(w_cc, x, c)
|
||||
))
|
||||
widen.legalize(
|
||||
a << insts.icmp.bind(int_ty)(w_cc, b, c),
|
||||
Rtl(
|
||||
x << uextend.i32(b),
|
||||
y << uextend.i32(c),
|
||||
a << insts.icmp.i32(w_cc, x, y)
|
||||
))
|
||||
for w_cc in [intcc.sgt, intcc.slt, intcc.sge, intcc.sle]:
|
||||
widen.legalize(
|
||||
a << insts.icmp_imm.bind(int_ty)(w_cc, b, c),
|
||||
Rtl(
|
||||
x << sextend.i32(b),
|
||||
a << insts.icmp_imm(w_cc, x, c)
|
||||
))
|
||||
widen.legalize(
|
||||
a << insts.icmp.bind(int_ty)(w_cc, b, c),
|
||||
Rtl(
|
||||
x << sextend.i32(b),
|
||||
y << sextend.i32(c),
|
||||
a << insts.icmp(w_cc, x, y)
|
||||
)
|
||||
)
|
||||
|
||||
# Expand integer operations with carry for RISC architectures that don't have
|
||||
# the flags.
|
||||
expand.legalize(
|
||||
(a, c) << iadd_cout(x, y),
|
||||
Rtl(
|
||||
a << iadd(x, y),
|
||||
c << icmp(intcc.ult, a, x)
|
||||
))
|
||||
|
||||
expand.legalize(
|
||||
(a, b) << isub_bout(x, y),
|
||||
Rtl(
|
||||
a << isub(x, y),
|
||||
b << icmp(intcc.ugt, a, x)
|
||||
))
|
||||
|
||||
expand.legalize(
|
||||
a << iadd_cin(x, y, c),
|
||||
Rtl(
|
||||
a1 << iadd(x, y),
|
||||
c_int << bint(c),
|
||||
a << iadd(a1, c_int)
|
||||
))
|
||||
|
||||
expand.legalize(
|
||||
a << isub_bin(x, y, b),
|
||||
Rtl(
|
||||
a1 << isub(x, y),
|
||||
b_int << bint(b),
|
||||
a << isub(a1, b_int)
|
||||
))
|
||||
|
||||
expand.legalize(
|
||||
(a, c) << iadd_carry(x, y, c_in),
|
||||
Rtl(
|
||||
(a1, c1) << iadd_cout(x, y),
|
||||
c_int << bint(c_in),
|
||||
(a, c2) << iadd_cout(a1, c_int),
|
||||
c << bor(c1, c2)
|
||||
))
|
||||
|
||||
expand.legalize(
|
||||
(a, b) << isub_borrow(x, y, b_in),
|
||||
Rtl(
|
||||
(a1, b1) << isub_bout(x, y),
|
||||
b_int << bint(b_in),
|
||||
(a, b2) << isub_bout(a1, b_int),
|
||||
b << bor(b1, b2)
|
||||
))
|
||||
|
||||
# Expansions for immediate operands that are out of range.
|
||||
for inst_imm, inst in [
|
||||
(iadd_imm, iadd),
|
||||
(imul_imm, imul),
|
||||
(sdiv_imm, sdiv),
|
||||
(udiv_imm, udiv),
|
||||
(srem_imm, srem),
|
||||
(urem_imm, urem),
|
||||
(band_imm, band),
|
||||
(bor_imm, bor),
|
||||
(bxor_imm, bxor),
|
||||
(ifcmp_imm, ifcmp)]:
|
||||
expand.legalize(
|
||||
a << inst_imm(x, y),
|
||||
Rtl(
|
||||
a1 << iconst(y),
|
||||
a << inst(x, a1)
|
||||
))
|
||||
expand.legalize(
|
||||
a << irsub_imm(y, x),
|
||||
Rtl(
|
||||
a1 << iconst(x),
|
||||
a << isub(a1, y)
|
||||
))
|
||||
|
||||
# Rotates and shifts.
|
||||
for inst_imm, inst in [
|
||||
(rotl_imm, rotl),
|
||||
(rotr_imm, rotr),
|
||||
(ishl_imm, ishl),
|
||||
(sshr_imm, sshr),
|
||||
(ushr_imm, ushr)]:
|
||||
expand.legalize(
|
||||
a << inst_imm(x, y),
|
||||
Rtl(
|
||||
a1 << iconst.i32(y),
|
||||
a << inst(x, a1)
|
||||
))
|
||||
|
||||
expand.legalize(
|
||||
a << icmp_imm(cc, x, y),
|
||||
Rtl(
|
||||
a1 << iconst(y),
|
||||
a << icmp(cc, x, a1)
|
||||
))
|
||||
|
||||
# Expansions for *_not variants of bitwise ops.
|
||||
for inst_not, inst in [
|
||||
(band_not, band),
|
||||
(bor_not, bor),
|
||||
(bxor_not, bxor)]:
|
||||
expand.legalize(
|
||||
a << inst_not(x, y),
|
||||
Rtl(
|
||||
a1 << bnot(y),
|
||||
a << inst(x, a1)
|
||||
))
|
||||
|
||||
# Expand bnot using xor.
|
||||
expand.legalize(
|
||||
a << bnot(x),
|
||||
Rtl(
|
||||
y << iconst(imm64(-1)),
|
||||
a << bxor(x, y)
|
||||
))
|
||||
|
||||
# Expand bitrev
|
||||
# Adapted from Stack Overflow.
|
||||
# https://stackoverflow.com/questions/746171/most-efficient-algorithm-for-bit-reversal-from-msb-lsb-to-lsb-msb-in-c
|
||||
widen.legalize(
|
||||
a << bitrev.i8(x),
|
||||
Rtl(
|
||||
a1 << band_imm(x, imm64(0xaa)),
|
||||
a2 << ushr_imm(a1, imm64(1)),
|
||||
a3 << band_imm(x, imm64(0x55)),
|
||||
a4 << ishl_imm(a3, imm64(1)),
|
||||
b << bor(a2, a4),
|
||||
b1 << band_imm(b, imm64(0xcc)),
|
||||
b2 << ushr_imm(b1, imm64(2)),
|
||||
b3 << band_imm(b, imm64(0x33)),
|
||||
b4 << ushr_imm(b3, imm64(2)),
|
||||
c << bor(b2, b4),
|
||||
c1 << band_imm(c, imm64(0xf0)),
|
||||
c2 << ushr_imm(c1, imm64(4)),
|
||||
c3 << band_imm(c, imm64(0x0f)),
|
||||
c4 << ishl_imm(c3, imm64(4)),
|
||||
a << bor(c2, c4),
|
||||
))
|
||||
|
||||
widen.legalize(
|
||||
a << bitrev.i16(x),
|
||||
Rtl(
|
||||
a1 << band_imm(x, imm64(0xaaaa)),
|
||||
a2 << ushr_imm(a1, imm64(1)),
|
||||
a3 << band_imm(x, imm64(0x5555)),
|
||||
a4 << ishl_imm(a3, imm64(1)),
|
||||
b << bor(a2, a4),
|
||||
b1 << band_imm(b, imm64(0xcccc)),
|
||||
b2 << ushr_imm(b1, imm64(2)),
|
||||
b3 << band_imm(b, imm64(0x3333)),
|
||||
b4 << ushr_imm(b3, imm64(2)),
|
||||
c << bor(b2, b4),
|
||||
c1 << band_imm(c, imm64(0xf0f0)),
|
||||
c2 << ushr_imm(c1, imm64(4)),
|
||||
c3 << band_imm(c, imm64(0x0f0f)),
|
||||
c4 << ishl_imm(c3, imm64(4)),
|
||||
d << bor(c2, c4),
|
||||
d1 << band_imm(d, imm64(0xff00)),
|
||||
d2 << ushr_imm(d1, imm64(8)),
|
||||
d3 << band_imm(d, imm64(0x00ff)),
|
||||
d4 << ishl_imm(d3, imm64(8)),
|
||||
a << bor(d2, d4),
|
||||
))
|
||||
|
||||
expand.legalize(
|
||||
a << bitrev.i32(x),
|
||||
Rtl(
|
||||
a1 << band_imm(x, imm64(0xaaaaaaaa)),
|
||||
a2 << ushr_imm(a1, imm64(1)),
|
||||
a3 << band_imm(x, imm64(0x55555555)),
|
||||
a4 << ishl_imm(a3, imm64(1)),
|
||||
b << bor(a2, a4),
|
||||
b1 << band_imm(b, imm64(0xcccccccc)),
|
||||
b2 << ushr_imm(b1, imm64(2)),
|
||||
b3 << band_imm(b, imm64(0x33333333)),
|
||||
b4 << ushr_imm(b3, imm64(2)),
|
||||
c << bor(b2, b4),
|
||||
c1 << band_imm(c, imm64(0xf0f0f0f0)),
|
||||
c2 << ushr_imm(c1, imm64(4)),
|
||||
c3 << band_imm(c, imm64(0x0f0f0f0f)),
|
||||
c4 << ishl_imm(c3, imm64(4)),
|
||||
d << bor(c2, c4),
|
||||
d1 << band_imm(d, imm64(0xff00ff00)),
|
||||
d2 << ushr_imm(d1, imm64(8)),
|
||||
d3 << band_imm(d, imm64(0x00ff00ff)),
|
||||
d4 << ishl_imm(d3, imm64(8)),
|
||||
e << bor(d2, d4),
|
||||
e1 << ushr_imm(e, imm64(16)),
|
||||
e2 << ishl_imm(e, imm64(16)),
|
||||
a << bor(e1, e2),
|
||||
))
|
||||
|
||||
expand.legalize(
|
||||
a << bitrev.i64(x),
|
||||
Rtl(
|
||||
a1 << band_imm(x, imm64(0xaaaaaaaaaaaaaaaa)),
|
||||
a2 << ushr_imm(a1, imm64(1)),
|
||||
a3 << band_imm(x, imm64(0x5555555555555555)),
|
||||
a4 << ishl_imm(a3, imm64(1)),
|
||||
b << bor(a2, a4),
|
||||
b1 << band_imm(b, imm64(0xcccccccccccccccc)),
|
||||
b2 << ushr_imm(b1, imm64(2)),
|
||||
b3 << band_imm(b, imm64(0x3333333333333333)),
|
||||
b4 << ushr_imm(b3, imm64(2)),
|
||||
c << bor(b2, b4),
|
||||
c1 << band_imm(c, imm64(0xf0f0f0f0f0f0f0f0)),
|
||||
c2 << ushr_imm(c1, imm64(4)),
|
||||
c3 << band_imm(c, imm64(0x0f0f0f0f0f0f0f0f)),
|
||||
c4 << ishl_imm(c3, imm64(4)),
|
||||
d << bor(c2, c4),
|
||||
d1 << band_imm(d, imm64(0xff00ff00ff00ff00)),
|
||||
d2 << ushr_imm(d1, imm64(8)),
|
||||
d3 << band_imm(d, imm64(0x00ff00ff00ff00ff)),
|
||||
d4 << ishl_imm(d3, imm64(8)),
|
||||
e << bor(d2, d4),
|
||||
e1 << band_imm(e, imm64(0xffff0000ffff0000)),
|
||||
e2 << ushr_imm(e1, imm64(16)),
|
||||
e3 << band_imm(e, imm64(0x0000ffff0000ffff)),
|
||||
e4 << ishl_imm(e3, imm64(16)),
|
||||
f << bor(e2, e4),
|
||||
f1 << ushr_imm(f, imm64(32)),
|
||||
f2 << ishl_imm(f, imm64(32)),
|
||||
a << bor(f1, f2),
|
||||
))
|
||||
|
||||
# Floating-point sign manipulations.
|
||||
for ty, minus_zero in [
|
||||
(types.f32, f32const(ieee32.bits(0x80000000))),
|
||||
(types.f64, f64const(ieee64.bits(0x8000000000000000)))]:
|
||||
expand.legalize(
|
||||
a << insts.fabs.bind(ty)(x),
|
||||
Rtl(
|
||||
b << minus_zero,
|
||||
a << band_not(x, b),
|
||||
))
|
||||
expand.legalize(
|
||||
a << insts.fneg.bind(ty)(x),
|
||||
Rtl(
|
||||
b << minus_zero,
|
||||
a << bxor(x, b),
|
||||
))
|
||||
expand.legalize(
|
||||
a << insts.fcopysign.bind(ty)(x, y),
|
||||
Rtl(
|
||||
b << minus_zero,
|
||||
a1 << band_not(x, b),
|
||||
a2 << band(y, b),
|
||||
a << bor(a1, a2)
|
||||
))
|
||||
|
||||
expand.custom_legalize(insts.br_icmp, 'expand_br_icmp')
|
||||
|
||||
# Expansions using CPU flags.
|
||||
|
||||
expand_flags.legalize(
|
||||
insts.trapnz(x, c),
|
||||
Rtl(
|
||||
a << insts.ifcmp_imm(x, imm64(0)),
|
||||
insts.trapif(intcc.ne, a, c)
|
||||
))
|
||||
expand_flags.legalize(
|
||||
insts.trapz(x, c),
|
||||
Rtl(
|
||||
a << insts.ifcmp_imm(x, imm64(0)),
|
||||
insts.trapif(intcc.eq, a, c)
|
||||
))
|
||||
42
cranelift/codegen/meta-python/base/predicates.py
Normal file
42
cranelift/codegen/meta-python/base/predicates.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""
|
||||
Cranelift predicates that consider `Function` fields.
|
||||
"""
|
||||
from cdsl.predicates import FieldPredicate
|
||||
from .formats import UnaryGlobalValue, InstructionFormat
|
||||
|
||||
try:
|
||||
from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from cdsl.formats import InstructionFormat, FormatField # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class IsColocatedFunc(FieldPredicate):
|
||||
"""
|
||||
An instruction predicate that checks the referenced function is colocated.
|
||||
"""
|
||||
|
||||
def __init__(self, field):
|
||||
# type: (FormatField) -> None
|
||||
super(IsColocatedFunc, self).__init__(
|
||||
field, 'is_colocated_func', ('func',))
|
||||
|
||||
|
||||
class IsColocatedData(FieldPredicate):
|
||||
"""
|
||||
An instruction predicate that checks the referenced data object is
|
||||
colocated.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# type: () -> None
|
||||
super(IsColocatedData, self).__init__(
|
||||
UnaryGlobalValue.global_value, 'is_colocated_data', ('func',))
|
||||
|
||||
|
||||
class LengthEquals(FieldPredicate):
|
||||
def __init__(self, iform, num):
|
||||
# type: (InstructionFormat, int) -> None
|
||||
super(LengthEquals, self).__init__(
|
||||
iform.args(), 'has_length_of', (num, 'func'))
|
||||
218
cranelift/codegen/meta-python/base/semantics.py
Normal file
218
cranelift/codegen/meta-python/base/semantics.py
Normal file
@@ -0,0 +1,218 @@
|
||||
from __future__ import absolute_import
|
||||
from semantics.primitives import prim_to_bv, prim_from_bv, bvsplit, bvconcat,\
|
||||
bvadd, bvzeroext, bvsignext
|
||||
from semantics.primitives import bveq, bvne, bvsge, bvsgt, bvsle, bvslt,\
|
||||
bvuge, bvugt, bvule, bvult
|
||||
from semantics.macros import bool2bv
|
||||
from .instructions import vsplit, vconcat, iadd, iadd_cout, icmp, bextend, \
|
||||
isplit, iconcat, iadd_cin, iadd_carry
|
||||
from .immediates import intcc
|
||||
from cdsl.xform import Rtl, XForm
|
||||
from cdsl.ast import Var
|
||||
from cdsl.typevar import TypeSet
|
||||
from cdsl.ti import InTypeset
|
||||
|
||||
try:
|
||||
from typing import TYPE_CHECKING # noqa
|
||||
if TYPE_CHECKING:
|
||||
from cdsl.ast import Enumerator # noqa
|
||||
from cdsl.instructions import Instruction # noqa
|
||||
except ImportError:
|
||||
TYPE_CHECKING = False
|
||||
|
||||
x = Var('x')
|
||||
y = Var('y')
|
||||
a = Var('a')
|
||||
b = Var('b')
|
||||
c_out = Var('c_out')
|
||||
c_in = Var('c_in')
|
||||
CC = Var('CC')
|
||||
bc_out = Var('bc_out')
|
||||
bvc_out = Var('bvc_out')
|
||||
bvc_in = Var('bvc_in')
|
||||
xhi = Var('xhi')
|
||||
yhi = Var('yhi')
|
||||
ahi = Var('ahi')
|
||||
bhi = Var('bhi')
|
||||
xlo = Var('xlo')
|
||||
ylo = Var('ylo')
|
||||
alo = Var('alo')
|
||||
blo = Var('blo')
|
||||
lo = Var('lo')
|
||||
hi = Var('hi')
|
||||
bvx = Var('bvx')
|
||||
bvy = Var('bvy')
|
||||
bva = Var('bva')
|
||||
bvt = Var('bvt')
|
||||
bvs = Var('bvs')
|
||||
bva_wide = Var('bva_wide')
|
||||
bvlo = Var('bvlo')
|
||||
bvhi = Var('bvhi')
|
||||
|
||||
ScalarTS = TypeSet(lanes=(1, 1), ints=True, floats=True, bools=True)
|
||||
|
||||
vsplit.set_semantics(
|
||||
(lo, hi) << vsplit(x),
|
||||
Rtl(
|
||||
bvx << prim_to_bv(x),
|
||||
(bvlo, bvhi) << bvsplit(bvx),
|
||||
lo << prim_from_bv(bvlo),
|
||||
hi << prim_from_bv(bvhi)
|
||||
))
|
||||
|
||||
vconcat.set_semantics(
|
||||
x << vconcat(lo, hi),
|
||||
Rtl(
|
||||
bvlo << prim_to_bv(lo),
|
||||
bvhi << prim_to_bv(hi),
|
||||
bvx << bvconcat(bvlo, bvhi),
|
||||
x << prim_from_bv(bvx)
|
||||
))
|
||||
|
||||
iadd.set_semantics(
|
||||
a << iadd(x, y),
|
||||
(Rtl(
|
||||
bvx << prim_to_bv(x),
|
||||
bvy << prim_to_bv(y),
|
||||
bva << bvadd(bvx, bvy),
|
||||
a << prim_from_bv(bva)
|
||||
), [InTypeset(x.get_typevar(), ScalarTS)]),
|
||||
Rtl(
|
||||
(xlo, xhi) << vsplit(x),
|
||||
(ylo, yhi) << vsplit(y),
|
||||
alo << iadd(xlo, ylo),
|
||||
ahi << iadd(xhi, yhi),
|
||||
a << vconcat(alo, ahi)
|
||||
))
|
||||
|
||||
#
|
||||
# Integer arithmetic with carry and/or borrow.
|
||||
#
|
||||
iadd_cin.set_semantics(
|
||||
a << iadd_cin(x, y, c_in),
|
||||
Rtl(
|
||||
bvx << prim_to_bv(x),
|
||||
bvy << prim_to_bv(y),
|
||||
bvc_in << prim_to_bv(c_in),
|
||||
bvs << bvzeroext(bvc_in),
|
||||
bvt << bvadd(bvx, bvy),
|
||||
bva << bvadd(bvt, bvs),
|
||||
a << prim_from_bv(bva)
|
||||
))
|
||||
|
||||
iadd_cout.set_semantics(
|
||||
(a, c_out) << iadd_cout(x, y),
|
||||
Rtl(
|
||||
bvx << prim_to_bv(x),
|
||||
bvy << prim_to_bv(y),
|
||||
bva << bvadd(bvx, bvy),
|
||||
bc_out << bvult(bva, bvx),
|
||||
bvc_out << bool2bv(bc_out),
|
||||
a << prim_from_bv(bva),
|
||||
c_out << prim_from_bv(bvc_out)
|
||||
))
|
||||
|
||||
iadd_carry.set_semantics(
|
||||
(a, c_out) << iadd_carry(x, y, c_in),
|
||||
Rtl(
|
||||
bvx << prim_to_bv(x),
|
||||
bvy << prim_to_bv(y),
|
||||
bvc_in << prim_to_bv(c_in),
|
||||
bvs << bvzeroext(bvc_in),
|
||||
bvt << bvadd(bvx, bvy),
|
||||
bva << bvadd(bvt, bvs),
|
||||
bc_out << bvult(bva, bvx),
|
||||
bvc_out << bool2bv(bc_out),
|
||||
a << prim_from_bv(bva),
|
||||
c_out << prim_from_bv(bvc_out)
|
||||
))
|
||||
|
||||
bextend.set_semantics(
|
||||
a << bextend(x),
|
||||
(Rtl(
|
||||
bvx << prim_to_bv(x),
|
||||
bvy << bvsignext(bvx),
|
||||
a << prim_from_bv(bvy)
|
||||
), [InTypeset(x.get_typevar(), ScalarTS)]),
|
||||
Rtl(
|
||||
(xlo, xhi) << vsplit(x),
|
||||
alo << bextend(xlo),
|
||||
ahi << bextend(xhi),
|
||||
a << vconcat(alo, ahi)
|
||||
))
|
||||
|
||||
|
||||
def create_comp_xform(cc, bvcmp_func):
|
||||
# type: (Enumerator, Instruction) -> XForm
|
||||
ba = Var('ba')
|
||||
return XForm(
|
||||
Rtl(
|
||||
a << icmp(cc, x, y)
|
||||
),
|
||||
Rtl(
|
||||
bvx << prim_to_bv(x),
|
||||
bvy << prim_to_bv(y),
|
||||
ba << bvcmp_func(bvx, bvy),
|
||||
bva << bool2bv(ba),
|
||||
bva_wide << bvzeroext(bva),
|
||||
a << prim_from_bv(bva_wide),
|
||||
),
|
||||
constraints=InTypeset(x.get_typevar(), ScalarTS))
|
||||
|
||||
|
||||
icmp.set_semantics(
|
||||
a << icmp(CC, x, y),
|
||||
Rtl(
|
||||
(xlo, xhi) << vsplit(x),
|
||||
(ylo, yhi) << vsplit(y),
|
||||
alo << icmp(CC, xlo, ylo),
|
||||
ahi << icmp(CC, xhi, yhi),
|
||||
b << vconcat(alo, ahi),
|
||||
a << bextend(b)
|
||||
),
|
||||
create_comp_xform(intcc.eq, bveq),
|
||||
create_comp_xform(intcc.ne, bvne),
|
||||
create_comp_xform(intcc.sge, bvsge),
|
||||
create_comp_xform(intcc.sgt, bvsgt),
|
||||
create_comp_xform(intcc.sle, bvsle),
|
||||
create_comp_xform(intcc.slt, bvslt),
|
||||
create_comp_xform(intcc.uge, bvuge),
|
||||
create_comp_xform(intcc.ugt, bvugt),
|
||||
create_comp_xform(intcc.ule, bvule),
|
||||
create_comp_xform(intcc.ult, bvult))
|
||||
|
||||
#
|
||||
# Legalization helper instructions.
|
||||
#
|
||||
|
||||
isplit.set_semantics(
|
||||
(xlo, xhi) << isplit(x),
|
||||
(Rtl(
|
||||
bvx << prim_to_bv(x),
|
||||
(bvlo, bvhi) << bvsplit(bvx),
|
||||
xlo << prim_from_bv(bvlo),
|
||||
xhi << prim_from_bv(bvhi)
|
||||
), [InTypeset(x.get_typevar(), ScalarTS)]),
|
||||
Rtl(
|
||||
(a, b) << vsplit(x),
|
||||
(alo, ahi) << isplit(a),
|
||||
(blo, bhi) << isplit(b),
|
||||
xlo << vconcat(alo, blo),
|
||||
xhi << vconcat(bhi, bhi)
|
||||
))
|
||||
|
||||
iconcat.set_semantics(
|
||||
x << iconcat(xlo, xhi),
|
||||
(Rtl(
|
||||
bvlo << prim_to_bv(xlo),
|
||||
bvhi << prim_to_bv(xhi),
|
||||
bvx << bvconcat(bvlo, bvhi),
|
||||
x << prim_from_bv(bvx)
|
||||
), [InTypeset(x.get_typevar(), ScalarTS)]),
|
||||
Rtl(
|
||||
(alo, ahi) << vsplit(xlo),
|
||||
(blo, bhi) << vsplit(xhi),
|
||||
a << iconcat(alo, blo),
|
||||
b << iconcat(ahi, bhi),
|
||||
x << vconcat(a, b),
|
||||
))
|
||||
144
cranelift/codegen/meta-python/base/settings.py
Normal file
144
cranelift/codegen/meta-python/base/settings.py
Normal file
@@ -0,0 +1,144 @@
|
||||
"""
|
||||
Cranelift shared settings.
|
||||
|
||||
This module defines settings relevant for all code generators.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.settings import SettingGroup, BoolSetting, EnumSetting, NumSetting
|
||||
|
||||
group = SettingGroup('shared')
|
||||
|
||||
opt_level = EnumSetting(
|
||||
"""
|
||||
Optimization level:
|
||||
|
||||
- default: Very profitable optimizations enabled, none slow.
|
||||
- best: Enable all optimizations
|
||||
- fastest: Optimize for compile time by disabling most optimizations.
|
||||
""",
|
||||
'default', 'best', 'fastest')
|
||||
|
||||
enable_verifier = BoolSetting(
|
||||
"""
|
||||
Run the Cranelift IR verifier at strategic times during compilation.
|
||||
|
||||
This makes compilation slower but catches many bugs. The verifier is
|
||||
disabled by default, except when reading Cranelift IR from a text file.
|
||||
""",
|
||||
default=True)
|
||||
|
||||
# Note that Cranelift doesn't currently need an is_pie flag, because PIE is
|
||||
# just PIC where symbols can't be pre-empted, which can be expressed with the
|
||||
# `colocated` flag on external functions and global values.
|
||||
is_pic = BoolSetting("Enable Position-Independent Code generation")
|
||||
|
||||
colocated_libcalls = BoolSetting(
|
||||
"""
|
||||
Use colocated libcalls.
|
||||
|
||||
Generate code that assumes that libcalls can be declared "colocated",
|
||||
meaning they will be defined along with the current function, such that
|
||||
they can use more efficient addressing.
|
||||
""")
|
||||
|
||||
avoid_div_traps = BoolSetting(
|
||||
"""
|
||||
Generate explicit checks around native division instructions to avoid
|
||||
their trapping.
|
||||
|
||||
This is primarily used by SpiderMonkey which doesn't install a signal
|
||||
handler for SIGFPE, but expects a SIGILL trap for division by zero.
|
||||
|
||||
On ISAs like ARM where the native division instructions don't trap,
|
||||
this setting has no effect - explicit checks are always inserted.
|
||||
""")
|
||||
|
||||
enable_float = BoolSetting(
|
||||
"""
|
||||
Enable the use of floating-point instructions
|
||||
|
||||
Disabling use of floating-point instructions is not yet implemented.
|
||||
""",
|
||||
default=True)
|
||||
|
||||
enable_nan_canonicalization = BoolSetting(
|
||||
"""
|
||||
Enable NaN canonicalization
|
||||
|
||||
This replaces NaNs with a single canonical value, for users requiring
|
||||
entirely deterministic WebAssembly computation. This is not required
|
||||
by the WebAssembly spec, so it is not enabled by default.
|
||||
""",
|
||||
default=False)
|
||||
|
||||
enable_simd = BoolSetting(
|
||||
"""Enable the use of SIMD instructions.""",
|
||||
default=True)
|
||||
|
||||
enable_atomics = BoolSetting(
|
||||
"""Enable the use of atomic instructions""",
|
||||
default=True)
|
||||
|
||||
#
|
||||
# Settings specific to the `baldrdash` calling convention.
|
||||
#
|
||||
baldrdash_prologue_words = NumSetting(
|
||||
"""
|
||||
Number of pointer-sized words pushed by the baldrdash prologue.
|
||||
|
||||
Functions with the `baldrdash` calling convention don't generate their
|
||||
own prologue and epilogue. They depend on externally generated code
|
||||
that pushes a fixed number of words in the prologue and restores them
|
||||
in the epilogue.
|
||||
|
||||
This setting configures the number of pointer-sized words pushed on the
|
||||
stack when the Cranelift-generated code is entered. This includes the
|
||||
pushed return address on x86.
|
||||
""")
|
||||
|
||||
#
|
||||
# BaldrMonkey requires that not-yet-relocated function addresses be encoded
|
||||
# as all-ones bitpatterns.
|
||||
#
|
||||
allones_funcaddrs = BoolSetting(
|
||||
"""
|
||||
Emit not-yet-relocated function addresses as all-ones bit patterns.
|
||||
""")
|
||||
|
||||
#
|
||||
# Stack probing options.
|
||||
#
|
||||
probestack_enabled = BoolSetting(
|
||||
"""
|
||||
Enable the use of stack probes, for calling conventions which support
|
||||
this functionality.
|
||||
""",
|
||||
default=True)
|
||||
|
||||
probestack_func_adjusts_sp = BoolSetting(
|
||||
"""
|
||||
Set this to true of the stack probe function modifies the stack pointer
|
||||
itself.
|
||||
""")
|
||||
|
||||
probestack_size_log2 = NumSetting(
|
||||
"""
|
||||
The log2 of the size of the stack guard region.
|
||||
|
||||
Stack frames larger than this size will have stack overflow checked
|
||||
by calling the probestack function.
|
||||
|
||||
The default is 12, which translates to a size of 4096.
|
||||
""",
|
||||
default=12)
|
||||
|
||||
#
|
||||
# Jump table options.
|
||||
#
|
||||
jump_tables_enabled = BoolSetting(
|
||||
"""
|
||||
Enable the use of jump tables in generated machine code.
|
||||
""",
|
||||
default=True)
|
||||
|
||||
group.close(globals())
|
||||
49
cranelift/codegen/meta-python/base/types.py
Normal file
49
cranelift/codegen/meta-python/base/types.py
Normal file
@@ -0,0 +1,49 @@
|
||||
"""
|
||||
The base.types module predefines all the Cranelift scalar types.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.types import IntType, FloatType, BoolType, FlagsType
|
||||
|
||||
#: Abstract boolean (can't be stored in memory, use bint to convert to 0 or 1).
|
||||
b1 = BoolType(1) #: 1-bit bool.
|
||||
|
||||
#: Booleans used as SIMD elements (can be stored in memory, true is all-ones).
|
||||
b8 = BoolType(8) #: 8-bit bool.
|
||||
b16 = BoolType(16) #: 16-bit bool.
|
||||
b32 = BoolType(32) #: 32-bit bool.
|
||||
b64 = BoolType(64) #: 64-bit bool.
|
||||
|
||||
# Integers.
|
||||
i8 = IntType(8) #: 8-bit int.
|
||||
i16 = IntType(16) #: 16-bit int.
|
||||
i32 = IntType(32) #: 32-bit int.
|
||||
i64 = IntType(64) #: 64-bit int.
|
||||
|
||||
#: IEEE single precision.
|
||||
f32 = FloatType(
|
||||
32, """
|
||||
A 32-bit floating point type represented in the IEEE 754-2008
|
||||
*binary32* interchange format. This corresponds to the :c:type:`float`
|
||||
type in most C implementations.
|
||||
""")
|
||||
|
||||
#: IEEE double precision.
|
||||
f64 = FloatType(
|
||||
64, """
|
||||
A 64-bit floating point type represented in the IEEE 754-2008
|
||||
*binary64* interchange format. This corresponds to the :c:type:`double`
|
||||
type in most C implementations.
|
||||
""")
|
||||
#: CPU flags from an integer comparison.
|
||||
iflags = FlagsType(
|
||||
'iflags', """
|
||||
CPU flags representing the result of an integer comparison. These flags
|
||||
can be tested with an :type:`intcc` condition code.
|
||||
""")
|
||||
|
||||
#: CPU flags from a floating point comparison.
|
||||
fflags = FlagsType(
|
||||
'fflags', """
|
||||
CPU flags representing the result of a floating point comparison. These
|
||||
flags can be tested with a :type:`floatcc` condition code.
|
||||
""")
|
||||
36
cranelift/codegen/meta-python/build.py
Normal file
36
cranelift/codegen/meta-python/build.py
Normal file
@@ -0,0 +1,36 @@
|
||||
# Second-level build script.
|
||||
#
|
||||
# This script is run from cranelift-codegen/build.rs to generate Rust files.
|
||||
|
||||
from __future__ import absolute_import
|
||||
import argparse
|
||||
import isa
|
||||
import gen_instr
|
||||
import gen_settings
|
||||
import gen_build_deps
|
||||
import gen_encoding
|
||||
import gen_legalizer
|
||||
import gen_binemit
|
||||
|
||||
|
||||
def main():
|
||||
# type: () -> None
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Generate sources for Cranelift.')
|
||||
parser.add_argument('--out-dir', help='set output directory')
|
||||
|
||||
args = parser.parse_args()
|
||||
out_dir = args.out_dir
|
||||
|
||||
isas = isa.all_isas()
|
||||
|
||||
gen_instr.generate(isas, out_dir)
|
||||
gen_settings.generate(isas, out_dir)
|
||||
gen_encoding.generate(isas, out_dir)
|
||||
gen_legalizer.generate(isas, out_dir)
|
||||
gen_binemit.generate(isas, out_dir)
|
||||
gen_build_deps.generate()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
59
cranelift/codegen/meta-python/cdsl/__init__.py
Normal file
59
cranelift/codegen/meta-python/cdsl/__init__.py
Normal file
@@ -0,0 +1,59 @@
|
||||
"""
|
||||
Cranelift DSL classes.
|
||||
|
||||
This module defines the classes that are used to define Cranelift instructions
|
||||
and other entities.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
import re
|
||||
|
||||
|
||||
camel_re = re.compile('(^|_)([a-z])')
|
||||
|
||||
|
||||
def camel_case(s):
|
||||
# type: (str) -> str
|
||||
"""Convert the string s to CamelCase:
|
||||
>>> camel_case('x')
|
||||
'X'
|
||||
>>> camel_case('camel_case')
|
||||
'CamelCase'
|
||||
"""
|
||||
return camel_re.sub(lambda m: m.group(2).upper(), s)
|
||||
|
||||
|
||||
def is_power_of_two(x):
|
||||
# type: (int) -> bool
|
||||
"""Check if `x` is a power of two:
|
||||
>>> is_power_of_two(0)
|
||||
False
|
||||
>>> is_power_of_two(1)
|
||||
True
|
||||
>>> is_power_of_two(2)
|
||||
True
|
||||
>>> is_power_of_two(3)
|
||||
False
|
||||
"""
|
||||
return x > 0 and x & (x-1) == 0
|
||||
|
||||
|
||||
def next_power_of_two(x):
|
||||
# type: (int) -> int
|
||||
"""
|
||||
Compute the next power of two that is greater than `x`:
|
||||
>>> next_power_of_two(0)
|
||||
1
|
||||
>>> next_power_of_two(1)
|
||||
2
|
||||
>>> next_power_of_two(2)
|
||||
4
|
||||
>>> next_power_of_two(3)
|
||||
4
|
||||
>>> next_power_of_two(4)
|
||||
8
|
||||
"""
|
||||
s = 1
|
||||
while x & (x + 1) != 0:
|
||||
x |= x >> s
|
||||
s *= 2
|
||||
return x + 1
|
||||
581
cranelift/codegen/meta-python/cdsl/ast.py
Normal file
581
cranelift/codegen/meta-python/cdsl/ast.py
Normal file
@@ -0,0 +1,581 @@
|
||||
"""
|
||||
Abstract syntax trees.
|
||||
|
||||
This module defines classes that can be used to create abstract syntax trees
|
||||
for pattern matching an rewriting of cranelift instructions.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from . import instructions
|
||||
from .typevar import TypeVar
|
||||
from .predicates import IsEqual, And, TypePredicate, CtrlTypePredicate
|
||||
|
||||
try:
|
||||
from typing import Union, Tuple, Sequence, TYPE_CHECKING, Dict, List # noqa
|
||||
from typing import Optional, Set, Any # noqa
|
||||
if TYPE_CHECKING:
|
||||
from .operands import ImmediateKind # noqa
|
||||
from .predicates import PredNode # noqa
|
||||
VarAtomMap = Dict["Var", "Atom"]
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def replace_var(arg, m):
|
||||
# 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: Atom
|
||||
m[arg] = new_arg
|
||||
return new_arg
|
||||
return arg
|
||||
|
||||
|
||||
class Def(object):
|
||||
"""
|
||||
An AST definition associates a set of variables with the values produced by
|
||||
an expression.
|
||||
|
||||
Example:
|
||||
|
||||
>>> from base.instructions 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):
|
||||
# type: (Union[Var, Tuple[Var, ...]], Apply) -> None
|
||||
if not isinstance(defs, tuple):
|
||||
self.defs = (defs,) # type: Tuple[Var, ...]
|
||||
else:
|
||||
self.defs = defs
|
||||
assert isinstance(expr, Apply)
|
||||
self.expr = expr
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return "{} << {!r}".format(self.defs, self.expr)
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
if len(self.defs) == 1:
|
||||
return "{!s} << {!s}".format(self.defs[0], self.expr)
|
||||
else:
|
||||
return "({}) << {!s}".format(
|
||||
', '.join(map(str, self.defs)), self.expr)
|
||||
|
||||
def copy(self, m):
|
||||
# type: (VarAtomMap) -> Def
|
||||
"""
|
||||
Return a copy of this Def with vars replaced with fresh variables,
|
||||
in accordance with the map m. Update m as necessary.
|
||||
"""
|
||||
new_expr = self.expr.copy(m)
|
||||
new_defs = [] # type: List[Var]
|
||||
for v in self.defs:
|
||||
new_v = replace_var(v, m)
|
||||
assert(isinstance(new_v, Var))
|
||||
new_defs.append(new_v)
|
||||
|
||||
return Def(tuple(new_defs), new_expr)
|
||||
|
||||
def definitions(self):
|
||||
# type: () -> Set[Var]
|
||||
""" Return the set of all Vars that are defined by self"""
|
||||
return set(self.defs)
|
||||
|
||||
def uses(self):
|
||||
# type: () -> Set[Var]
|
||||
""" Return the set of all Vars that are used(read) by self"""
|
||||
return set(self.expr.vars())
|
||||
|
||||
def vars(self):
|
||||
# type: () -> Set[Var]
|
||||
"""Return the set of all Vars in self that correspond to SSA values"""
|
||||
return self.definitions().union(self.uses())
|
||||
|
||||
def substitution(self, other, s):
|
||||
# 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
|
||||
Defs agree structurally if there exists a Var substitution, that can
|
||||
transform one into the other. See Apply.substitution() for more
|
||||
details.
|
||||
"""
|
||||
s = self.expr.substitution(other.expr, s)
|
||||
|
||||
if (s is None):
|
||||
return s
|
||||
|
||||
assert len(self.defs) == len(other.defs)
|
||||
for (self_d, other_d) in zip(self.defs, other.defs):
|
||||
assert self_d not in s # Guaranteed by SSA form
|
||||
s[self_d] = other_d
|
||||
|
||||
return s
|
||||
|
||||
|
||||
class Expr(object):
|
||||
"""
|
||||
An AST expression.
|
||||
"""
|
||||
|
||||
|
||||
class Atom(Expr):
|
||||
"""
|
||||
An Atom in the DSL is either a literal or a Var
|
||||
"""
|
||||
|
||||
|
||||
class Var(Atom):
|
||||
"""
|
||||
A free variable.
|
||||
|
||||
When variables are used in `XForms` with source and destination patterns,
|
||||
they are classified as follows:
|
||||
|
||||
Input values
|
||||
Uses in the source pattern with no preceding def. These may appear as
|
||||
inputs in the destination pattern too, but no new inputs can be
|
||||
introduced.
|
||||
Output values
|
||||
Variables that are defined in both the source and destination pattern.
|
||||
These values may have uses outside the source pattern, and the
|
||||
destination pattern must compute the same value.
|
||||
Intermediate values
|
||||
Values that are defined in the source pattern, but not in the
|
||||
destination pattern. These may have uses outside the source pattern, so
|
||||
the defining instruction can't be deleted immediately.
|
||||
Temporary values
|
||||
Values that are defined only in the destination pattern.
|
||||
"""
|
||||
|
||||
def __init__(self, name, typevar=None):
|
||||
# type: (str, TypeVar) -> None
|
||||
self.name = name
|
||||
# The `Def` defining this variable in a source pattern.
|
||||
self.src_def = None # type: Def
|
||||
# The `Def` defining this variable in a destination pattern.
|
||||
self.dst_def = None # type: Def
|
||||
# TypeVar representing the type of this variable.
|
||||
self.typevar = typevar # type: TypeVar
|
||||
# The original 'typeof(x)' type variable that was created for this Var.
|
||||
# This one doesn't change. `self.typevar` above may be changed to
|
||||
# another typevar by type inference.
|
||||
self.original_typevar = self.typevar # type: TypeVar
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
return self.name
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
s = self.name
|
||||
if self.src_def:
|
||||
s += ", src"
|
||||
if self.dst_def:
|
||||
s += ", dst"
|
||||
return "Var({})".format(s)
|
||||
|
||||
# Context bits for `set_def` indicating which pattern has defines of this
|
||||
# var.
|
||||
SRCCTX = 1
|
||||
DSTCTX = 2
|
||||
|
||||
def set_def(self, context, d):
|
||||
# type: (int, Def) -> None
|
||||
"""
|
||||
Set the `Def` that defines this variable in the given context.
|
||||
|
||||
The `context` must be one of `SRCCTX` or `DSTCTX`
|
||||
"""
|
||||
if context == self.SRCCTX:
|
||||
self.src_def = d
|
||||
else:
|
||||
self.dst_def = d
|
||||
|
||||
def get_def(self, context):
|
||||
# type: (int) -> Def
|
||||
"""
|
||||
Get the def of this variable in context.
|
||||
|
||||
The `context` must be one of `SRCCTX` or `DSTCTX`
|
||||
"""
|
||||
if context == self.SRCCTX:
|
||||
return self.src_def
|
||||
else:
|
||||
return self.dst_def
|
||||
|
||||
def is_input(self):
|
||||
# type: () -> bool
|
||||
"""Is this an input value to the src pattern?"""
|
||||
return self.src_def is None and self.dst_def is None
|
||||
|
||||
def is_output(self):
|
||||
# type: () -> bool
|
||||
"""Is this an output value, defined in both src and dst patterns?"""
|
||||
return self.src_def is not None and self.dst_def is not None
|
||||
|
||||
def is_intermediate(self):
|
||||
# type: () -> bool
|
||||
"""Is this an intermediate value, defined only in the src pattern?"""
|
||||
return self.src_def is not None and self.dst_def is None
|
||||
|
||||
def is_temp(self):
|
||||
# type: () -> bool
|
||||
"""Is this a temp value, defined only in the dst pattern?"""
|
||||
return self.src_def is None and self.dst_def is not None
|
||||
|
||||
def get_typevar(self):
|
||||
# type: () -> TypeVar
|
||||
"""Get the type variable representing the type of this variable."""
|
||||
if not self.typevar:
|
||||
# Create a TypeVar allowing all types.
|
||||
tv = TypeVar(
|
||||
'typeof_{}'.format(self),
|
||||
'Type of the pattern variable `{}`'.format(self),
|
||||
ints=True, floats=True, bools=True,
|
||||
scalars=True, simd=True, bitvecs=True,
|
||||
specials=True)
|
||||
self.original_typevar = tv
|
||||
self.typevar = tv
|
||||
return self.typevar
|
||||
|
||||
def set_typevar(self, tv):
|
||||
# type: (TypeVar) -> None
|
||||
self.typevar = tv
|
||||
|
||||
def has_free_typevar(self):
|
||||
# type: () -> bool
|
||||
"""
|
||||
Check if this variable has a free type variable.
|
||||
|
||||
If not, the type of this variable is computed from the type of another
|
||||
variable.
|
||||
"""
|
||||
if not self.typevar or self.typevar.is_derived:
|
||||
return False
|
||||
return self.typevar is self.original_typevar
|
||||
|
||||
def rust_type(self):
|
||||
# type: () -> str
|
||||
"""
|
||||
Get a Rust expression that computes the type of this variable.
|
||||
|
||||
It is assumed that local variables exist corresponding to the free type
|
||||
variables.
|
||||
"""
|
||||
return self.typevar.rust_expr()
|
||||
|
||||
|
||||
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.instructions 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):
|
||||
# type: (instructions.MaybeBoundInst, Tuple[Expr, ...]) -> None # noqa
|
||||
if isinstance(inst, instructions.BoundInstruction):
|
||||
self.inst = inst.inst
|
||||
self.typevars = inst.typevars
|
||||
else:
|
||||
assert isinstance(inst, instructions.Instruction)
|
||||
self.inst = inst
|
||||
self.typevars = ()
|
||||
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
|
||||
"""
|
||||
Define variables using `var << expr` or `(v1, v2) << expr`.
|
||||
"""
|
||||
return Def(other, self)
|
||||
|
||||
def instname(self):
|
||||
# type: () -> str
|
||||
i = self.inst.name
|
||||
for t in self.typevars:
|
||||
i += '.{}'.format(t)
|
||||
return i
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return "Apply({}, {})".format(self.instname(), self.args)
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
args = ', '.join(map(str, self.args))
|
||||
return '{}({})'.format(self.instname(), args)
|
||||
|
||||
def rust_builder(self, defs=None):
|
||||
# type: (Sequence[Var]) -> str
|
||||
"""
|
||||
Return a Rust Builder method call for instantiating this instruction
|
||||
application.
|
||||
|
||||
The `defs` argument should be a list of variables defined by this
|
||||
instruction. It is used to construct a result type if necessary.
|
||||
"""
|
||||
args = ', '.join(map(str, self.args))
|
||||
# Do we need to pass an explicit type argument?
|
||||
if self.inst.is_polymorphic and not self.inst.use_typevar_operand:
|
||||
args = defs[0].rust_type() + ', ' + args
|
||||
method = self.inst.snake_name()
|
||||
return '{}({})'.format(method, args)
|
||||
|
||||
def inst_predicate(self):
|
||||
# type: () -> PredNode
|
||||
"""
|
||||
Construct an instruction predicate that verifies the immediate operands
|
||||
on this instruction.
|
||||
|
||||
Immediate operands in a source pattern can be either free variables or
|
||||
constants like `ConstantInt` and `Enumerator`. We don't currently
|
||||
support constraints on free variables, but we may in the future.
|
||||
"""
|
||||
pred = None # type: PredNode
|
||||
iform = self.inst.format
|
||||
|
||||
# Examine all of the immediate operands.
|
||||
for ffield, opnum in zip(iform.imm_fields, self.inst.imm_opnums):
|
||||
arg = self.args[opnum]
|
||||
|
||||
# Ignore free variables for now. We may add variable predicates
|
||||
# later.
|
||||
if isinstance(arg, Var):
|
||||
continue
|
||||
|
||||
pred = And.combine(pred, IsEqual(ffield, arg))
|
||||
|
||||
# Add checks for any bound secondary type variables.
|
||||
# We can't check the controlling type variable this way since it may
|
||||
# not appear as the type of an operand.
|
||||
if len(self.typevars) > 1:
|
||||
for bound_ty, tv in zip(self.typevars[1:],
|
||||
self.inst.other_typevars):
|
||||
if bound_ty is None:
|
||||
continue
|
||||
type_chk = TypePredicate.typevar_check(self.inst, tv, bound_ty)
|
||||
pred = And.combine(pred, type_chk)
|
||||
|
||||
return pred
|
||||
|
||||
def inst_predicate_with_ctrl_typevar(self):
|
||||
# type: () -> PredNode
|
||||
"""
|
||||
Same as `inst_predicate()`, but also check the controlling type
|
||||
variable.
|
||||
"""
|
||||
pred = self.inst_predicate()
|
||||
|
||||
if len(self.typevars) > 0:
|
||||
bound_ty = self.typevars[0]
|
||||
type_chk = None # type: PredNode
|
||||
if bound_ty is not None:
|
||||
# Prefer to look at the types of input operands.
|
||||
if self.inst.use_typevar_operand:
|
||||
type_chk = TypePredicate.typevar_check(
|
||||
self.inst, self.inst.ctrl_typevar, bound_ty)
|
||||
else:
|
||||
type_chk = CtrlTypePredicate(bound_ty)
|
||||
pred = And.combine(pred, type_chk)
|
||||
|
||||
return pred
|
||||
|
||||
def copy(self, m):
|
||||
# type: (VarAtomMap) -> Apply
|
||||
"""
|
||||
Return a copy of this Expr with vars replaced with fresh variables,
|
||||
in accordance with the map m. Update m as necessary.
|
||||
"""
|
||||
return Apply(self.inst, tuple(map(lambda e: replace_var(e, m),
|
||||
self.args)))
|
||||
|
||||
def vars(self):
|
||||
# type: () -> Set[Var]
|
||||
"""Return the set of all Vars in self that correspond to SSA values"""
|
||||
res = set()
|
||||
for i in self.inst.value_opnums:
|
||||
arg = self.args[i]
|
||||
assert isinstance(arg, Var)
|
||||
res.add(arg)
|
||||
return res
|
||||
|
||||
def substitution(self, other, s):
|
||||
# type: (Apply, VarAtomMap) -> Optional[VarAtomMap]
|
||||
"""
|
||||
If there is a substitution 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
|
||||
|
||||
# Guaranteed by self.inst == other.inst
|
||||
assert (len(self.args) == len(other.args))
|
||||
|
||||
for (self_a, other_a) in zip(self.args, other.args):
|
||||
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(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, 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 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.
|
||||
|
||||
Immediate operands like `imm64` or `offset32` can be specified in AST
|
||||
expressions using the call syntax: `imm64(5)` which creates a `ConstantInt`
|
||||
node.
|
||||
"""
|
||||
|
||||
def __init__(self, kind, value):
|
||||
# type: (ImmediateKind, int) -> None
|
||||
super(ConstantInt, self).__init__(kind, value)
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
# If the value is in the signed imm64 range, print it as-is.
|
||||
if self.value >= -(2**63) and self.value < (2**63):
|
||||
return str(self.value)
|
||||
# Otherwise if the value is in the unsigned imm64 range, print its
|
||||
# bitwise counterpart in the signed imm64 range.
|
||||
if self.value >= (2**63) and self.value < (2**64):
|
||||
return str(self.value - (2**64))
|
||||
assert False, "immediate value not in signed or unsigned imm64 range"
|
||||
|
||||
|
||||
class ConstantBits(Literal):
|
||||
"""
|
||||
A bitwise value of an immediate operand.
|
||||
|
||||
This is used to create bitwise exact floating point constants using
|
||||
`ieee32.bits(0x80000000)`.
|
||||
"""
|
||||
|
||||
def __init__(self, kind, bits):
|
||||
# type: (ImmediateKind, int) -> None
|
||||
v = '{}::with_bits({:#x})'.format(kind.rust_type, bits)
|
||||
super(ConstantBits, self).__init__(kind, v)
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
"""
|
||||
Get the Rust expression form of this constant.
|
||||
"""
|
||||
return str(self.value)
|
||||
|
||||
|
||||
class Enumerator(Literal):
|
||||
"""
|
||||
A value of an enumerated immediate operand.
|
||||
|
||||
Some immediate operand kinds like `intcc` and `floatcc` have an enumerated
|
||||
range of values corresponding to a Rust enum type. An `Enumerator` object
|
||||
is an AST leaf node representing one of the values.
|
||||
|
||||
:param kind: The enumerated `ImmediateKind` containing the value.
|
||||
:param value: The textual IR representation of the value.
|
||||
|
||||
`Enumerator` nodes are not usually created directly. They are created by
|
||||
using the dot syntax on immediate kinds: `intcc.ult`.
|
||||
"""
|
||||
|
||||
def __init__(self, kind, value):
|
||||
# type: (ImmediateKind, str) -> None
|
||||
super(Enumerator, self).__init__(kind, value)
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
"""
|
||||
Get the Rust expression form of this enumerator.
|
||||
"""
|
||||
return self.kind.rust_enumerator(self.value)
|
||||
268
cranelift/codegen/meta-python/cdsl/formats.py
Normal file
268
cranelift/codegen/meta-python/cdsl/formats.py
Normal file
@@ -0,0 +1,268 @@
|
||||
"""Classes for describing instruction formats."""
|
||||
from __future__ import absolute_import
|
||||
from .operands import OperandKind, VALUE, VARIABLE_ARGS
|
||||
from .operands import Operand # noqa
|
||||
|
||||
# The typing module is only required by mypy, and we don't use these imports
|
||||
# outside type comments.
|
||||
try:
|
||||
from typing import Dict, List, Tuple, Union, Any, Sequence, Iterable # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class InstructionContext(object):
|
||||
"""
|
||||
Most instruction predicates refer to immediate fields of a specific
|
||||
instruction format, so their `predicate_context()` method returns the
|
||||
specific instruction format.
|
||||
|
||||
Predicates that only care about the types of SSA values are independent of
|
||||
the instruction format. They can be evaluated in the context of any
|
||||
instruction.
|
||||
|
||||
The singleton `InstructionContext` class serves as the predicate context
|
||||
for these predicates.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# type: () -> None
|
||||
self.name = 'inst'
|
||||
|
||||
|
||||
# Singleton instance.
|
||||
instruction_context = InstructionContext()
|
||||
|
||||
|
||||
class InstructionFormat(object):
|
||||
"""
|
||||
Every instruction opcode has a corresponding instruction format which
|
||||
determines the number of operands and their kinds. Instruction formats are
|
||||
identified structurally, i.e., the format of an instruction is derived from
|
||||
the kinds of operands used in its declaration.
|
||||
|
||||
The instruction format stores two separate lists of operands: Immediates
|
||||
and values. Immediate operands (including entity references) are
|
||||
represented as explicit members in the `InstructionData` variants. The
|
||||
value operands are stored differently, depending on how many there are.
|
||||
Beyond a certain point, instruction formats switch to an external value
|
||||
list for storing value arguments. Value lists can hold an arbitrary number
|
||||
of values.
|
||||
|
||||
All instruction formats must be predefined in the
|
||||
:py:mod:`cranelift.formats` module.
|
||||
|
||||
:param kinds: List of `OperandKind` objects describing the operands.
|
||||
:param name: Instruction format name in CamelCase. This is used as a Rust
|
||||
variant name in both the `InstructionData` and `InstructionFormat`
|
||||
enums.
|
||||
:param typevar_operand: Index of the value input operand that is used to
|
||||
infer the controlling type variable. By default, this is `0`, the first
|
||||
`value` operand. The index is relative to the values only, ignoring
|
||||
immediate operands.
|
||||
"""
|
||||
|
||||
# Map (imm_kinds, num_value_operands) -> format
|
||||
_registry = dict() # type: Dict[Tuple[Tuple[OperandKind, ...], int, bool], InstructionFormat] # noqa
|
||||
|
||||
# All existing formats.
|
||||
all_formats = list() # type: List[InstructionFormat]
|
||||
|
||||
def __init__(self, *kinds, **kwargs):
|
||||
# type: (*Union[OperandKind, Tuple[str, OperandKind]], **Any) -> None # noqa
|
||||
self.name = kwargs.get('name', None) # type: str
|
||||
self.parent = instruction_context
|
||||
|
||||
# The number of value operands stored in the format, or `None` when
|
||||
# `has_value_list` is set.
|
||||
self.num_value_operands = 0
|
||||
# Does this format use a value list for storing value operands?
|
||||
self.has_value_list = False
|
||||
# Operand fields for the immediate operands. All other instruction
|
||||
# operands are values or variable argument lists. They are all handled
|
||||
# specially.
|
||||
self.imm_fields = tuple(self._process_member_names(kinds))
|
||||
|
||||
# The typevar_operand argument must point to a 'value' operand.
|
||||
self.typevar_operand = kwargs.get('typevar_operand', None) # type: int
|
||||
if self.typevar_operand is not None:
|
||||
if not self.has_value_list:
|
||||
assert self.typevar_operand < self.num_value_operands, \
|
||||
"typevar_operand must indicate a 'value' operand"
|
||||
elif self.has_value_list or self.num_value_operands > 0:
|
||||
# Default to the first 'value' operand, if there is one.
|
||||
self.typevar_operand = 0
|
||||
|
||||
# Compute a signature for the global registry.
|
||||
imm_kinds = tuple(f.kind for f in self.imm_fields)
|
||||
sig = (imm_kinds, self.num_value_operands, self.has_value_list)
|
||||
if sig in InstructionFormat._registry:
|
||||
raise RuntimeError(
|
||||
"Format '{}' has the same signature as existing format '{}'"
|
||||
.format(self.name, InstructionFormat._registry[sig]))
|
||||
InstructionFormat._registry[sig] = self
|
||||
InstructionFormat.all_formats.append(self)
|
||||
|
||||
def args(self):
|
||||
# type: () -> FormatField
|
||||
"""
|
||||
Provides a ValueListField, which is derived from FormatField,
|
||||
corresponding to the full ValueList of the instruction format. This
|
||||
is useful for creating predicates for instructions which use variadic
|
||||
arguments.
|
||||
"""
|
||||
|
||||
if self.has_value_list:
|
||||
return ValueListField(self)
|
||||
return None
|
||||
|
||||
def _process_member_names(self, kinds):
|
||||
# type: (Sequence[Union[OperandKind, Tuple[str, OperandKind]]]) -> Iterable[FormatField] # noqa
|
||||
"""
|
||||
Extract names of all the immediate operands in the kinds tuple.
|
||||
|
||||
Each entry is either an `OperandKind` instance, or a `(member, kind)`
|
||||
pair. The member names correspond to members in the Rust
|
||||
`InstructionData` data structure.
|
||||
|
||||
Updates the fields `self.num_value_operands` and `self.has_value_list`.
|
||||
|
||||
Yields the immediate operand fields.
|
||||
"""
|
||||
inum = 0
|
||||
for arg in kinds:
|
||||
if isinstance(arg, OperandKind):
|
||||
member = arg.default_member
|
||||
k = arg
|
||||
else:
|
||||
member, k = arg
|
||||
|
||||
# We define 'immediate' as not a value or variable arguments.
|
||||
if k is VALUE:
|
||||
self.num_value_operands += 1
|
||||
elif k is VARIABLE_ARGS:
|
||||
self.has_value_list = True
|
||||
else:
|
||||
yield FormatField(self, inum, k, member)
|
||||
inum += 1
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
args = ', '.join(
|
||||
'{}: {}'.format(f.member, f.kind) for f in self.imm_fields)
|
||||
return '{}(imms=({}), vals={})'.format(
|
||||
self.name, args, self.num_value_operands)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
# type: (str) -> FormatField
|
||||
"""
|
||||
Make immediate instruction format members available as attributes.
|
||||
|
||||
Each non-value format member becomes a corresponding `FormatField`
|
||||
attribute.
|
||||
"""
|
||||
for f in self.imm_fields:
|
||||
if f.member == attr:
|
||||
# Cache this field attribute so we won't have to search again.
|
||||
setattr(self, attr, f)
|
||||
return f
|
||||
|
||||
raise AttributeError(
|
||||
'{} is neither a {} member or a '
|
||||
.format(attr, self.name) +
|
||||
'normal InstructionFormat attribute')
|
||||
|
||||
@staticmethod
|
||||
def lookup(ins, outs):
|
||||
# type: (Sequence[Operand], Sequence[Operand]) -> InstructionFormat
|
||||
"""
|
||||
Find an existing instruction format that matches the given lists of
|
||||
instruction inputs and outputs.
|
||||
|
||||
The `ins` and `outs` arguments correspond to the
|
||||
:py:class:`Instruction` arguments of the same name, except they must be
|
||||
tuples of :py:`Operand` objects.
|
||||
"""
|
||||
# Construct a signature.
|
||||
imm_kinds = tuple(op.kind for op in ins if op.is_immediate())
|
||||
num_values = sum(1 for op in ins if op.is_value())
|
||||
has_varargs = (VARIABLE_ARGS in tuple(op.kind for op in ins))
|
||||
|
||||
sig = (imm_kinds, num_values, has_varargs)
|
||||
if sig in InstructionFormat._registry:
|
||||
return InstructionFormat._registry[sig]
|
||||
|
||||
# Try another value list format as an alternative.
|
||||
sig = (imm_kinds, 0, True)
|
||||
if sig in InstructionFormat._registry:
|
||||
return InstructionFormat._registry[sig]
|
||||
|
||||
raise RuntimeError(
|
||||
'No instruction format matches '
|
||||
'imms={}, vals={}, varargs={}'.format(
|
||||
imm_kinds, num_values, has_varargs))
|
||||
|
||||
@staticmethod
|
||||
def extract_names(globs):
|
||||
# type: (Dict[str, Any]) -> None
|
||||
"""
|
||||
Given a dict mapping name -> object as returned by `globals()`, find
|
||||
all the InstructionFormat objects and set their name from the dict key.
|
||||
This is used to name a bunch of global values in a module.
|
||||
"""
|
||||
for name, obj in globs.items():
|
||||
if isinstance(obj, InstructionFormat):
|
||||
assert obj.name is None
|
||||
obj.name = name
|
||||
|
||||
|
||||
class FormatField(object):
|
||||
"""
|
||||
An immediate field in an instruction format.
|
||||
|
||||
This corresponds to a single member of a variant of the `InstructionData`
|
||||
data type.
|
||||
|
||||
:param iform: Parent `InstructionFormat`.
|
||||
:param immnum: Immediate operand number in parent.
|
||||
:param kind: Immediate Operand kind.
|
||||
:param member: Member name in `InstructionData` variant.
|
||||
"""
|
||||
|
||||
def __init__(self, iform, immnum, kind, member):
|
||||
# type: (InstructionFormat, int, OperandKind, str) -> None
|
||||
self.format = iform
|
||||
self.immnum = immnum
|
||||
self.kind = kind
|
||||
self.member = member
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
return '{}.{}'.format(self.format.name, self.member)
|
||||
|
||||
def rust_destructuring_name(self):
|
||||
# type: () -> str
|
||||
return self.member
|
||||
|
||||
def rust_name(self):
|
||||
# type: () -> str
|
||||
return self.member
|
||||
|
||||
|
||||
class ValueListField(FormatField):
|
||||
"""
|
||||
The full value list field of an instruction format.
|
||||
|
||||
This corresponds to all Value-type members of a variant of the
|
||||
`InstructionData` format, which contains a ValueList.
|
||||
|
||||
:param iform: Parent `InstructionFormat`.
|
||||
"""
|
||||
def __init__(self, iform):
|
||||
# type: (InstructionFormat) -> None
|
||||
self.format = iform
|
||||
self.member = "args"
|
||||
|
||||
def rust_destructuring_name(self):
|
||||
# type: () -> str
|
||||
return 'ref {}'.format(self.member)
|
||||
446
cranelift/codegen/meta-python/cdsl/instructions.py
Normal file
446
cranelift/codegen/meta-python/cdsl/instructions.py
Normal file
@@ -0,0 +1,446 @@
|
||||
"""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 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 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)
|
||||
455
cranelift/codegen/meta-python/cdsl/isa.py
Normal file
455
cranelift/codegen/meta-python/cdsl/isa.py
Normal file
@@ -0,0 +1,455 @@
|
||||
"""Defining instruction set architectures."""
|
||||
from __future__ import absolute_import
|
||||
from collections import OrderedDict
|
||||
from .predicates import And, TypePredicate
|
||||
from .registers import RegClass, Register, Stack
|
||||
from .ast import Apply
|
||||
from .types import ValueType
|
||||
from .instructions import InstructionGroup
|
||||
|
||||
# The typing module is only required by mypy, and we don't use these imports
|
||||
# outside type comments.
|
||||
try:
|
||||
from typing import Tuple, Union, Any, Iterable, Sequence, List, Set, Dict, TYPE_CHECKING # noqa
|
||||
if TYPE_CHECKING:
|
||||
from .instructions import MaybeBoundInst, InstructionFormat # noqa
|
||||
from .predicates import PredNode, PredKey # noqa
|
||||
from .settings import SettingGroup # noqa
|
||||
from .registers import RegBank # noqa
|
||||
from .xform import XFormGroup # noqa
|
||||
OperandConstraint = Union[RegClass, Register, int, Stack]
|
||||
ConstraintSeq = Union[OperandConstraint, Tuple[OperandConstraint, ...]]
|
||||
# Instruction specification for encodings. Allows for predicated
|
||||
# instructions.
|
||||
InstSpec = Union[MaybeBoundInst, Apply]
|
||||
BranchRange = Sequence[int]
|
||||
# A recipe predicate consisting of an ISA predicate and an instruction
|
||||
# predicate.
|
||||
RecipePred = Tuple[PredNode, PredNode]
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class TargetISA(object):
|
||||
"""
|
||||
A target instruction set architecture.
|
||||
|
||||
The `TargetISA` class collects everything known about a target ISA.
|
||||
|
||||
:param name: Short mnemonic name for the ISA.
|
||||
:param instruction_groups: List of `InstructionGroup` instances that are
|
||||
relevant for this ISA.
|
||||
"""
|
||||
|
||||
def __init__(self, name, instruction_groups):
|
||||
# type: (str, Sequence[InstructionGroup]) -> None
|
||||
self.name = name
|
||||
self.settings = None # type: SettingGroup
|
||||
self.instruction_groups = instruction_groups
|
||||
self.cpumodes = list() # type: List[CPUMode]
|
||||
self.regbanks = list() # type: List[RegBank]
|
||||
self.legalize_codes = OrderedDict() # type: OrderedDict[XFormGroup, int] # noqa
|
||||
# Unique copies of all predicates.
|
||||
self._predicates = dict() # type: Dict[PredKey, PredNode]
|
||||
|
||||
assert InstructionGroup._current is None,\
|
||||
"InstructionGroup {} is still open"\
|
||||
.format(InstructionGroup._current.name)
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
return self.name
|
||||
|
||||
def finish(self):
|
||||
# type: () -> TargetISA
|
||||
"""
|
||||
Finish the definition of a target ISA after adding all CPU modes and
|
||||
settings.
|
||||
|
||||
This computes some derived properties that are used in multiple
|
||||
places.
|
||||
|
||||
:returns self:
|
||||
"""
|
||||
self._collect_encoding_recipes()
|
||||
self._collect_predicates()
|
||||
self._collect_legalize_codes()
|
||||
return self
|
||||
|
||||
def _collect_encoding_recipes(self):
|
||||
# type: () -> None
|
||||
"""
|
||||
Collect and number all encoding recipes in use.
|
||||
"""
|
||||
self.all_recipes = list() # type: List[EncRecipe]
|
||||
rcps = set() # type: Set[EncRecipe]
|
||||
for cpumode in self.cpumodes:
|
||||
for enc in cpumode.encodings:
|
||||
recipe = enc.recipe
|
||||
if recipe not in rcps:
|
||||
assert recipe.number is None
|
||||
recipe.number = len(rcps)
|
||||
rcps.add(recipe)
|
||||
self.all_recipes.append(recipe)
|
||||
# Make sure ISA predicates are registered.
|
||||
if recipe.isap:
|
||||
recipe.isap = self.unique_pred(recipe.isap)
|
||||
self.settings.number_predicate(recipe.isap)
|
||||
recipe.instp = self.unique_pred(recipe.instp)
|
||||
|
||||
def _collect_predicates(self):
|
||||
# type: () -> None
|
||||
"""
|
||||
Collect and number all predicates in use.
|
||||
|
||||
Ensures that all ISA predicates have an assigned bit number in
|
||||
`self.settings`.
|
||||
"""
|
||||
self.instp_number = OrderedDict() # type: OrderedDict[PredNode, int]
|
||||
for cpumode in self.cpumodes:
|
||||
for enc in cpumode.encodings:
|
||||
instp = enc.instp
|
||||
if instp and instp not in self.instp_number:
|
||||
# assign predicate number starting from 0.
|
||||
n = len(self.instp_number)
|
||||
self.instp_number[instp] = n
|
||||
|
||||
# All referenced ISA predicates must have a number in
|
||||
# `self.settings`. This may cause some parent predicates to be
|
||||
# replicated here, which is OK.
|
||||
if enc.isap:
|
||||
self.settings.number_predicate(enc.isap)
|
||||
|
||||
def _collect_legalize_codes(self):
|
||||
# type: () -> None
|
||||
"""
|
||||
Make sure all legalization transforms have been assigned a code.
|
||||
"""
|
||||
for cpumode in self.cpumodes:
|
||||
self.legalize_code(cpumode.default_legalize)
|
||||
for x in cpumode.type_legalize.values():
|
||||
self.legalize_code(x)
|
||||
|
||||
def legalize_code(self, xgrp):
|
||||
# type: (XFormGroup) -> int
|
||||
"""
|
||||
Get the legalization code for the transform group `xgrp`. Assign one if
|
||||
necessary.
|
||||
|
||||
Each target ISA has its own list of legalization actions with
|
||||
associated legalize codes that appear in the encoding tables.
|
||||
|
||||
This method is used to maintain the registry of legalization actions
|
||||
and their table codes.
|
||||
"""
|
||||
if xgrp in self.legalize_codes:
|
||||
code = self.legalize_codes[xgrp]
|
||||
else:
|
||||
code = len(self.legalize_codes)
|
||||
self.legalize_codes[xgrp] = code
|
||||
return code
|
||||
|
||||
def unique_pred(self, pred):
|
||||
# type: (PredNode) -> PredNode
|
||||
"""
|
||||
Get a unique predicate that is equivalent to `pred`.
|
||||
"""
|
||||
if pred is None:
|
||||
return pred
|
||||
# TODO: We could actually perform some algebraic simplifications. It's
|
||||
# not clear if it is worthwhile.
|
||||
k = pred.predicate_key()
|
||||
if k in self._predicates:
|
||||
return self._predicates[k]
|
||||
self._predicates[k] = pred
|
||||
return pred
|
||||
|
||||
|
||||
class CPUMode(object):
|
||||
"""
|
||||
A CPU mode determines which instruction encodings are active.
|
||||
|
||||
All instruction encodings are associated with exactly one `CPUMode`, and
|
||||
all CPU modes are associated with exactly one `TargetISA`.
|
||||
|
||||
:param name: Short mnemonic name for the CPU mode.
|
||||
:param target: Associated `TargetISA`.
|
||||
"""
|
||||
|
||||
def __init__(self, name, isa):
|
||||
# type: (str, TargetISA) -> None
|
||||
self.name = name
|
||||
self.isa = isa
|
||||
self.encodings = [] # type: List[Encoding]
|
||||
isa.cpumodes.append(self)
|
||||
|
||||
# Tables for configuring legalization actions when no valid encoding
|
||||
# exists for an instruction.
|
||||
self.default_legalize = None # type: XFormGroup
|
||||
self.type_legalize = OrderedDict() # type: OrderedDict[ValueType, XFormGroup] # noqa
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
return self.name
|
||||
|
||||
def enc(self, *args, **kwargs):
|
||||
# type: (*Any, **Any) -> None
|
||||
"""
|
||||
Add a new encoding to this CPU mode.
|
||||
|
||||
Arguments are the `Encoding constructor arguments, except for the first
|
||||
`CPUMode argument which is implied.
|
||||
"""
|
||||
self.encodings.append(Encoding(self, *args, **kwargs))
|
||||
|
||||
def legalize_type(self, default=None, **kwargs):
|
||||
# type: (XFormGroup, **XFormGroup) -> None
|
||||
"""
|
||||
Configure the legalization action per controlling type variable.
|
||||
|
||||
Instructions that have a controlling type variable mentioned in one of
|
||||
the arguments will be legalized according to the action specified here
|
||||
instead of using the `legalize_default` action.
|
||||
|
||||
The keyword arguments are value type names:
|
||||
|
||||
mode.legalize_type(i8=widen, i16=widen, i32=expand)
|
||||
|
||||
The `default` argument specifies the action to take for controlling
|
||||
type variables that don't have an explicitly configured action.
|
||||
"""
|
||||
if default is not None:
|
||||
self.default_legalize = default
|
||||
|
||||
for name, xgrp in kwargs.items():
|
||||
ty = ValueType.by_name(name)
|
||||
self.type_legalize[ty] = xgrp
|
||||
|
||||
def legalize_monomorphic(self, xgrp):
|
||||
# type: (XFormGroup) -> None
|
||||
"""
|
||||
Configure the legalization action to take for monomorphic instructions
|
||||
which don't have a controlling type variable.
|
||||
|
||||
See also `legalize_type()` for polymorphic instructions.
|
||||
"""
|
||||
self.type_legalize[None] = xgrp
|
||||
|
||||
def get_legalize_action(self, ty):
|
||||
# type: (ValueType) -> XFormGroup
|
||||
"""
|
||||
Get the legalization action to use for `ty`.
|
||||
"""
|
||||
return self.type_legalize.get(ty, self.default_legalize)
|
||||
|
||||
|
||||
class EncRecipe(object):
|
||||
"""
|
||||
A recipe for encoding instructions with a given format.
|
||||
|
||||
Many different instructions can be encoded by the same recipe, but they
|
||||
must all have the same instruction format.
|
||||
|
||||
The `ins` and `outs` arguments are tuples specifying the register
|
||||
allocation constraints for the value operands and results respectively. The
|
||||
possible constraints for an operand are:
|
||||
|
||||
- A `RegClass` specifying the set of allowed registers.
|
||||
- A `Register` specifying a fixed-register operand.
|
||||
- An integer indicating that this result is tied to a value operand, so
|
||||
they must use the same register.
|
||||
- A `Stack` specifying a value in a stack slot.
|
||||
|
||||
The `branch_range` argument must be provided for recipes that can encode
|
||||
branch instructions. It is an `(origin, bits)` tuple describing the exact
|
||||
range that can be encoded in a branch instruction.
|
||||
|
||||
For ISAs that use CPU flags in `iflags` and `fflags` value types, the
|
||||
`clobbers_flags` is used to indicate instruction encodings that clobbers
|
||||
the CPU flags, so they can't be used where a flag value is live.
|
||||
|
||||
:param name: Short mnemonic name for this recipe.
|
||||
:param format: All encoded instructions must have this
|
||||
:py:class:`InstructionFormat`.
|
||||
:param base_size: Base number of bytes in the binary encoded instruction.
|
||||
:param compute_size: Function name to use when computing actual size.
|
||||
:param ins: Tuple of register constraints for value operands.
|
||||
:param outs: Tuple of register constraints for results.
|
||||
:param branch_range: `(origin, bits)` range for branches.
|
||||
:param clobbers_flags: This instruction clobbers `iflags` and `fflags`.
|
||||
:param instp: Instruction predicate.
|
||||
:param isap: ISA predicate.
|
||||
:param emit: Rust code for binary emission.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name, # type: str
|
||||
format, # type: InstructionFormat
|
||||
base_size, # type: int
|
||||
ins, # type: ConstraintSeq
|
||||
outs, # type: ConstraintSeq
|
||||
compute_size=None, # type: str
|
||||
branch_range=None, # type: BranchRange
|
||||
clobbers_flags=True, # type: bool
|
||||
instp=None, # type: PredNode
|
||||
isap=None, # type: PredNode
|
||||
emit=None # type: str
|
||||
):
|
||||
# type: (...) -> None
|
||||
self.name = name
|
||||
self.format = format
|
||||
assert base_size >= 0
|
||||
self.base_size = base_size
|
||||
self.compute_size = compute_size if compute_size is not None \
|
||||
else 'base_size'
|
||||
self.branch_range = branch_range
|
||||
self.clobbers_flags = clobbers_flags
|
||||
self.instp = instp
|
||||
self.isap = isap
|
||||
self.emit = emit
|
||||
if instp:
|
||||
assert instp.predicate_context() == format
|
||||
self.number = None # type: int
|
||||
|
||||
self.ins = self._verify_constraints(ins)
|
||||
if not format.has_value_list:
|
||||
assert len(self.ins) == format.num_value_operands
|
||||
self.outs = self._verify_constraints(outs)
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
return self.name
|
||||
|
||||
def _verify_constraints(self, seq):
|
||||
# type: (ConstraintSeq) -> Sequence[OperandConstraint]
|
||||
if not isinstance(seq, tuple):
|
||||
seq = (seq,)
|
||||
for c in seq:
|
||||
if isinstance(c, int):
|
||||
# An integer constraint is bound to a value operand.
|
||||
# Check that it is in range.
|
||||
assert c >= 0 and c < len(self.ins)
|
||||
else:
|
||||
assert (isinstance(c, RegClass)
|
||||
or isinstance(c, Register)
|
||||
or isinstance(c, Stack))
|
||||
return seq
|
||||
|
||||
def ties(self):
|
||||
# type: () -> Tuple[Dict[int, int], Dict[int, int]]
|
||||
"""
|
||||
Return two dictionaries representing the tied operands.
|
||||
|
||||
The first maps input number to tied output number, the second maps
|
||||
output number to tied input number.
|
||||
"""
|
||||
i2o = dict() # type: Dict[int, int]
|
||||
o2i = dict() # type: Dict[int, int]
|
||||
for o, i in enumerate(self.outs):
|
||||
if isinstance(i, int):
|
||||
i2o[i] = o
|
||||
o2i[o] = i
|
||||
return (i2o, o2i)
|
||||
|
||||
def fixed_ops(self):
|
||||
# type: () -> Tuple[Set[Register], Set[Register]]
|
||||
"""
|
||||
Return two sets of registers representing the fixed input and output
|
||||
operands.
|
||||
"""
|
||||
i = set(r for r in self.ins if isinstance(r, Register))
|
||||
o = set(r for r in self.outs if isinstance(r, Register))
|
||||
return (i, o)
|
||||
|
||||
def recipe_pred(self):
|
||||
# type: () -> RecipePred
|
||||
"""
|
||||
Get the combined recipe predicate which includes both the ISA predicate
|
||||
and the instruction predicate.
|
||||
|
||||
Return `None` if this recipe has neither predicate.
|
||||
"""
|
||||
if self.isap is None and self.instp is None:
|
||||
return None
|
||||
else:
|
||||
return (self.isap, self.instp)
|
||||
|
||||
|
||||
class Encoding(object):
|
||||
"""
|
||||
Encoding for a concrete instruction.
|
||||
|
||||
An `Encoding` object ties an instruction opcode with concrete type
|
||||
variables together with and encoding recipe and encoding bits.
|
||||
|
||||
The concrete instruction can be in three different forms:
|
||||
|
||||
1. A naked opcode: `trap` for non-polymorphic instructions.
|
||||
2. With bound type variables: `iadd.i32` for polymorphic instructions.
|
||||
3. With operands providing constraints: `icmp.i32(intcc.eq, x, y)`.
|
||||
|
||||
If the instruction is polymorphic, all type variables must be provided.
|
||||
|
||||
:param cpumode: The CPU mode where the encoding is active.
|
||||
:param inst: The :py:class:`Instruction` or :py:class:`BoundInstruction`
|
||||
being encoded.
|
||||
:param recipe: The :py:class:`EncRecipe` to use.
|
||||
:param encbits: Additional encoding bits to be interpreted by `recipe`.
|
||||
:param instp: Instruction predicate, or `None`.
|
||||
:param isap: ISA predicate, or `None`.
|
||||
"""
|
||||
|
||||
def __init__(self, cpumode, inst, recipe, encbits, instp=None, isap=None):
|
||||
# type: (CPUMode, InstSpec, EncRecipe, int, PredNode, PredNode) -> None # noqa
|
||||
assert isinstance(cpumode, CPUMode)
|
||||
assert isinstance(recipe, EncRecipe)
|
||||
|
||||
# Check for possible instruction predicates in `inst`.
|
||||
if isinstance(inst, Apply):
|
||||
instp = And.combine(instp, inst.inst_predicate())
|
||||
self.inst = inst.inst
|
||||
self.typevars = inst.typevars
|
||||
else:
|
||||
self.inst, self.typevars = inst.fully_bound()
|
||||
|
||||
# Add secondary type variables to the instruction predicate.
|
||||
# This is already included by Apply.inst_predicate() above.
|
||||
if len(self.typevars) > 1:
|
||||
for tv, vt in zip(self.inst.other_typevars, self.typevars[1:]):
|
||||
# A None tv is an 'any' wild card: `ishl.i32.any`.
|
||||
if vt is None:
|
||||
continue
|
||||
typred = TypePredicate.typevar_check(self.inst, tv, vt)
|
||||
instp = And.combine(instp, typred)
|
||||
|
||||
self.cpumode = cpumode
|
||||
assert self.inst.format == recipe.format, (
|
||||
"Format {} must match recipe: {}".format(
|
||||
self.inst.format, recipe.format))
|
||||
|
||||
if self.inst.is_branch and not self.inst.is_indirect_branch:
|
||||
assert recipe.branch_range, (
|
||||
'Recipe {} for {} must have a branch_range'
|
||||
.format(recipe, self.inst.name))
|
||||
|
||||
self.recipe = recipe
|
||||
self.encbits = encbits
|
||||
|
||||
# Record specific predicates. Note that the recipe also has predicates.
|
||||
self.instp = self.cpumode.isa.unique_pred(instp)
|
||||
self.isap = self.cpumode.isa.unique_pred(isap)
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
return '[{}#{:02x}]'.format(self.recipe, self.encbits)
|
||||
|
||||
def ctrl_typevar(self):
|
||||
# type: () -> ValueType
|
||||
"""
|
||||
Get the controlling type variable for this encoding or `None`.
|
||||
"""
|
||||
if self.typevars:
|
||||
return self.typevars[0]
|
||||
else:
|
||||
return None
|
||||
251
cranelift/codegen/meta-python/cdsl/operands.py
Normal file
251
cranelift/codegen/meta-python/cdsl/operands.py
Normal file
@@ -0,0 +1,251 @@
|
||||
"""Classes for describing instruction operands."""
|
||||
from __future__ import absolute_import
|
||||
from . import camel_case
|
||||
from .types import ValueType
|
||||
from .typevar import TypeVar
|
||||
|
||||
try:
|
||||
from typing import Union, Dict, TYPE_CHECKING, Iterable # noqa
|
||||
OperandSpec = Union['OperandKind', ValueType, TypeVar]
|
||||
if TYPE_CHECKING:
|
||||
from .ast import Enumerator, ConstantInt, ConstantBits, Literal # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
# Kinds of operands.
|
||||
#
|
||||
# Each instruction has an opcode and a number of operands. The opcode
|
||||
# determines the instruction format, and the format determines the number of
|
||||
# operands and the kind of each operand.
|
||||
class OperandKind(object):
|
||||
"""
|
||||
An instance of the `OperandKind` class corresponds to a kind of operand.
|
||||
Each operand kind has a corresponding type in the Rust representation of an
|
||||
instruction.
|
||||
"""
|
||||
|
||||
def __init__(self, name, doc, default_member=None, rust_type=None):
|
||||
# type: (str, str, str, str) -> None
|
||||
self.name = name
|
||||
self.__doc__ = doc
|
||||
self.default_member = default_member
|
||||
# The camel-cased name of an operand kind is also the Rust type used to
|
||||
# represent it.
|
||||
self.rust_type = rust_type or ('ir::' + camel_case(name))
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
return self.name
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return 'OperandKind({})'.format(self.name)
|
||||
|
||||
|
||||
#: An SSA value operand. This is a value defined by another instruction.
|
||||
VALUE = OperandKind(
|
||||
'value', """
|
||||
An SSA value defined by another instruction.
|
||||
|
||||
This kind of operand can represent any SSA value type, but the
|
||||
instruction format may restrict the valid value types for a given
|
||||
operand.
|
||||
""")
|
||||
|
||||
#: A variable-sized list of value operands. Use for Ebb and function call
|
||||
#: arguments.
|
||||
VARIABLE_ARGS = OperandKind(
|
||||
'variable_args', """
|
||||
A variable size list of `value` operands.
|
||||
|
||||
Use this to represent arguments passed to a function call, arguments
|
||||
passed to an extended basic block, or a variable number of results
|
||||
returned from an instruction.
|
||||
""",
|
||||
rust_type='&[Value]')
|
||||
|
||||
|
||||
# Instances of immediate operand types are provided in the
|
||||
# `cranelift.immediates` module.
|
||||
class ImmediateKind(OperandKind):
|
||||
"""
|
||||
The kind of an immediate instruction operand.
|
||||
|
||||
:param default_member: The default member name of this kind the
|
||||
`InstructionData` data structure.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, name, doc,
|
||||
default_member='imm',
|
||||
rust_type=None,
|
||||
values=None):
|
||||
# type: (str, str, str, str, Dict[str, str]) -> None
|
||||
if rust_type is None:
|
||||
rust_type = 'ir::immediates::' + camel_case(name)
|
||||
super(ImmediateKind, self).__init__(
|
||||
name, doc, default_member, rust_type)
|
||||
self.values = values
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return 'ImmediateKind({})'.format(self.name)
|
||||
|
||||
def __getattr__(self, value):
|
||||
# type: (str) -> Enumerator
|
||||
"""
|
||||
Enumerated immediate kinds allow the use of dot syntax to produce
|
||||
`Enumerator` AST nodes: `icmp.i32(intcc.ult, a, b)`.
|
||||
"""
|
||||
from .ast import Enumerator # noqa
|
||||
if not self.values:
|
||||
raise AssertionError(
|
||||
'{n} is not an enumerated operand kind: {n}.{a}'.format(
|
||||
n=self.name, a=value))
|
||||
if value not in self.values:
|
||||
raise AssertionError(
|
||||
'No such {n} enumerator: {n}.{a}'.format(
|
||||
n=self.name, a=value))
|
||||
return Enumerator(self, value)
|
||||
|
||||
def __call__(self, value):
|
||||
# type: (int) -> ConstantInt
|
||||
"""
|
||||
Create an AST node representing a constant integer:
|
||||
|
||||
iconst(imm64(0))
|
||||
"""
|
||||
from .ast import ConstantInt # noqa
|
||||
if self.values:
|
||||
raise AssertionError(
|
||||
"{}({}): Can't make a constant numeric value for an enum"
|
||||
.format(self.name, value))
|
||||
return ConstantInt(self, value)
|
||||
|
||||
def bits(self, bits):
|
||||
# type: (int) -> ConstantBits
|
||||
"""
|
||||
Create an AST literal node for the given bitwise representation of this
|
||||
immediate operand kind.
|
||||
"""
|
||||
from .ast import ConstantBits # noqa
|
||||
return ConstantBits(self, bits)
|
||||
|
||||
def rust_enumerator(self, value):
|
||||
# type: (str) -> str
|
||||
"""
|
||||
Get the qualified Rust name of the enumerator value `value`.
|
||||
"""
|
||||
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
|
||||
# `cranelift.entities` module.
|
||||
class EntityRefKind(OperandKind):
|
||||
"""
|
||||
The kind of an entity reference instruction operand.
|
||||
"""
|
||||
|
||||
def __init__(self, name, doc, default_member=None, rust_type=None):
|
||||
# type: (str, str, str, str) -> None
|
||||
super(EntityRefKind, self).__init__(
|
||||
name, doc, default_member or name, rust_type)
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return 'EntityRefKind({})'.format(self.name)
|
||||
|
||||
|
||||
class Operand(object):
|
||||
"""
|
||||
An instruction operand can be an *immediate*, an *SSA value*, or an *entity
|
||||
reference*. The type of the operand is one of:
|
||||
|
||||
1. A :py:class:`ValueType` instance indicates an SSA value operand with a
|
||||
concrete type.
|
||||
|
||||
2. A :py:class:`TypeVar` instance indicates an SSA value operand, and the
|
||||
instruction is polymorphic over the possible concrete types that the
|
||||
type variable can assume.
|
||||
|
||||
3. An :py:class:`ImmediateKind` instance indicates an immediate operand
|
||||
whose value is encoded in the instruction itself rather than being
|
||||
passed as an SSA value.
|
||||
|
||||
4. An :py:class:`EntityRefKind` instance indicates an operand that
|
||||
references another entity in the function, typically something declared
|
||||
in the function preamble.
|
||||
|
||||
"""
|
||||
def __init__(self, name, typ, doc=''):
|
||||
# type: (str, OperandSpec, str) -> None
|
||||
self.name = name
|
||||
self.__doc__ = doc
|
||||
|
||||
# Decode the operand spec and set self.kind.
|
||||
# Only VALUE operands have a typevar member.
|
||||
if isinstance(typ, ValueType):
|
||||
self.kind = VALUE
|
||||
self.typevar = TypeVar.singleton(typ)
|
||||
elif isinstance(typ, TypeVar):
|
||||
self.kind = VALUE
|
||||
self.typevar = typ
|
||||
else:
|
||||
assert isinstance(typ, OperandKind)
|
||||
self.kind = typ
|
||||
|
||||
def get_doc(self):
|
||||
# type: () -> str
|
||||
if self.__doc__:
|
||||
return self.__doc__
|
||||
if self.kind is VALUE:
|
||||
return self.typevar.__doc__
|
||||
return self.kind.__doc__
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
return "`{}`".format(self.name)
|
||||
|
||||
def is_value(self):
|
||||
# type: () -> bool
|
||||
"""
|
||||
Is this an SSA value operand?
|
||||
"""
|
||||
return self.kind is VALUE
|
||||
|
||||
def is_varargs(self):
|
||||
# type: () -> bool
|
||||
"""
|
||||
Is this a VARIABLE_ARGS operand?
|
||||
"""
|
||||
return self.kind is VARIABLE_ARGS
|
||||
|
||||
def is_immediate(self):
|
||||
# type: () -> bool
|
||||
"""
|
||||
Is this an immediate operand?
|
||||
|
||||
Note that this includes both `ImmediateKind` operands *and* entity
|
||||
references. It is any operand that doesn't represent a value
|
||||
dependency.
|
||||
"""
|
||||
return self.kind is not VALUE and self.kind is not VARIABLE_ARGS
|
||||
|
||||
def is_cpu_flags(self):
|
||||
# type: () -> bool
|
||||
"""
|
||||
Is this a CPU flags operand?
|
||||
"""
|
||||
return self.kind is VALUE and self.typevar.name in ['iflags', 'fflags']
|
||||
448
cranelift/codegen/meta-python/cdsl/predicates.py
Normal file
448
cranelift/codegen/meta-python/cdsl/predicates.py
Normal file
@@ -0,0 +1,448 @@
|
||||
"""
|
||||
Cranelift predicates.
|
||||
|
||||
A *predicate* is a function that computes a boolean result. The inputs to the
|
||||
function determine the kind of predicate:
|
||||
|
||||
- An *ISA predicate* is evaluated on the current ISA settings together with the
|
||||
shared settings defined in the :py:mod:`settings` module. Once a target ISA
|
||||
has been configured, the value of all ISA predicates is known.
|
||||
|
||||
- An *Instruction predicate* is evaluated on an instruction instance, so it can
|
||||
inspect all the immediate fields and type variables of the instruction.
|
||||
Instruction predicates can be evaluated before register allocation, so they
|
||||
can not depend on specific register assignments to the value operands or
|
||||
outputs.
|
||||
|
||||
Predicates can also be computed from other predicates using the `And`, `Or`,
|
||||
and `Not` combinators defined in this module.
|
||||
|
||||
All predicates have a *context* which determines where they can be evaluated.
|
||||
For an ISA predicate, the context is the ISA settings group. For an instruction
|
||||
predicate, the context is the instruction format.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from functools import reduce
|
||||
from .formats import instruction_context
|
||||
|
||||
try:
|
||||
from typing import Sequence, Tuple, Set, Any, Union, TYPE_CHECKING # noqa
|
||||
if TYPE_CHECKING:
|
||||
from .formats import InstructionFormat, InstructionContext, FormatField # noqa
|
||||
from .instructions import Instruction # noqa
|
||||
from .settings import BoolSetting, SettingGroup # noqa
|
||||
from .types import ValueType # noqa
|
||||
from .typevar import TypeVar # noqa
|
||||
PredContext = Union[SettingGroup, InstructionFormat,
|
||||
InstructionContext]
|
||||
PredLeaf = Union[BoolSetting, 'FieldPredicate', 'TypePredicate',
|
||||
'CtrlTypePredicate']
|
||||
PredNode = Union[PredLeaf, 'Predicate']
|
||||
# A predicate key is a (recursive) tuple of primitive types that
|
||||
# uniquely describes a predicate. It is used for interning.
|
||||
PredKey = Tuple[Any, ...]
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def _is_parent(a, b):
|
||||
# type: (PredContext, PredContext) -> bool
|
||||
"""
|
||||
Return true if a is a parent of b, or equal to it.
|
||||
"""
|
||||
while b and a is not b:
|
||||
b = getattr(b, 'parent', None)
|
||||
return a is b
|
||||
|
||||
|
||||
def _descendant(a, b):
|
||||
# type: (PredContext, PredContext) -> PredContext
|
||||
"""
|
||||
If a is a parent of b or b is a parent of a, return the descendant of the
|
||||
two.
|
||||
|
||||
If neither is a parent of the other, return None.
|
||||
"""
|
||||
if _is_parent(a, b):
|
||||
return b
|
||||
if _is_parent(b, a):
|
||||
return a
|
||||
return None
|
||||
|
||||
|
||||
class Predicate(object):
|
||||
"""
|
||||
Superclass for all computed predicates.
|
||||
|
||||
Leaf predicates can have other types, such as `Setting`.
|
||||
|
||||
:param parts: Tuple of components in the predicate expression.
|
||||
"""
|
||||
|
||||
def __init__(self, parts):
|
||||
# type: (Sequence[PredNode]) -> None
|
||||
self.parts = parts
|
||||
self.context = reduce(
|
||||
_descendant,
|
||||
(p.predicate_context() for p in parts))
|
||||
assert self.context, "Incompatible predicate parts"
|
||||
self.predkey = None # type: PredKey
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
return '{}({})'.format(type(self).__name__,
|
||||
', '.join(map(str, self.parts)))
|
||||
|
||||
def predicate_context(self):
|
||||
# type: () -> PredContext
|
||||
return self.context
|
||||
|
||||
def predicate_leafs(self, leafs):
|
||||
# type: (Set[PredLeaf]) -> None
|
||||
"""
|
||||
Collect all leaf predicates into the `leafs` set.
|
||||
"""
|
||||
for part in self.parts:
|
||||
part.predicate_leafs(leafs)
|
||||
|
||||
def rust_predicate(self, prec):
|
||||
# type: (int) -> str
|
||||
raise NotImplementedError("rust_predicate is an abstract method")
|
||||
|
||||
def predicate_key(self):
|
||||
# type: () -> PredKey
|
||||
"""Tuple uniquely identifying a predicate."""
|
||||
if not self.predkey:
|
||||
p = tuple(p.predicate_key() for p in self.parts) # type: PredKey
|
||||
self.predkey = (type(self).__name__,) + p
|
||||
return self.predkey
|
||||
|
||||
|
||||
class And(Predicate):
|
||||
"""
|
||||
Computed predicate that is true if all parts are true.
|
||||
"""
|
||||
|
||||
precedence = 2
|
||||
|
||||
def __init__(self, *args):
|
||||
# type: (*PredNode) -> None
|
||||
super(And, self).__init__(args)
|
||||
|
||||
def rust_predicate(self, prec):
|
||||
# type: (int) -> str
|
||||
"""
|
||||
Return a Rust expression computing the value of this predicate.
|
||||
|
||||
The surrounding precedence determines whether parentheses are needed:
|
||||
|
||||
0. An `if` statement.
|
||||
1. An `||` expression.
|
||||
2. An `&&` expression.
|
||||
3. A `!` expression.
|
||||
"""
|
||||
s = ' && '.join(p.rust_predicate(And.precedence) for p in self.parts)
|
||||
if prec > And.precedence:
|
||||
s = '({})'.format(s)
|
||||
return s
|
||||
|
||||
@staticmethod
|
||||
def combine(*args):
|
||||
# type: (*PredNode) -> PredNode
|
||||
"""
|
||||
Combine a sequence of predicates, allowing for `None` members.
|
||||
|
||||
Return a predicate that is true when all non-`None` arguments are true,
|
||||
or `None` if all of the arguments are `None`.
|
||||
"""
|
||||
args = tuple(p for p in args if p)
|
||||
if args == ():
|
||||
return None
|
||||
if len(args) == 1:
|
||||
return args[0]
|
||||
# We have multiple predicate args. Combine with `And`.
|
||||
return And(*args)
|
||||
|
||||
|
||||
class Or(Predicate):
|
||||
"""
|
||||
Computed predicate that is true if any parts are true.
|
||||
"""
|
||||
|
||||
precedence = 1
|
||||
|
||||
def __init__(self, *args):
|
||||
# type: (*PredNode) -> None
|
||||
super(Or, self).__init__(args)
|
||||
|
||||
def rust_predicate(self, prec):
|
||||
# type: (int) -> str
|
||||
s = ' || '.join(p.rust_predicate(Or.precedence) for p in self.parts)
|
||||
if prec > Or.precedence:
|
||||
s = '({})'.format(s)
|
||||
return s
|
||||
|
||||
|
||||
class Not(Predicate):
|
||||
"""
|
||||
Computed predicate that is true if its single part is false.
|
||||
"""
|
||||
|
||||
precedence = 3
|
||||
|
||||
def __init__(self, part):
|
||||
# type: (PredNode) -> None
|
||||
super(Not, self).__init__((part,))
|
||||
|
||||
def rust_predicate(self, prec):
|
||||
# type: (int) -> str
|
||||
return '!' + self.parts[0].rust_predicate(Not.precedence)
|
||||
|
||||
|
||||
class FieldPredicate(object):
|
||||
"""
|
||||
An instruction predicate that performs a test on a single `FormatField`.
|
||||
|
||||
:param field: The `FormatField` to be tested.
|
||||
:param function: Boolean predicate function to call.
|
||||
:param args: Additional arguments for the predicate function.
|
||||
"""
|
||||
|
||||
def __init__(self, field, function, args):
|
||||
# type: (FormatField, str, Sequence[Any]) -> None
|
||||
self.field = field
|
||||
self.function = function
|
||||
self.args = args
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
args = (self.field.rust_name(),) + tuple(map(str, self.args))
|
||||
return '{}({})'.format(self.function, ', '.join(args))
|
||||
|
||||
def predicate_context(self):
|
||||
# type: () -> PredContext
|
||||
"""
|
||||
This predicate can be evaluated in the context of an instruction
|
||||
format.
|
||||
"""
|
||||
iform = self.field.format # type: InstructionFormat
|
||||
return iform
|
||||
|
||||
def predicate_key(self):
|
||||
# type: () -> PredKey
|
||||
a = tuple(map(str, self.args))
|
||||
return (self.function, str(self.field)) + a
|
||||
|
||||
def predicate_leafs(self, leafs):
|
||||
# type: (Set[PredLeaf]) -> None
|
||||
leafs.add(self)
|
||||
|
||||
def rust_predicate(self, prec):
|
||||
# type: (int) -> str
|
||||
"""
|
||||
Return a string of Rust code that evaluates this predicate.
|
||||
"""
|
||||
# Prepend `field` to the predicate function arguments.
|
||||
args = (self.field.rust_name(),) + tuple(map(str, self.args))
|
||||
return 'crate::predicates::{}({})'\
|
||||
.format(self.function, ', '.join(args))
|
||||
|
||||
|
||||
class IsEqual(FieldPredicate):
|
||||
"""
|
||||
Instruction predicate that checks if an immediate instruction format field
|
||||
is equal to a constant value.
|
||||
|
||||
:param field: `FormatField` to be checked.
|
||||
:param value: The constant value to compare against.
|
||||
"""
|
||||
|
||||
def __init__(self, field, value):
|
||||
# type: (FormatField, Any) -> None
|
||||
super(IsEqual, self).__init__(field, 'is_equal', (value,))
|
||||
self.value = value
|
||||
|
||||
|
||||
class IsZero32BitFloat(FieldPredicate):
|
||||
"""
|
||||
Instruction predicate that checks if an immediate instruction format field
|
||||
is equal to zero.
|
||||
|
||||
:param field: `FormatField` to be checked.
|
||||
:param value: The constant value to check.
|
||||
"""
|
||||
|
||||
def __init__(self, field):
|
||||
# type: (FormatField) -> None
|
||||
super(IsZero32BitFloat, self).__init__(field,
|
||||
'is_zero_32_bit_float',
|
||||
())
|
||||
|
||||
|
||||
class IsZero64BitFloat(FieldPredicate):
|
||||
"""
|
||||
Instruction predicate that checks if an immediate instruction format field
|
||||
is equal to zero.
|
||||
|
||||
:param field: `FormatField` to be checked.
|
||||
:param value: The constant value to check.
|
||||
"""
|
||||
|
||||
def __init__(self, field):
|
||||
# type: (FormatField) -> None
|
||||
super(IsZero64BitFloat, self).__init__(field,
|
||||
'is_zero_64_bit_float',
|
||||
())
|
||||
|
||||
|
||||
class IsSignedInt(FieldPredicate):
|
||||
"""
|
||||
Instruction predicate that checks if an immediate instruction format field
|
||||
is representable as an n-bit two's complement integer.
|
||||
|
||||
:param field: `FormatField` to be checked.
|
||||
:param width: Number of bits in the allowed range.
|
||||
:param scale: Number of low bits that must be 0.
|
||||
|
||||
The predicate is true if the field is in the range:
|
||||
`-2^(width-1) -- 2^(width-1)-1`
|
||||
and a multiple of `2^scale`.
|
||||
"""
|
||||
|
||||
def __init__(self, field, width, scale=0):
|
||||
# type: (FormatField, int, int) -> None
|
||||
super(IsSignedInt, self).__init__(
|
||||
field, 'is_signed_int', (width, scale))
|
||||
self.width = width
|
||||
self.scale = scale
|
||||
assert width >= 0 and width <= 64
|
||||
assert scale >= 0 and scale < width
|
||||
|
||||
|
||||
class IsUnsignedInt(FieldPredicate):
|
||||
"""
|
||||
Instruction predicate that checks if an immediate instruction format field
|
||||
is representable as an n-bit unsigned complement integer.
|
||||
|
||||
:param field: `FormatField` to be checked.
|
||||
:param width: Number of bits in the allowed range.
|
||||
:param scale: Number of low bits that must be 0.
|
||||
|
||||
The predicate is true if the field is in the range:
|
||||
`0 -- 2^width - 1` and a multiple of `2^scale`.
|
||||
"""
|
||||
|
||||
def __init__(self, field, width, scale=0):
|
||||
# type: (FormatField, int, int) -> None
|
||||
super(IsUnsignedInt, self).__init__(
|
||||
field, 'is_unsigned_int', (width, scale))
|
||||
self.width = width
|
||||
self.scale = scale
|
||||
assert width >= 0 and width <= 64
|
||||
assert scale >= 0 and scale < width
|
||||
|
||||
|
||||
class TypePredicate(object):
|
||||
"""
|
||||
An instruction predicate that checks the type of an SSA argument value.
|
||||
|
||||
Type predicates are used to implement encodings for instructions with
|
||||
multiple type variables. The encoding tables are keyed by the controlling
|
||||
type variable, type predicates check any secondary type variables.
|
||||
|
||||
A type predicate is not bound to any specific instruction format.
|
||||
|
||||
:param value_arg: Index of the value argument to type check.
|
||||
:param value_type: The required value type.
|
||||
"""
|
||||
|
||||
def __init__(self, value_arg, value_type):
|
||||
# type: (int, ValueType) -> None
|
||||
assert value_arg >= 0
|
||||
assert value_type is not None
|
||||
self.value_arg = value_arg
|
||||
self.value_type = value_type
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
return 'args[{}]:{}'.format(self.value_arg, self.value_type)
|
||||
|
||||
def predicate_context(self):
|
||||
# type: () -> PredContext
|
||||
return instruction_context
|
||||
|
||||
def predicate_key(self):
|
||||
# type: () -> PredKey
|
||||
return ('typecheck', self.value_arg, self.value_type.name)
|
||||
|
||||
def predicate_leafs(self, leafs):
|
||||
# type: (Set[PredLeaf]) -> None
|
||||
leafs.add(self)
|
||||
|
||||
@staticmethod
|
||||
def typevar_check(inst, typevar, value_type):
|
||||
# type: (Instruction, TypeVar, ValueType) -> TypePredicate
|
||||
"""
|
||||
Return a type check predicate for the given type variable in `inst`.
|
||||
|
||||
The type variable must appear directly as the type of one of the
|
||||
operands to `inst`, so this is only guaranteed to work for secondary
|
||||
type variables.
|
||||
|
||||
Find an `inst` value operand whose type is determined by `typevar` and
|
||||
create a `TypePredicate` that checks that the type variable has the
|
||||
value `value_type`.
|
||||
"""
|
||||
# Find the first value operand whose type is `typevar`.
|
||||
value_arg = next(i for i, opnum in enumerate(inst.value_opnums)
|
||||
if inst.ins[opnum].typevar == typevar)
|
||||
return TypePredicate(value_arg, value_type)
|
||||
|
||||
def rust_predicate(self, prec):
|
||||
# type: (int) -> str
|
||||
"""
|
||||
Return Rust code for evaluating this predicate.
|
||||
|
||||
It is assumed that the context has `func` and `args` variables.
|
||||
"""
|
||||
return 'func.dfg.value_type(args[{}]) == {}'.format(
|
||||
self.value_arg, self.value_type.rust_name())
|
||||
|
||||
|
||||
class CtrlTypePredicate(object):
|
||||
"""
|
||||
An instruction predicate that checks the controlling type variable
|
||||
|
||||
:param value_type: The required value type.
|
||||
"""
|
||||
|
||||
def __init__(self, value_type):
|
||||
# type: (ValueType) -> None
|
||||
assert value_type is not None
|
||||
self.value_type = value_type
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
return 'ctrl_typevar:{}'.format(self.value_type)
|
||||
|
||||
def predicate_context(self):
|
||||
# type: () -> PredContext
|
||||
return instruction_context
|
||||
|
||||
def predicate_key(self):
|
||||
# type: () -> PredKey
|
||||
return ('ctrltypecheck', self.value_type.name)
|
||||
|
||||
def predicate_leafs(self, leafs):
|
||||
# type: (Set[PredLeaf]) -> None
|
||||
leafs.add(self)
|
||||
|
||||
def rust_predicate(self, prec):
|
||||
# type: (int) -> str
|
||||
"""
|
||||
Return Rust code for evaluating this predicate.
|
||||
|
||||
It is assumed that the context has `func` and `inst` variables.
|
||||
"""
|
||||
return 'func.dfg.ctrl_typevar(inst) == {}'.format(
|
||||
self.value_type.rust_name())
|
||||
413
cranelift/codegen/meta-python/cdsl/registers.py
Normal file
413
cranelift/codegen/meta-python/cdsl/registers.py
Normal file
@@ -0,0 +1,413 @@
|
||||
"""
|
||||
Register set definitions
|
||||
------------------------
|
||||
|
||||
Each ISA defines a separate register set that is used by the register allocator
|
||||
and the final binary encoding of machine code.
|
||||
|
||||
The CPU registers are first divided into disjoint register banks, represented
|
||||
by a `RegBank` instance. Registers in different register banks never interfere
|
||||
with each other. A typical CPU will have a general purpose and a floating point
|
||||
register bank.
|
||||
|
||||
A register bank consists of a number of *register units* which are the smallest
|
||||
indivisible units of allocation and interference. A register unit doesn't
|
||||
necessarily correspond to a particular number of bits in a register, it is more
|
||||
like a placeholder that can be used to determine of a register is taken or not.
|
||||
|
||||
The register allocator works with *register classes* which can allocate one or
|
||||
more register units at a time. A register class allocates more than one
|
||||
register unit at a time when its registers are composed of smaller allocatable
|
||||
units. For example, the ARM double precision floating point registers are
|
||||
composed of two single precision registers.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from . import is_power_of_two, next_power_of_two
|
||||
|
||||
|
||||
try:
|
||||
from typing import Sequence, Tuple, List, Dict, Any, Optional, TYPE_CHECKING # noqa
|
||||
if TYPE_CHECKING:
|
||||
from .isa import TargetISA # noqa
|
||||
# A tuple uniquely identifying a register class inside a register bank.
|
||||
# (width, bitmask)
|
||||
RCTup = Tuple[int, int]
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
# The number of 32-bit elements in a register unit mask
|
||||
MASK_LEN = 3
|
||||
|
||||
# The maximum total number of register units allowed.
|
||||
# This limit can be raised by also adjusting the RegUnitMask type in
|
||||
# src/isa/registers.rs.
|
||||
MAX_UNITS = MASK_LEN * 32
|
||||
|
||||
|
||||
class RegBank(object):
|
||||
"""
|
||||
A register bank belonging to an ISA.
|
||||
|
||||
A register bank controls a set of *register units* disjoint from all the
|
||||
other register banks in the ISA. The register units are numbered uniquely
|
||||
within the target ISA, and the units in a register bank form a contiguous
|
||||
sequence starting from a sufficiently aligned point that their low bits can
|
||||
be used directly when encoding machine code instructions.
|
||||
|
||||
Register units can be given generated names like `r0`, `r1`, ..., or a
|
||||
tuple of special register unit names can be provided.
|
||||
|
||||
:param name: Name of this register bank.
|
||||
:param doc: Documentation string.
|
||||
:param units: Number of register units.
|
||||
:param pressure_tracking: Enable tracking of register pressure.
|
||||
:param prefix: Prefix for generated unit names.
|
||||
:param names: Special names for the first units. May be shorter than
|
||||
`units`, the remaining units are named using `prefix`.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name, # type: str
|
||||
isa, # type: TargetISA
|
||||
doc, # type: str
|
||||
units, # type: int
|
||||
pressure_tracking=True, # type: bool
|
||||
prefix='r', # type: str
|
||||
names=() # type: Sequence[str]
|
||||
):
|
||||
# type: (...) -> None
|
||||
self.name = name
|
||||
self.isa = isa
|
||||
self.first_unit = 0
|
||||
self.units = units
|
||||
self.pressure_tracking = pressure_tracking
|
||||
self.prefix = prefix
|
||||
self.names = names
|
||||
self.classes = list() # type: List[RegClass]
|
||||
self.toprcs = list() # type: List[RegClass]
|
||||
|
||||
assert len(names) <= units
|
||||
|
||||
if isa.regbanks:
|
||||
# Get the next free unit number.
|
||||
last = isa.regbanks[-1]
|
||||
u = last.first_unit + last.units
|
||||
align = units
|
||||
if not is_power_of_two(align):
|
||||
align = next_power_of_two(align)
|
||||
self.first_unit = (u + align - 1) & -align
|
||||
|
||||
self.index = len(isa.regbanks)
|
||||
isa.regbanks.append(self)
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return ('RegBank({}, units={}, first_unit={})'
|
||||
.format(self.name, self.units, self.first_unit))
|
||||
|
||||
def finish_regclasses(self):
|
||||
# type: () -> None
|
||||
"""
|
||||
Compute subclasses and the top-level register class.
|
||||
|
||||
Verify that the set of register classes satisfies:
|
||||
|
||||
1. Closed under intersection: The intersection of any two register
|
||||
classes in the set is either empty or identical to a member of the
|
||||
set.
|
||||
2. There are no identical classes under different names.
|
||||
3. Classes are sorted topologically such that all subclasses have a
|
||||
higher index that the superclass.
|
||||
|
||||
We could reorder classes topologically here instead of just enforcing
|
||||
the order, but the ordering tends to fall out naturally anyway.
|
||||
"""
|
||||
cmap = dict() # type: Dict[RCTup, RegClass]
|
||||
|
||||
for rc in self.classes:
|
||||
# All register classes must be given a name.
|
||||
assert rc.name, "Anonymous register class found"
|
||||
|
||||
# Check for duplicates.
|
||||
tup = rc.rctup()
|
||||
if tup in cmap:
|
||||
raise AssertionError(
|
||||
'{} and {} are identical register classes'
|
||||
.format(rc, cmap[tup]))
|
||||
cmap[tup] = rc
|
||||
|
||||
# Check intersections and topological order.
|
||||
for idx, rc1 in enumerate(self.classes):
|
||||
rc1.toprc = rc1
|
||||
for rc2 in self.classes[0:idx]:
|
||||
itup = rc1.intersect(rc2)
|
||||
if itup is None:
|
||||
continue
|
||||
if itup not in cmap:
|
||||
raise AssertionError(
|
||||
'intersection of {} and {} missing'
|
||||
.format(rc1, rc2))
|
||||
irc = cmap[itup]
|
||||
# rc1 > rc2, so rc2 can't be the sub-class.
|
||||
if irc is rc2:
|
||||
raise AssertionError(
|
||||
'Bad topological order: {}/{}'
|
||||
.format(rc1, rc2))
|
||||
if irc is rc1:
|
||||
# The intersection of rc1 and rc2 is rc1, so it must be a
|
||||
# sub-class.
|
||||
rc2.subclasses.append(rc1)
|
||||
rc1.toprc = rc2.toprc
|
||||
|
||||
if rc1.is_toprc():
|
||||
self.toprcs.append(rc1)
|
||||
|
||||
def unit_by_name(self, name):
|
||||
# type: (str) -> int
|
||||
"""
|
||||
Get a register unit in this bank by name.
|
||||
"""
|
||||
if name in self.names:
|
||||
r = self.names.index(name)
|
||||
elif name.startswith(self.prefix):
|
||||
r = int(name[len(self.prefix):])
|
||||
assert r < self.units, 'Invalid register name: ' + name
|
||||
return self.first_unit + r
|
||||
|
||||
|
||||
class RegClass(object):
|
||||
"""
|
||||
A register class is a subset of register units in a RegBank along with a
|
||||
strategy for allocating registers.
|
||||
|
||||
The *width* parameter determines how many register units are allocated at a
|
||||
time. Usually it that is one, but for example the ARM D registers are
|
||||
allocated two units at a time. When multiple units are allocated, it is
|
||||
always a contiguous set of unit numbers.
|
||||
|
||||
:param bank: The register bank we're allocating from.
|
||||
:param count: The maximum number of allocations in this register class. By
|
||||
default, the whole register bank can be allocated.
|
||||
:param width: How many units to allocate at a time.
|
||||
:param start: The first unit to allocate, relative to `bank.first.unit`.
|
||||
"""
|
||||
|
||||
def __init__(self, bank, count=0, width=1, start=0, bitmask=None):
|
||||
# type: (RegBank, int, int, int, Optional[int]) -> None
|
||||
self.name = None # type: str
|
||||
self.index = None # type: int
|
||||
self.bank = bank
|
||||
self.width = width
|
||||
self.bitmask = 0
|
||||
|
||||
# This is computed later in `finish_regclasses()`.
|
||||
self.subclasses = list() # type: List[RegClass]
|
||||
self.toprc = None # type: RegClass
|
||||
|
||||
assert width > 0
|
||||
|
||||
if bitmask:
|
||||
self.bitmask = bitmask
|
||||
else:
|
||||
assert start >= 0 and start < bank.units
|
||||
if count == 0:
|
||||
count = bank.units // width
|
||||
for a in range(count):
|
||||
u = start + a * self.width
|
||||
self.bitmask |= 1 << u
|
||||
|
||||
bank.classes.append(self)
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
return self.name
|
||||
|
||||
def is_toprc(self):
|
||||
# type: () -> bool
|
||||
"""
|
||||
Is this a top-level register class?
|
||||
|
||||
A top-level register class has no sub-classes. This can only be
|
||||
answered aster running `finish_regclasses()`.
|
||||
"""
|
||||
return self.toprc is self
|
||||
|
||||
def rctup(self):
|
||||
# type: () -> RCTup
|
||||
"""
|
||||
Get a tuple that uniquely identifies the registers in this class.
|
||||
|
||||
The tuple can be used as a dictionary key to ensure that there are no
|
||||
duplicate register classes.
|
||||
"""
|
||||
return (self.width, self.bitmask)
|
||||
|
||||
def intersect(self, other):
|
||||
# type: (RegClass) -> RCTup
|
||||
"""
|
||||
Get a tuple representing the intersection of two register classes.
|
||||
|
||||
Returns `None` if the two classes are disjoint.
|
||||
"""
|
||||
if self.width != other.width:
|
||||
return None
|
||||
intersection = self.bitmask & other.bitmask
|
||||
if intersection == 0:
|
||||
return None
|
||||
|
||||
return (self.width, intersection)
|
||||
|
||||
def __getitem__(self, sliced):
|
||||
# type: (slice) -> RegClass
|
||||
"""
|
||||
Create a sub-class of a register class using slice notation. The slice
|
||||
indexes refer to allocations in the parent register class, not register
|
||||
units.
|
||||
"""
|
||||
assert isinstance(sliced, slice), "RegClass slicing can't be 1 reg"
|
||||
# We could add strided sub-classes if needed.
|
||||
assert sliced.step is None, 'Subclass striding not supported'
|
||||
# Can't slice a non-contiguous class
|
||||
assert self.is_contiguous(), 'Cannot slice non-contiguous RegClass'
|
||||
|
||||
w = self.width
|
||||
s = self.start() + sliced.start * w
|
||||
c = sliced.stop - sliced.start
|
||||
assert c > 1, "Can't have single-register classes"
|
||||
|
||||
return RegClass(self.bank, count=c, width=w, start=s)
|
||||
|
||||
def without(self, *registers):
|
||||
# type: (*Register) -> RegClass
|
||||
"""
|
||||
Create a sub-class of a register class excluding a specific set of
|
||||
registers.
|
||||
|
||||
For example: GPR.without(GPR.r9)
|
||||
"""
|
||||
bm = self.bitmask
|
||||
w = self.width
|
||||
fmask = (1 << self.width) - 1
|
||||
for reg in registers:
|
||||
bm &= ~(fmask << (reg.unit * w))
|
||||
|
||||
return RegClass(self.bank, bitmask=bm)
|
||||
|
||||
def is_contiguous(self):
|
||||
# type: () -> bool
|
||||
"""
|
||||
Returns boolean indicating whether a register class is a contiguous set
|
||||
of register units.
|
||||
"""
|
||||
x = self.bitmask | (self.bitmask-1)
|
||||
return self.bitmask != 0 and ((x+1) & x) == 0
|
||||
|
||||
def start(self):
|
||||
# type: () -> int
|
||||
"""
|
||||
Returns the first valid register unit in this class.
|
||||
"""
|
||||
start = 0
|
||||
bm = self.bitmask
|
||||
fmask = (1 << self.width) - 1
|
||||
while True:
|
||||
if bm & fmask > 0:
|
||||
break
|
||||
start += 1
|
||||
bm >>= self.width
|
||||
|
||||
return start
|
||||
|
||||
def __getattr__(self, attr):
|
||||
# type: (str) -> Register
|
||||
"""
|
||||
Get a specific register in the class by name.
|
||||
|
||||
For example: `GPR.r5`.
|
||||
"""
|
||||
reg = Register(self, self.bank.unit_by_name(attr))
|
||||
# Save this register so we won't have to create it again.
|
||||
setattr(self, attr, reg)
|
||||
return reg
|
||||
|
||||
def mask(self):
|
||||
# type: () -> List[int]
|
||||
"""
|
||||
Compute a bit-mask of the register units allocated by this register
|
||||
class.
|
||||
|
||||
Return as a list of 32-bit integers.
|
||||
"""
|
||||
out_mask = []
|
||||
mask32 = (1 << 32) - 1
|
||||
bitmask = self.bitmask << self.bank.first_unit
|
||||
for i in range(MASK_LEN):
|
||||
out_mask.append((bitmask >> (i * 32)) & mask32)
|
||||
|
||||
return out_mask
|
||||
|
||||
def subclass_mask(self):
|
||||
# type: () -> int
|
||||
"""
|
||||
Compute a bit-mask of subclasses, including self.
|
||||
"""
|
||||
m = 1 << self.index
|
||||
for rc in self.subclasses:
|
||||
m |= 1 << rc.index
|
||||
return m
|
||||
|
||||
@staticmethod
|
||||
def extract_names(globs):
|
||||
# type: (Dict[str, Any]) -> None
|
||||
"""
|
||||
Given a dict mapping name -> object as returned by `globals()`, find
|
||||
all the RegClass objects and set their name from the dict key.
|
||||
This is used to name a bunch of global values in a module.
|
||||
"""
|
||||
for name, obj in globs.items():
|
||||
if isinstance(obj, RegClass):
|
||||
assert obj.name is None
|
||||
obj.name = name
|
||||
|
||||
|
||||
class Register(object):
|
||||
"""
|
||||
A specific register in a register class.
|
||||
|
||||
A register is identified by the top-level register class it belongs to and
|
||||
its first register unit.
|
||||
|
||||
Specific registers are used to describe constraints on instructions where
|
||||
some operands must use a fixed register.
|
||||
|
||||
Register instances can be created with the constructor, or accessed as
|
||||
attributes on the register class: `GPR.rcx`.
|
||||
"""
|
||||
def __init__(self, rc, unit):
|
||||
# type: (RegClass, int) -> None
|
||||
self.regclass = rc
|
||||
self.unit = unit
|
||||
|
||||
|
||||
class Stack(object):
|
||||
"""
|
||||
An operand that must be in a stack slot.
|
||||
|
||||
A `Stack` object can be used to indicate an operand constraint for a value
|
||||
operand that must live in a stack slot.
|
||||
"""
|
||||
def __init__(self, rc):
|
||||
# type: (RegClass) -> None
|
||||
self.regclass = rc
|
||||
|
||||
def stack_base_mask(self):
|
||||
# type: () -> str
|
||||
"""
|
||||
Get the StackBaseMask to use for this operand.
|
||||
|
||||
This is a mask of base registers that can be supported by this operand.
|
||||
"""
|
||||
# TODO: Make this configurable instead of just using the SP.
|
||||
return 'StackBaseMask(1)'
|
||||
407
cranelift/codegen/meta-python/cdsl/settings.py
Normal file
407
cranelift/codegen/meta-python/cdsl/settings.py
Normal file
@@ -0,0 +1,407 @@
|
||||
"""Classes for describing settings and groups of settings."""
|
||||
from __future__ import absolute_import
|
||||
from collections import OrderedDict
|
||||
from .predicates import Predicate
|
||||
|
||||
try:
|
||||
from typing import Tuple, Set, List, Dict, Any, Union, TYPE_CHECKING # noqa
|
||||
BoolOrPresetOrDict = Union['BoolSetting', 'Preset', Dict['Setting', Any]]
|
||||
if TYPE_CHECKING:
|
||||
from .predicates import PredLeaf, PredNode, PredKey # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class Setting(object):
|
||||
"""
|
||||
A named setting variable that can be configured externally to Cranelift.
|
||||
|
||||
Settings are normally not named when they are created. They get their name
|
||||
from the `extract_names` method.
|
||||
"""
|
||||
|
||||
def __init__(self, doc):
|
||||
# type: (str) -> None
|
||||
self.name = None # type: str # Assigned later by `extract_names()`.
|
||||
self.__doc__ = doc
|
||||
# Offset of byte in settings vector containing this setting.
|
||||
self.byte_offset = None # type: int
|
||||
# Index into the generated DESCRIPTORS table.
|
||||
self.descriptor_index = None # type: int
|
||||
|
||||
self.group = SettingGroup.append(self)
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
return '{}.{}'.format(self.group.name, self.name)
|
||||
|
||||
def default_byte(self):
|
||||
# type: () -> int
|
||||
raise NotImplementedError("default_byte is an abstract method")
|
||||
|
||||
def byte_for_value(self, value):
|
||||
# type: (Any) -> int
|
||||
"""Get the setting byte value that corresponds to `value`"""
|
||||
raise NotImplementedError("byte_for_value is an abstract method")
|
||||
|
||||
def byte_mask(self):
|
||||
# type: () -> int
|
||||
"""Get a mask of bits in our byte that are relevant to this setting."""
|
||||
# Only BoolSetting has a different mask.
|
||||
return 0xff
|
||||
|
||||
|
||||
class BoolSetting(Setting):
|
||||
"""
|
||||
A named setting with a boolean on/off value.
|
||||
|
||||
:param doc: Documentation string.
|
||||
:param default: The default value of this setting.
|
||||
"""
|
||||
|
||||
def __init__(self, doc, default=False):
|
||||
# type: (str, bool) -> None
|
||||
super(BoolSetting, self).__init__(doc)
|
||||
self.default = default
|
||||
self.bit_offset = None # type: int
|
||||
|
||||
def default_byte(self):
|
||||
# type: () -> int
|
||||
"""
|
||||
Get the default value of this setting, as a byte that can be bitwise
|
||||
or'ed with the other booleans sharing the same byte.
|
||||
"""
|
||||
if self.default:
|
||||
return 1 << self.bit_offset
|
||||
else:
|
||||
return 0
|
||||
|
||||
def byte_for_value(self, value):
|
||||
# type: (Any) -> int
|
||||
if value:
|
||||
return 1 << self.bit_offset
|
||||
else:
|
||||
return 0
|
||||
|
||||
def byte_mask(self):
|
||||
# type: () -> int
|
||||
return 1 << self.bit_offset
|
||||
|
||||
def predicate_context(self):
|
||||
# type: () -> SettingGroup
|
||||
"""
|
||||
Return the context where this setting can be evaluated as a (leaf)
|
||||
predicate.
|
||||
"""
|
||||
return self.group
|
||||
|
||||
def predicate_key(self):
|
||||
# type: () -> PredKey
|
||||
assert self.name, "Can't compute key before setting is named"
|
||||
return ('setting', self.group.name, self.name)
|
||||
|
||||
def predicate_leafs(self, leafs):
|
||||
# type: (Set[PredLeaf]) -> None
|
||||
leafs.add(self)
|
||||
|
||||
def rust_predicate(self, prec):
|
||||
# type: (int) -> str
|
||||
"""
|
||||
Return the Rust code to compute the value of this setting.
|
||||
|
||||
The emitted code assumes that the setting group exists as a local
|
||||
variable.
|
||||
"""
|
||||
return '{}.{}()'.format(self.group.name, self.name)
|
||||
|
||||
|
||||
class NumSetting(Setting):
|
||||
"""
|
||||
A named setting with an integral value in the range 0--255.
|
||||
|
||||
:param doc: Documentation string.
|
||||
:param default: The default value of this setting.
|
||||
"""
|
||||
|
||||
def __init__(self, doc, default=0):
|
||||
# type: (str, int) -> None
|
||||
super(NumSetting, self).__init__(doc)
|
||||
assert default == int(default)
|
||||
assert default >= 0 and default <= 255
|
||||
self.default = default
|
||||
|
||||
def default_byte(self):
|
||||
# type: () -> int
|
||||
return self.default
|
||||
|
||||
def byte_for_value(self, value):
|
||||
# type: (Any) -> int
|
||||
assert isinstance(value, int), "NumSetting must be set to an int"
|
||||
assert value >= 0 and value <= 255
|
||||
return value
|
||||
|
||||
|
||||
class EnumSetting(Setting):
|
||||
"""
|
||||
A named setting with an enumerated set of possible values.
|
||||
|
||||
The default value is always the first enumerator.
|
||||
|
||||
:param doc: Documentation string.
|
||||
:param args: Tuple of unique strings representing the possible values.
|
||||
"""
|
||||
|
||||
def __init__(self, doc, *args):
|
||||
# type: (str, *str) -> None
|
||||
super(EnumSetting, self).__init__(doc)
|
||||
assert len(args) > 0, "EnumSetting must have at least one value"
|
||||
self.values = tuple(str(x) for x in args)
|
||||
self.default = self.values[0]
|
||||
|
||||
def default_byte(self):
|
||||
# type: () -> int
|
||||
return 0
|
||||
|
||||
def byte_for_value(self, value):
|
||||
# type: (Any) -> int
|
||||
return self.values.index(value)
|
||||
|
||||
|
||||
class SettingGroup(object):
|
||||
"""
|
||||
A group of settings.
|
||||
|
||||
Whenever a :class:`Setting` object is created, it is added to the currently
|
||||
open group. A setting group must be closed explicitly before another can be
|
||||
opened.
|
||||
|
||||
:param name: Short mnemonic name for setting group.
|
||||
:param parent: Parent settings group.
|
||||
"""
|
||||
|
||||
# The currently open setting group.
|
||||
_current = None # type: SettingGroup
|
||||
|
||||
def __init__(self, name, parent=None):
|
||||
# type: (str, SettingGroup) -> None
|
||||
self.name = name
|
||||
self.parent = parent
|
||||
self.settings = [] # type: List[Setting]
|
||||
# Named predicates computed from settings in this group or its
|
||||
# parents.
|
||||
self.named_predicates = OrderedDict() # type: OrderedDict[str, Predicate] # noqa
|
||||
# All boolean predicates that can be accessed by number. This includes:
|
||||
# - All boolean settings in this group.
|
||||
# - All named predicates.
|
||||
# - Added anonymous predicates, see `number_predicate()`.
|
||||
# - Added parent predicates that are replicated in this group.
|
||||
# Maps predicate -> number.
|
||||
self.predicate_number = OrderedDict() # type: OrderedDict[PredNode, int] # noqa
|
||||
self.presets = [] # type: List[Preset]
|
||||
|
||||
# Fully qualified Rust module name. See gen_settings.py.
|
||||
self.qual_mod = None # type: str
|
||||
|
||||
self.open()
|
||||
|
||||
def open(self):
|
||||
# type: () -> None
|
||||
"""
|
||||
Open this setting group such that future new settings are added to this
|
||||
group.
|
||||
"""
|
||||
assert SettingGroup._current is None, (
|
||||
"Can't open {} since {} is already open"
|
||||
.format(self, SettingGroup._current))
|
||||
SettingGroup._current = self
|
||||
|
||||
def close(self, globs=None):
|
||||
# type: (Dict[str, Any]) -> None
|
||||
"""
|
||||
Close this setting group. This function must be called before opening
|
||||
another setting group.
|
||||
|
||||
:param globs: Pass in `globals()` to run `extract_names` on all
|
||||
settings defined in the module.
|
||||
"""
|
||||
assert SettingGroup._current is self, (
|
||||
"Can't close {}, the open setting group is {}"
|
||||
.format(self, SettingGroup._current))
|
||||
SettingGroup._current = None
|
||||
if globs:
|
||||
for name, obj in globs.items():
|
||||
if isinstance(obj, Setting):
|
||||
assert obj.name is None, obj.name
|
||||
obj.name = name
|
||||
if isinstance(obj, Predicate):
|
||||
self.named_predicates[name] = obj
|
||||
if isinstance(obj, Preset):
|
||||
assert obj.name is None, obj.name
|
||||
obj.name = name
|
||||
|
||||
self.layout()
|
||||
|
||||
@staticmethod
|
||||
def append(setting):
|
||||
# type: (Setting) -> SettingGroup
|
||||
g = SettingGroup._current
|
||||
assert g, "Open a setting group before defining settings."
|
||||
g.settings.append(setting)
|
||||
return g
|
||||
|
||||
@staticmethod
|
||||
def append_preset(preset):
|
||||
# type: (Preset) -> SettingGroup
|
||||
g = SettingGroup._current
|
||||
assert g, "Open a setting group before defining presets."
|
||||
g.presets.append(preset)
|
||||
return g
|
||||
|
||||
def number_predicate(self, pred):
|
||||
# type: (PredNode) -> int
|
||||
"""
|
||||
Make sure that `pred` has an assigned number, and will be included in
|
||||
this group's bit vector.
|
||||
|
||||
The numbered predicates include:
|
||||
- `BoolSetting` settings that belong to this group.
|
||||
- `Predicate` instances in `named_predicates`.
|
||||
- `Predicate` instances without a name.
|
||||
- Settings or computed predicates that belong to the parent group, but
|
||||
need to be accessible by number in this group.
|
||||
|
||||
The numbered predicates are referenced by the encoding tables as ISA
|
||||
predicates. See the `isap` field on `Encoding`.
|
||||
|
||||
:returns: The assigned predicate number in this group.
|
||||
"""
|
||||
if pred in self.predicate_number:
|
||||
return self.predicate_number[pred]
|
||||
else:
|
||||
number = len(self.predicate_number)
|
||||
self.predicate_number[pred] = number
|
||||
return number
|
||||
|
||||
def layout(self):
|
||||
# type: () -> None
|
||||
"""
|
||||
Compute the layout of the byte vector used to represent this settings
|
||||
group.
|
||||
|
||||
The byte vector contains the following entries in order:
|
||||
|
||||
1. Byte-sized settings like `NumSetting` and `EnumSetting`.
|
||||
2. `BoolSetting` settings.
|
||||
3. Precomputed named predicates.
|
||||
4. Other numbered predicates, including anonymous predicates and parent
|
||||
predicates that need to be accessible by number.
|
||||
|
||||
Set `self.settings_size` to the length of the byte vector prefix that
|
||||
contains the settings. All bytes after that are computed, not
|
||||
configured.
|
||||
|
||||
Set `self.boolean_offset` to the beginning of the numbered predicates,
|
||||
2. in the list above.
|
||||
|
||||
Assign `byte_offset` and `bit_offset` fields in all settings.
|
||||
|
||||
After calling this method, no more settings can be added, but
|
||||
additional predicates can be made accessible with `number_predicate()`.
|
||||
"""
|
||||
assert len(self.predicate_number) == 0, "Too late for layout"
|
||||
|
||||
# Assign the non-boolean settings.
|
||||
byte_offset = 0
|
||||
for s in self.settings:
|
||||
if not isinstance(s, BoolSetting):
|
||||
s.byte_offset = byte_offset
|
||||
byte_offset += 1
|
||||
|
||||
# Then the boolean settings.
|
||||
self.boolean_offset = byte_offset
|
||||
for s in self.settings:
|
||||
if isinstance(s, BoolSetting):
|
||||
number = self.number_predicate(s)
|
||||
s.byte_offset = byte_offset + number // 8
|
||||
s.bit_offset = number % 8
|
||||
|
||||
# This is the end of the settings. Round up to a whole number of bytes.
|
||||
self.boolean_settings = len(self.predicate_number)
|
||||
self.settings_size = self.byte_size()
|
||||
|
||||
# Now assign numbers to all our named predicates.
|
||||
for name, pred in self.named_predicates.items():
|
||||
self.number_predicate(pred)
|
||||
|
||||
def byte_size(self):
|
||||
# type: () -> int
|
||||
"""
|
||||
Compute the number of bytes required to hold all settings and
|
||||
precomputed predicates.
|
||||
|
||||
This is the size of the byte-sized settings plus all the numbered
|
||||
predicate bits rounded up to a whole number of bytes.
|
||||
"""
|
||||
return self.boolean_offset + (len(self.predicate_number) + 7) // 8
|
||||
|
||||
|
||||
class Preset(object):
|
||||
"""
|
||||
A collection of setting values that are applied at once.
|
||||
|
||||
A `Preset` represents a shorthand notation for applying a number of
|
||||
settings at once. Example:
|
||||
|
||||
nehalem = Preset(has_sse41, has_cmov, has_avx=0)
|
||||
|
||||
Enabling the `nehalem` setting is equivalent to enabling `has_sse41` and
|
||||
`has_cmov` while disabling the `has_avx` setting.
|
||||
"""
|
||||
|
||||
def __init__(self, *args):
|
||||
# type: (*BoolOrPresetOrDict) -> None
|
||||
self.name = None # type: str # Assigned later by `SettingGroup`.
|
||||
# Each tuple provides the value for a setting.
|
||||
self.values = list() # type: List[Tuple[Setting, Any]]
|
||||
|
||||
for arg in args:
|
||||
if isinstance(arg, Preset):
|
||||
# Any presets in args are immediately expanded.
|
||||
self.values.extend(arg.values)
|
||||
elif isinstance(arg, dict):
|
||||
# A dictionary of key: value pairs.
|
||||
self.values.extend(arg.items())
|
||||
else:
|
||||
# A BoolSetting to enable.
|
||||
assert isinstance(arg, BoolSetting)
|
||||
self.values.append((arg, True))
|
||||
|
||||
self.group = SettingGroup.append_preset(self)
|
||||
# Index into the generated DESCRIPTORS table.
|
||||
self.descriptor_index = None # type: int
|
||||
|
||||
def layout(self):
|
||||
# type: () -> List[Tuple[int, int]]
|
||||
"""
|
||||
Compute a list of (mask, byte) pairs that incorporate all values in
|
||||
this preset.
|
||||
|
||||
The list will have an entry for each setting byte in the settings
|
||||
group.
|
||||
"""
|
||||
lst = [(0, 0)] * self.group.settings_size
|
||||
|
||||
# Apply setting values in order.
|
||||
for s, v in self.values:
|
||||
ofs = s.byte_offset
|
||||
s_mask = s.byte_mask()
|
||||
s_val = s.byte_for_value(v)
|
||||
assert (s_val & ~s_mask) == 0
|
||||
l_mask, l_val = lst[ofs]
|
||||
# Accumulated mask of modified bits.
|
||||
l_mask |= s_mask
|
||||
# Overwrite the relevant bits with the new value.
|
||||
l_val = (l_val & ~s_mask) | s_val
|
||||
lst[ofs] = (l_mask, l_val)
|
||||
|
||||
return lst
|
||||
28
cranelift/codegen/meta-python/cdsl/test_ast.py
Normal file
28
cranelift/codegen/meta-python/cdsl/test_ast.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from __future__ import absolute_import
|
||||
from unittest import TestCase
|
||||
from doctest import DocTestSuite
|
||||
from . import ast
|
||||
from base.instructions 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'))")
|
||||
8
cranelift/codegen/meta-python/cdsl/test_package.py
Normal file
8
cranelift/codegen/meta-python/cdsl/test_package.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from __future__ import absolute_import
|
||||
import doctest
|
||||
import cdsl
|
||||
|
||||
|
||||
def load_tests(loader, tests, ignore):
|
||||
tests.addTests(doctest.DocTestSuite(cdsl))
|
||||
return tests
|
||||
605
cranelift/codegen/meta-python/cdsl/test_ti.py
Normal file
605
cranelift/codegen/meta-python/cdsl/test_ti.py
Normal file
@@ -0,0 +1,605 @@
|
||||
from __future__ import absolute_import
|
||||
from base.instructions import vselect, vsplit, vconcat, iconst, iadd, bint,\
|
||||
b1, icmp, iadd_cout, iadd_cin, uextend, sextend, ireduce, fpromote, \
|
||||
fdemote
|
||||
from base.legalize import narrow, expand
|
||||
from base.immediates import intcc
|
||||
from base.types import i32, i8
|
||||
from .typevar import TypeVar
|
||||
from .ast import Var, Def
|
||||
from .xform import Rtl, XForm
|
||||
from .ti import ti_rtl, subst, TypeEnv, get_type_env, TypesEqual, WiderOrEq
|
||||
from unittest import TestCase
|
||||
from functools import reduce
|
||||
|
||||
try:
|
||||
from .ti import TypeMap, ConstraintList, VarTyping, TypingOrError # noqa
|
||||
from typing import List, Dict, Tuple, TYPE_CHECKING, cast # noqa
|
||||
except ImportError:
|
||||
TYPE_CHECKING = False
|
||||
|
||||
|
||||
def agree(me, other):
|
||||
# type: (TypeEnv, TypeEnv) -> bool
|
||||
"""
|
||||
Given TypeEnvs me and other, check if they agree. As part of that build
|
||||
a map m from TVs in me to their corresponding TVs in other.
|
||||
Specifically:
|
||||
|
||||
1. Check that all TVs that are keys in me.type_map are also defined
|
||||
in other.type_map
|
||||
|
||||
2. For any tv in me.type_map check that:
|
||||
me[tv].get_typeset() == other[tv].get_typeset()
|
||||
|
||||
3. Set m[me[tv]] = other[tv] in the substitution m
|
||||
|
||||
4. If we find another tv1 such that me[tv1] == me[tv], assert that
|
||||
other[tv1] == m[me[tv1]] == m[me[tv]] = other[tv]
|
||||
|
||||
5. Check that me and other have the same constraints under the
|
||||
substitution m
|
||||
"""
|
||||
m = {} # type: TypeMap
|
||||
# Check that our type map and other's agree and built substitution m
|
||||
for tv in me.type_map:
|
||||
if (me[tv] not in m):
|
||||
m[me[tv]] = other[tv]
|
||||
if me[tv].get_typeset() != other[tv].get_typeset():
|
||||
return False
|
||||
else:
|
||||
if m[me[tv]] != other[tv]:
|
||||
return False
|
||||
|
||||
# Translate our constraints using m, and sort
|
||||
me_equiv_constr = sorted([constr.translate(m)
|
||||
for constr in me.constraints], key=repr)
|
||||
# Sort other's constraints
|
||||
other_equiv_constr = sorted([constr.translate(other)
|
||||
for constr in other.constraints], key=repr)
|
||||
return me_equiv_constr == other_equiv_constr
|
||||
|
||||
|
||||
def check_typing(got_or_err, expected, symtab=None):
|
||||
# type: (TypingOrError, Tuple[VarTyping, ConstraintList], Dict[str, Var]) -> None # noqa
|
||||
"""
|
||||
Check that a the typing we received (got_or_err) complies with the
|
||||
expected typing (expected). If symtab is specified, substitute the Vars in
|
||||
expected using symtab first (used when checking type inference on XForms)
|
||||
"""
|
||||
(m, c) = expected
|
||||
got = get_type_env(got_or_err)
|
||||
|
||||
if (symtab is not None):
|
||||
# For xforms we first need to re-write our TVs in terms of the tvs
|
||||
# stored internally in the XForm. Use the symtab passed
|
||||
subst_m = {k.get_typevar(): symtab[str(k)].get_typevar()
|
||||
for k in m.keys()}
|
||||
# Convert m from a Var->TypeVar map to TypeVar->TypeVar map where
|
||||
# the key TypeVar is re-written to its XForm internal version
|
||||
tv_m = {subst(k.get_typevar(), subst_m): v for (k, v) in m.items()}
|
||||
# Rewrite the TVs in the input constraints to their XForm internal
|
||||
# versions
|
||||
c = [constr.translate(subst_m) for constr in c]
|
||||
else:
|
||||
# If no symtab, just convert m from Var->TypeVar map to a
|
||||
# TypeVar->TypeVar map
|
||||
tv_m = {k.get_typevar(): v for (k, v) in m.items()}
|
||||
|
||||
expected_typ = TypeEnv((tv_m, c))
|
||||
assert agree(expected_typ, got), \
|
||||
"typings disagree:\n {} \n {}".format(got.dot(),
|
||||
expected_typ.dot())
|
||||
|
||||
|
||||
def check_concrete_typing_rtl(var_types, rtl):
|
||||
# type: (VarTyping, Rtl) -> None
|
||||
"""
|
||||
Check that a concrete type assignment var_types (Dict[Var, TypeVar]) is
|
||||
valid for an Rtl rtl. Specifically check that:
|
||||
|
||||
1) For each Var v \\in rtl, v is defined in var_types
|
||||
|
||||
2) For all v, var_types[v] is a singleton type
|
||||
|
||||
3) For each v, and each location u, where v is used with expected type
|
||||
tv_u, var_types[v].get_typeset() is a subset of
|
||||
subst(tv_u, m).get_typeset() where m is the substitution of
|
||||
formals->actuals we are building so far.
|
||||
|
||||
4) If tv_u is non-derived and not in m, set m[tv_u]= var_types[v]
|
||||
"""
|
||||
for d in rtl.rtl:
|
||||
assert isinstance(d, Def)
|
||||
inst = d.expr.inst
|
||||
# Accumulate all actual TVs for value defs/opnums in actual_tvs
|
||||
actual_tvs = [var_types[d.defs[i]] for i in inst.value_results]
|
||||
for v in [d.expr.args[i] for i in inst.value_opnums]:
|
||||
assert isinstance(v, Var)
|
||||
actual_tvs.append(var_types[v])
|
||||
|
||||
# Accumulate all formal TVs for value defs/opnums in actual_tvs
|
||||
formal_tvs = [inst.outs[i].typevar for i in inst.value_results] +\
|
||||
[inst.ins[i].typevar for i in inst.value_opnums]
|
||||
m = {} # type: TypeMap
|
||||
|
||||
# For each actual/formal pair check that they agree
|
||||
for (actual_tv, formal_tv) in zip(actual_tvs, formal_tvs):
|
||||
# actual should be a singleton
|
||||
assert actual_tv.singleton_type() is not None
|
||||
formal_tv = subst(formal_tv, m)
|
||||
# actual should agree with the concretized formal
|
||||
assert actual_tv.get_typeset().issubset(formal_tv.get_typeset())
|
||||
|
||||
if formal_tv not in m and not formal_tv.is_derived:
|
||||
m[formal_tv] = actual_tv
|
||||
|
||||
|
||||
def check_concrete_typing_xform(var_types, xform):
|
||||
# type: (VarTyping, XForm) -> None
|
||||
"""
|
||||
Check a concrete type assignment var_types for an XForm xform
|
||||
"""
|
||||
check_concrete_typing_rtl(var_types, xform.src)
|
||||
check_concrete_typing_rtl(var_types, xform.dst)
|
||||
|
||||
|
||||
class TypeCheckingBaseTest(TestCase):
|
||||
def setUp(self):
|
||||
# type: () -> None
|
||||
self.v0 = Var("v0")
|
||||
self.v1 = Var("v1")
|
||||
self.v2 = Var("v2")
|
||||
self.v3 = Var("v3")
|
||||
self.v4 = Var("v4")
|
||||
self.v5 = Var("v5")
|
||||
self.v6 = Var("v6")
|
||||
self.v7 = Var("v7")
|
||||
self.v8 = Var("v8")
|
||||
self.v9 = Var("v9")
|
||||
self.imm0 = Var("imm0")
|
||||
self.IxN_nonscalar = TypeVar("IxN", "", ints=True, scalars=False,
|
||||
simd=True)
|
||||
self.TxN = TypeVar("TxN", "", ints=True, bools=True, floats=True,
|
||||
scalars=False, simd=True)
|
||||
self.b1 = TypeVar.singleton(b1)
|
||||
|
||||
|
||||
class TestRTL(TypeCheckingBaseTest):
|
||||
def test_bad_rtl1(self):
|
||||
# type: () -> None
|
||||
r = Rtl(
|
||||
(self.v0, self.v1) << vsplit(self.v2),
|
||||
self.v3 << vconcat(self.v0, self.v2),
|
||||
)
|
||||
ti = TypeEnv()
|
||||
self.assertEqual(ti_rtl(r, ti),
|
||||
"On line 1: fail ti on `typeof_v2` <: `1`: " +
|
||||
"Error: empty type created when unifying " +
|
||||
"`typeof_v2` and `half_vector(typeof_v2)`")
|
||||
|
||||
def test_vselect(self):
|
||||
# type: () -> None
|
||||
r = Rtl(
|
||||
self.v0 << vselect(self.v1, self.v2, self.v3),
|
||||
)
|
||||
ti = TypeEnv()
|
||||
typing = ti_rtl(r, ti)
|
||||
txn = self.TxN.get_fresh_copy("TxN1")
|
||||
check_typing(typing, ({
|
||||
self.v0: txn,
|
||||
self.v1: txn.as_bool(),
|
||||
self.v2: txn,
|
||||
self.v3: txn
|
||||
}, []))
|
||||
|
||||
def test_vselect_icmpimm(self):
|
||||
# type: () -> None
|
||||
r = Rtl(
|
||||
self.v0 << iconst(self.imm0),
|
||||
self.v1 << icmp(intcc.eq, self.v2, self.v0),
|
||||
self.v5 << vselect(self.v1, self.v3, self.v4),
|
||||
)
|
||||
ti = TypeEnv()
|
||||
typing = ti_rtl(r, ti)
|
||||
ixn = self.IxN_nonscalar.get_fresh_copy("IxN1")
|
||||
txn = self.TxN.get_fresh_copy("TxN1")
|
||||
check_typing(typing, ({
|
||||
self.v0: ixn,
|
||||
self.v1: ixn.as_bool(),
|
||||
self.v2: ixn,
|
||||
self.v3: txn,
|
||||
self.v4: txn,
|
||||
self.v5: txn,
|
||||
}, [TypesEqual(ixn.as_bool(), txn.as_bool())]))
|
||||
|
||||
def test_vselect_vsplits(self):
|
||||
# type: () -> None
|
||||
r = Rtl(
|
||||
self.v3 << vselect(self.v0, self.v1, self.v2),
|
||||
(self.v4, self.v5) << vsplit(self.v3),
|
||||
(self.v6, self.v7) << vsplit(self.v4),
|
||||
)
|
||||
ti = TypeEnv()
|
||||
typing = ti_rtl(r, ti)
|
||||
t = TypeVar("t", "", ints=True, bools=True, floats=True,
|
||||
simd=(4, 256))
|
||||
check_typing(typing, ({
|
||||
self.v0: t.as_bool(),
|
||||
self.v1: t,
|
||||
self.v2: t,
|
||||
self.v3: t,
|
||||
self.v4: t.half_vector(),
|
||||
self.v5: t.half_vector(),
|
||||
self.v6: t.half_vector().half_vector(),
|
||||
self.v7: t.half_vector().half_vector(),
|
||||
}, []))
|
||||
|
||||
def test_vselect_vconcats(self):
|
||||
# type: () -> None
|
||||
r = Rtl(
|
||||
self.v3 << vselect(self.v0, self.v1, self.v2),
|
||||
self.v8 << vconcat(self.v3, self.v3),
|
||||
self.v9 << vconcat(self.v8, self.v8),
|
||||
)
|
||||
ti = TypeEnv()
|
||||
typing = ti_rtl(r, ti)
|
||||
t = TypeVar("t", "", ints=True, bools=True, floats=True,
|
||||
simd=(2, 64))
|
||||
check_typing(typing, ({
|
||||
self.v0: t.as_bool(),
|
||||
self.v1: t,
|
||||
self.v2: t,
|
||||
self.v3: t,
|
||||
self.v8: t.double_vector(),
|
||||
self.v9: t.double_vector().double_vector(),
|
||||
}, []))
|
||||
|
||||
def test_vselect_vsplits_vconcats(self):
|
||||
# type: () -> None
|
||||
r = Rtl(
|
||||
self.v3 << vselect(self.v0, self.v1, self.v2),
|
||||
(self.v4, self.v5) << vsplit(self.v3),
|
||||
(self.v6, self.v7) << vsplit(self.v4),
|
||||
self.v8 << vconcat(self.v3, self.v3),
|
||||
self.v9 << vconcat(self.v8, self.v8),
|
||||
)
|
||||
ti = TypeEnv()
|
||||
typing = ti_rtl(r, ti)
|
||||
t = TypeVar("t", "", ints=True, bools=True, floats=True,
|
||||
simd=(4, 64))
|
||||
check_typing(typing, ({
|
||||
self.v0: t.as_bool(),
|
||||
self.v1: t,
|
||||
self.v2: t,
|
||||
self.v3: t,
|
||||
self.v4: t.half_vector(),
|
||||
self.v5: t.half_vector(),
|
||||
self.v6: t.half_vector().half_vector(),
|
||||
self.v7: t.half_vector().half_vector(),
|
||||
self.v8: t.double_vector(),
|
||||
self.v9: t.double_vector().double_vector(),
|
||||
}, []))
|
||||
|
||||
def test_bint(self):
|
||||
# type: () -> None
|
||||
r = Rtl(
|
||||
self.v4 << iadd(self.v1, self.v2),
|
||||
self.v5 << bint(self.v3),
|
||||
self.v0 << iadd(self.v4, self.v5)
|
||||
)
|
||||
ti = TypeEnv()
|
||||
typing = ti_rtl(r, ti)
|
||||
itype = TypeVar("t", "", ints=True, simd=(1, 256))
|
||||
btype = TypeVar("b", "", bools=True, simd=True)
|
||||
|
||||
# Check that self.v5 gets the same integer type as
|
||||
# the rest of them
|
||||
# TODO: Add constraint nlanes(v3) == nlanes(v1) when we
|
||||
# add that type constraint to bint
|
||||
check_typing(typing, ({
|
||||
self.v1: itype,
|
||||
self.v2: itype,
|
||||
self.v4: itype,
|
||||
self.v5: itype,
|
||||
self.v3: btype,
|
||||
self.v0: itype,
|
||||
}, []))
|
||||
|
||||
def test_fully_bound_inst_inference_bad(self):
|
||||
# Incompatible bound instructions fail accordingly
|
||||
r = Rtl(
|
||||
self.v3 << uextend.i32(self.v1),
|
||||
self.v4 << uextend.i16(self.v2),
|
||||
self.v5 << iadd(self.v3, self.v4),
|
||||
)
|
||||
ti = TypeEnv()
|
||||
typing = ti_rtl(r, ti)
|
||||
|
||||
self.assertEqual(typing,
|
||||
"On line 2: fail ti on `typeof_v4` <: `4`: " +
|
||||
"Error: empty type created when unifying " +
|
||||
"`i16` and `i32`")
|
||||
|
||||
def test_extend_reduce(self):
|
||||
# type: () -> None
|
||||
r = Rtl(
|
||||
self.v1 << uextend(self.v0),
|
||||
self.v2 << ireduce(self.v1),
|
||||
self.v3 << sextend(self.v2),
|
||||
)
|
||||
ti = TypeEnv()
|
||||
typing = ti_rtl(r, ti)
|
||||
typing = typing.extract()
|
||||
|
||||
itype0 = TypeVar("t", "", ints=True, simd=(1, 256))
|
||||
itype1 = TypeVar("t1", "", ints=True, simd=(1, 256))
|
||||
itype2 = TypeVar("t2", "", ints=True, simd=(1, 256))
|
||||
itype3 = TypeVar("t3", "", ints=True, simd=(1, 256))
|
||||
|
||||
check_typing(typing, ({
|
||||
self.v0: itype0,
|
||||
self.v1: itype1,
|
||||
self.v2: itype2,
|
||||
self.v3: itype3,
|
||||
}, [WiderOrEq(itype1, itype0),
|
||||
WiderOrEq(itype1, itype2),
|
||||
WiderOrEq(itype3, itype2)]))
|
||||
|
||||
def test_extend_reduce_enumeration(self):
|
||||
# type: () -> None
|
||||
for op in (uextend, sextend, ireduce):
|
||||
r = Rtl(
|
||||
self.v1 << op(self.v0),
|
||||
)
|
||||
ti = TypeEnv()
|
||||
typing = ti_rtl(r, ti).extract()
|
||||
|
||||
# The number of possible typings is 9 * (3+ 2*2 + 3) = 90
|
||||
lst = [(t[self.v0], t[self.v1]) for t in typing.concrete_typings()]
|
||||
assert (len(lst) == len(set(lst)) and len(lst) == 90)
|
||||
for (tv0, tv1) in lst:
|
||||
typ0, typ1 = (tv0.singleton_type(), tv1.singleton_type())
|
||||
if (op == ireduce):
|
||||
assert typ0.wider_or_equal(typ1)
|
||||
else:
|
||||
assert typ1.wider_or_equal(typ0)
|
||||
|
||||
def test_fpromote_fdemote(self):
|
||||
# type: () -> None
|
||||
r = Rtl(
|
||||
self.v1 << fpromote(self.v0),
|
||||
self.v2 << fdemote(self.v1),
|
||||
)
|
||||
ti = TypeEnv()
|
||||
typing = ti_rtl(r, ti)
|
||||
typing = typing.extract()
|
||||
|
||||
ftype0 = TypeVar("t", "", floats=True, simd=(1, 256))
|
||||
ftype1 = TypeVar("t1", "", floats=True, simd=(1, 256))
|
||||
ftype2 = TypeVar("t2", "", floats=True, simd=(1, 256))
|
||||
|
||||
check_typing(typing, ({
|
||||
self.v0: ftype0,
|
||||
self.v1: ftype1,
|
||||
self.v2: ftype2,
|
||||
}, [WiderOrEq(ftype1, ftype0),
|
||||
WiderOrEq(ftype1, ftype2)]))
|
||||
|
||||
def test_fpromote_fdemote_enumeration(self):
|
||||
# type: () -> None
|
||||
for op in (fpromote, fdemote):
|
||||
r = Rtl(
|
||||
self.v1 << op(self.v0),
|
||||
)
|
||||
ti = TypeEnv()
|
||||
typing = ti_rtl(r, ti).extract()
|
||||
|
||||
# The number of possible typings is 9*(2 + 1) = 27
|
||||
lst = [(t[self.v0], t[self.v1]) for t in typing.concrete_typings()]
|
||||
assert (len(lst) == len(set(lst)) and len(lst) == 27)
|
||||
for (tv0, tv1) in lst:
|
||||
(typ0, typ1) = (tv0.singleton_type(), tv1.singleton_type())
|
||||
if (op == fdemote):
|
||||
assert typ0.wider_or_equal(typ1)
|
||||
else:
|
||||
assert typ1.wider_or_equal(typ0)
|
||||
|
||||
|
||||
class TestXForm(TypeCheckingBaseTest):
|
||||
def test_iadd_cout(self):
|
||||
# type: () -> None
|
||||
x = XForm(Rtl((self.v0, self.v1) << iadd_cout(self.v2, self.v3),),
|
||||
Rtl(
|
||||
self.v0 << iadd(self.v2, self.v3),
|
||||
self.v1 << icmp(intcc.ult, self.v0, self.v2)
|
||||
))
|
||||
itype = TypeVar("t", "", ints=True, simd=(1, 1))
|
||||
|
||||
check_typing(x.ti, ({
|
||||
self.v0: itype,
|
||||
self.v2: itype,
|
||||
self.v3: itype,
|
||||
self.v1: itype.as_bool(),
|
||||
}, []), x.symtab)
|
||||
|
||||
def test_iadd_cin(self):
|
||||
# type: () -> None
|
||||
x = XForm(Rtl(self.v0 << iadd_cin(self.v1, self.v2, self.v3)),
|
||||
Rtl(
|
||||
self.v4 << iadd(self.v1, self.v2),
|
||||
self.v5 << bint(self.v3),
|
||||
self.v0 << iadd(self.v4, self.v5)
|
||||
))
|
||||
itype = TypeVar("t", "", ints=True, simd=(1, 1))
|
||||
|
||||
check_typing(x.ti, ({
|
||||
self.v0: itype,
|
||||
self.v1: itype,
|
||||
self.v2: itype,
|
||||
self.v3: self.b1,
|
||||
self.v4: itype,
|
||||
self.v5: itype,
|
||||
}, []), x.symtab)
|
||||
|
||||
def test_enumeration_with_constraints(self):
|
||||
# type: () -> None
|
||||
xform = XForm(
|
||||
Rtl(
|
||||
self.v0 << iconst(self.imm0),
|
||||
self.v1 << icmp(intcc.eq, self.v2, self.v0),
|
||||
self.v5 << vselect(self.v1, self.v3, self.v4)
|
||||
),
|
||||
Rtl(
|
||||
self.v0 << iconst(self.imm0),
|
||||
self.v1 << icmp(intcc.eq, self.v2, self.v0),
|
||||
self.v5 << vselect(self.v1, self.v3, self.v4)
|
||||
))
|
||||
|
||||
# Check all var assigns are correct
|
||||
assert len(xform.ti.constraints) > 0
|
||||
concrete_var_assigns = list(xform.ti.concrete_typings())
|
||||
|
||||
v0 = xform.symtab[str(self.v0)]
|
||||
v1 = xform.symtab[str(self.v1)]
|
||||
v2 = xform.symtab[str(self.v2)]
|
||||
v3 = xform.symtab[str(self.v3)]
|
||||
v4 = xform.symtab[str(self.v4)]
|
||||
v5 = xform.symtab[str(self.v5)]
|
||||
|
||||
for var_m in concrete_var_assigns:
|
||||
assert var_m[v0] == var_m[v2] and \
|
||||
var_m[v3] == var_m[v4] and\
|
||||
var_m[v5] == var_m[v3] and\
|
||||
var_m[v1] == var_m[v2].as_bool() and\
|
||||
var_m[v1].get_typeset() == var_m[v3].as_bool().get_typeset()
|
||||
check_concrete_typing_xform(var_m, xform)
|
||||
|
||||
# The number of possible typings here is:
|
||||
# 8 cases for v0 = i8xN times 2 options for v3 - i8, b8 = 16
|
||||
# 8 cases for v0 = i16xN times 2 options for v3 - i16, b16 = 16
|
||||
# 8 cases for v0 = i32xN times 3 options for v3 - i32, b32, f32 = 24
|
||||
# 8 cases for v0 = i64xN times 3 options for v3 - i64, b64, f64 = 24
|
||||
#
|
||||
# (Note we have 8 cases for lanes since vselect prevents scalars)
|
||||
# Total: 2*16 + 2*24 = 80
|
||||
assert len(concrete_var_assigns) == 80
|
||||
|
||||
def test_base_legalizations_enumeration(self):
|
||||
# type: () -> None
|
||||
for xform in narrow.xforms + expand.xforms:
|
||||
# Any legalization patterns we defined should have at least 1
|
||||
# concrete typing
|
||||
concrete_typings_list = list(xform.ti.concrete_typings())
|
||||
assert len(concrete_typings_list) > 0
|
||||
|
||||
# If there are no free_typevars, this is a non-polymorphic pattern.
|
||||
# There should be only one possible concrete typing.
|
||||
if (len(xform.ti.free_typevars()) == 0):
|
||||
assert len(concrete_typings_list) == 1
|
||||
continue
|
||||
|
||||
# For any patterns where the type env includes constraints, at
|
||||
# least one of the "theoretically possible" concrete typings must
|
||||
# be prevented by the constraints. (i.e. we are not emitting
|
||||
# unnecessary constraints).
|
||||
# We check that by asserting that the number of concrete typings is
|
||||
# less than the number of all possible free typevar assignments
|
||||
if (len(xform.ti.constraints) > 0):
|
||||
theoretical_num_typings =\
|
||||
reduce(lambda x, y: x*y,
|
||||
[tv.get_typeset().size()
|
||||
for tv in xform.ti.free_typevars()], 1)
|
||||
assert len(concrete_typings_list) < theoretical_num_typings
|
||||
|
||||
# Check the validity of each individual concrete typing against the
|
||||
# xform
|
||||
for concrete_typing in concrete_typings_list:
|
||||
check_concrete_typing_xform(concrete_typing, xform)
|
||||
|
||||
def test_bound_inst_inference(self):
|
||||
# First example from issue #26
|
||||
x = XForm(
|
||||
Rtl(
|
||||
self.v0 << iadd(self.v1, self.v2),
|
||||
),
|
||||
Rtl(
|
||||
self.v3 << uextend.i32(self.v1),
|
||||
self.v4 << uextend.i32(self.v2),
|
||||
self.v5 << iadd(self.v3, self.v4),
|
||||
self.v0 << ireduce(self.v5)
|
||||
))
|
||||
itype = TypeVar("t", "", ints=True, simd=True)
|
||||
i32t = TypeVar.singleton(i32)
|
||||
|
||||
check_typing(x.ti, ({
|
||||
self.v0: itype,
|
||||
self.v1: itype,
|
||||
self.v2: itype,
|
||||
self.v3: i32t,
|
||||
self.v4: i32t,
|
||||
self.v5: i32t,
|
||||
}, [WiderOrEq(i32t, itype)]), x.symtab)
|
||||
|
||||
def test_bound_inst_inference1(self):
|
||||
# Second example taken from issue #26
|
||||
x = XForm(
|
||||
Rtl(
|
||||
self.v0 << iadd(self.v1, self.v2),
|
||||
),
|
||||
Rtl(
|
||||
self.v3 << uextend(self.v1),
|
||||
self.v4 << uextend(self.v2),
|
||||
self.v5 << iadd.i32(self.v3, self.v4),
|
||||
self.v0 << ireduce(self.v5)
|
||||
))
|
||||
itype = TypeVar("t", "", ints=True, simd=True)
|
||||
i32t = TypeVar.singleton(i32)
|
||||
|
||||
check_typing(x.ti, ({
|
||||
self.v0: itype,
|
||||
self.v1: itype,
|
||||
self.v2: itype,
|
||||
self.v3: i32t,
|
||||
self.v4: i32t,
|
||||
self.v5: i32t,
|
||||
}, [WiderOrEq(i32t, itype)]), x.symtab)
|
||||
|
||||
def test_fully_bound_inst_inference(self):
|
||||
# Second example taken from issue #26 with complete bounds
|
||||
x = XForm(
|
||||
Rtl(
|
||||
self.v0 << iadd(self.v1, self.v2),
|
||||
),
|
||||
Rtl(
|
||||
self.v3 << uextend.i32.i8(self.v1),
|
||||
self.v4 << uextend.i32.i8(self.v2),
|
||||
self.v5 << iadd(self.v3, self.v4),
|
||||
self.v0 << ireduce(self.v5)
|
||||
))
|
||||
i8t = TypeVar.singleton(i8)
|
||||
i32t = TypeVar.singleton(i32)
|
||||
|
||||
# Note no constraints here since they are all trivial
|
||||
check_typing(x.ti, ({
|
||||
self.v0: i8t,
|
||||
self.v1: i8t,
|
||||
self.v2: i8t,
|
||||
self.v3: i32t,
|
||||
self.v4: i32t,
|
||||
self.v5: i32t,
|
||||
}, []), x.symtab)
|
||||
|
||||
def test_fully_bound_inst_inference_bad(self):
|
||||
# Can't force a mistyped XForm using bound instructions
|
||||
with self.assertRaises(AssertionError):
|
||||
XForm(
|
||||
Rtl(
|
||||
self.v0 << iadd(self.v1, self.v2),
|
||||
),
|
||||
Rtl(
|
||||
self.v3 << uextend.i32.i8(self.v1),
|
||||
self.v4 << uextend.i32.i16(self.v2),
|
||||
self.v5 << iadd(self.v3, self.v4),
|
||||
self.v0 << ireduce(self.v5)
|
||||
))
|
||||
266
cranelift/codegen/meta-python/cdsl/test_typevar.py
Normal file
266
cranelift/codegen/meta-python/cdsl/test_typevar.py
Normal file
@@ -0,0 +1,266 @@
|
||||
from __future__ import absolute_import
|
||||
from unittest import TestCase
|
||||
from doctest import DocTestSuite
|
||||
from . import typevar
|
||||
from .typevar import TypeSet, TypeVar
|
||||
from base.types import i32, i16, b1, f64
|
||||
from itertools import product
|
||||
from functools import reduce
|
||||
|
||||
|
||||
def load_tests(loader, tests, ignore):
|
||||
tests.addTests(DocTestSuite(typevar))
|
||||
return tests
|
||||
|
||||
|
||||
class TestTypeSet(TestCase):
|
||||
def test_invalid(self):
|
||||
with self.assertRaises(AssertionError):
|
||||
TypeSet(lanes=(2, 1))
|
||||
with self.assertRaises(AssertionError):
|
||||
TypeSet(ints=(32, 16))
|
||||
with self.assertRaises(AssertionError):
|
||||
TypeSet(floats=(32, 16))
|
||||
with self.assertRaises(AssertionError):
|
||||
TypeSet(bools=(32, 16))
|
||||
with self.assertRaises(AssertionError):
|
||||
TypeSet(ints=(32, 33))
|
||||
|
||||
def test_hash(self):
|
||||
a = TypeSet(lanes=True, ints=True, floats=True)
|
||||
b = TypeSet(lanes=True, ints=True, floats=True)
|
||||
c = TypeSet(lanes=True, ints=(8, 16), floats=True)
|
||||
self.assertEqual(a, b)
|
||||
self.assertNotEqual(a, c)
|
||||
s = set()
|
||||
s.add(a)
|
||||
self.assertTrue(a in s)
|
||||
self.assertTrue(b in s)
|
||||
self.assertFalse(c in s)
|
||||
|
||||
def test_hash_modified(self):
|
||||
a = TypeSet(lanes=True, ints=True, floats=True)
|
||||
s = set()
|
||||
s.add(a)
|
||||
a.ints.remove(64)
|
||||
# Can't rehash after modification.
|
||||
with self.assertRaises(AssertionError):
|
||||
a in s
|
||||
|
||||
def test_forward_images(self):
|
||||
a = TypeSet(lanes=(2, 8), ints=(8, 8), floats=(32, 32))
|
||||
b = TypeSet(lanes=(1, 8), ints=(8, 8), floats=(32, 32))
|
||||
self.assertEqual(a.lane_of(), TypeSet(ints=(8, 8), floats=(32, 32)))
|
||||
|
||||
c = TypeSet(lanes=(2, 8))
|
||||
c.bools = set([8, 32])
|
||||
|
||||
# Test case with disjoint intervals
|
||||
self.assertEqual(a.as_bool(), c)
|
||||
|
||||
# For as_bool check b1 is present when 1 \in lanes
|
||||
d = TypeSet(lanes=(1, 8))
|
||||
d.bools = set([1, 8, 32])
|
||||
self.assertEqual(b.as_bool(), d)
|
||||
|
||||
self.assertEqual(TypeSet(lanes=(1, 32)).half_vector(),
|
||||
TypeSet(lanes=(1, 16)))
|
||||
|
||||
self.assertEqual(TypeSet(lanes=(1, 32)).double_vector(),
|
||||
TypeSet(lanes=(2, 64)))
|
||||
|
||||
self.assertEqual(TypeSet(lanes=(128, 256)).double_vector(),
|
||||
TypeSet(lanes=(256, 256)))
|
||||
|
||||
self.assertEqual(TypeSet(ints=(8, 32)).half_width(),
|
||||
TypeSet(ints=(8, 16)))
|
||||
|
||||
self.assertEqual(TypeSet(ints=(8, 32)).double_width(),
|
||||
TypeSet(ints=(16, 64)))
|
||||
|
||||
self.assertEqual(TypeSet(ints=(32, 64)).double_width(),
|
||||
TypeSet(ints=(64, 64)))
|
||||
|
||||
# Should produce an empty ts
|
||||
self.assertEqual(TypeSet(floats=(32, 32)).half_width(),
|
||||
TypeSet())
|
||||
|
||||
self.assertEqual(TypeSet(floats=(32, 64)).half_width(),
|
||||
TypeSet(floats=(32, 32)))
|
||||
|
||||
self.assertEqual(TypeSet(floats=(32, 32)).double_width(),
|
||||
TypeSet(floats=(64, 64)))
|
||||
|
||||
self.assertEqual(TypeSet(floats=(32, 64)).double_width(),
|
||||
TypeSet(floats=(64, 64)))
|
||||
|
||||
# Bools have trickier behavior around b1 (since b2, b4 don't exist)
|
||||
self.assertEqual(TypeSet(bools=(1, 8)).half_width(),
|
||||
TypeSet())
|
||||
|
||||
t = TypeSet()
|
||||
t.bools = set([8, 16])
|
||||
self.assertEqual(TypeSet(bools=(1, 32)).half_width(), t)
|
||||
|
||||
# double_width() of bools={1, 8, 16} must not include 2 or 8
|
||||
t.bools = set([16, 32])
|
||||
self.assertEqual(TypeSet(bools=(1, 16)).double_width(), t)
|
||||
|
||||
self.assertEqual(TypeSet(bools=(32, 64)).double_width(),
|
||||
TypeSet(bools=(64, 64)))
|
||||
|
||||
def test_get_singleton(self):
|
||||
# Raise error when calling get_singleton() on non-singleton TS
|
||||
t = TypeSet(lanes=(1, 1), ints=(8, 8), floats=(32, 32))
|
||||
with self.assertRaises(AssertionError):
|
||||
t.get_singleton()
|
||||
t = TypeSet(lanes=(1, 2), floats=(32, 32))
|
||||
|
||||
with self.assertRaises(AssertionError):
|
||||
t.get_singleton()
|
||||
|
||||
self.assertEqual(TypeSet(ints=(16, 16)).get_singleton(), i16)
|
||||
self.assertEqual(TypeSet(floats=(64, 64)).get_singleton(), f64)
|
||||
self.assertEqual(TypeSet(bools=(1, 1)).get_singleton(), b1)
|
||||
self.assertEqual(TypeSet(lanes=(4, 4), ints=(32, 32)).get_singleton(),
|
||||
i32.by(4))
|
||||
|
||||
def test_preimage(self):
|
||||
t = TypeSet(lanes=(1, 1), ints=(8, 8), floats=(32, 32))
|
||||
|
||||
# LANEOF
|
||||
self.assertEqual(TypeSet(lanes=True, ints=(8, 8), floats=(32, 32)),
|
||||
t.preimage(TypeVar.LANEOF))
|
||||
# Inverse of empty set is still empty across LANEOF
|
||||
self.assertEqual(TypeSet(),
|
||||
TypeSet().preimage(TypeVar.LANEOF))
|
||||
|
||||
# ASBOOL
|
||||
t = TypeSet(lanes=(1, 4), bools=(1, 64))
|
||||
self.assertEqual(t.preimage(TypeVar.ASBOOL),
|
||||
TypeSet(lanes=(1, 4), ints=True, bools=True,
|
||||
floats=True))
|
||||
|
||||
# Half/Double Vector
|
||||
t = TypeSet(lanes=(1, 1), ints=(8, 8))
|
||||
t1 = TypeSet(lanes=(256, 256), ints=(8, 8))
|
||||
self.assertEqual(t.preimage(TypeVar.DOUBLEVECTOR).size(), 0)
|
||||
self.assertEqual(t1.preimage(TypeVar.HALFVECTOR).size(), 0)
|
||||
|
||||
t = TypeSet(lanes=(1, 16), ints=(8, 16), floats=(32, 32))
|
||||
t1 = TypeSet(lanes=(64, 256), bools=(1, 32))
|
||||
|
||||
self.assertEqual(t.preimage(TypeVar.DOUBLEVECTOR),
|
||||
TypeSet(lanes=(1, 8), ints=(8, 16), floats=(32, 32)))
|
||||
self.assertEqual(t1.preimage(TypeVar.HALFVECTOR),
|
||||
TypeSet(lanes=(128, 256), bools=(1, 32)))
|
||||
|
||||
# Half/Double Width
|
||||
t = TypeSet(ints=(8, 8), floats=(32, 32), bools=(1, 8))
|
||||
t1 = TypeSet(ints=(64, 64), floats=(64, 64), bools=(64, 64))
|
||||
self.assertEqual(t.preimage(TypeVar.DOUBLEWIDTH).size(), 0)
|
||||
self.assertEqual(t1.preimage(TypeVar.HALFWIDTH).size(), 0)
|
||||
|
||||
t = TypeSet(lanes=(1, 16), ints=(8, 16), floats=(32, 64))
|
||||
t1 = TypeSet(lanes=(64, 256), bools=(1, 64))
|
||||
|
||||
self.assertEqual(t.preimage(TypeVar.DOUBLEWIDTH),
|
||||
TypeSet(lanes=(1, 16), ints=(8, 8), floats=(32, 32)))
|
||||
self.assertEqual(t1.preimage(TypeVar.HALFWIDTH),
|
||||
TypeSet(lanes=(64, 256), bools=(16, 64)))
|
||||
|
||||
|
||||
def has_non_bijective_derived_f(iterable):
|
||||
return any(not TypeVar.is_bijection(x) for x in iterable)
|
||||
|
||||
|
||||
class TestTypeVar(TestCase):
|
||||
def test_functions(self):
|
||||
x = TypeVar('x', 'all ints', ints=True)
|
||||
with self.assertRaises(AssertionError):
|
||||
x.double_width()
|
||||
with self.assertRaises(AssertionError):
|
||||
x.half_width()
|
||||
|
||||
x2 = TypeVar('x2', 'i16 and up', ints=(16, 64))
|
||||
with self.assertRaises(AssertionError):
|
||||
x2.double_width()
|
||||
self.assertEqual(str(x2.half_width()), '`half_width(x2)`')
|
||||
self.assertEqual(x2.half_width().rust_expr(), 'x2.half_width()')
|
||||
self.assertEqual(
|
||||
x2.half_width().double_width().rust_expr(),
|
||||
'x2.half_width().double_width()')
|
||||
|
||||
x3 = TypeVar('x3', 'up to i32', ints=(8, 32))
|
||||
self.assertEqual(str(x3.double_width()), '`double_width(x3)`')
|
||||
with self.assertRaises(AssertionError):
|
||||
x3.half_width()
|
||||
|
||||
def test_singleton(self):
|
||||
x = TypeVar.singleton(i32)
|
||||
self.assertEqual(str(x), '`i32`')
|
||||
self.assertEqual(min(x.type_set.ints), 32)
|
||||
self.assertEqual(max(x.type_set.ints), 32)
|
||||
self.assertEqual(min(x.type_set.lanes), 1)
|
||||
self.assertEqual(max(x.type_set.lanes), 1)
|
||||
self.assertEqual(len(x.type_set.floats), 0)
|
||||
self.assertEqual(len(x.type_set.bools), 0)
|
||||
|
||||
x = TypeVar.singleton(i32.by(4))
|
||||
self.assertEqual(str(x), '`i32x4`')
|
||||
self.assertEqual(min(x.type_set.ints), 32)
|
||||
self.assertEqual(max(x.type_set.ints), 32)
|
||||
self.assertEqual(min(x.type_set.lanes), 4)
|
||||
self.assertEqual(max(x.type_set.lanes), 4)
|
||||
self.assertEqual(len(x.type_set.floats), 0)
|
||||
self.assertEqual(len(x.type_set.bools), 0)
|
||||
|
||||
def test_stress_constrain_types(self):
|
||||
# Get all 43 possible derived vars of length up to 2
|
||||
funcs = [TypeVar.LANEOF,
|
||||
TypeVar.ASBOOL, TypeVar.HALFVECTOR, TypeVar.DOUBLEVECTOR,
|
||||
TypeVar.HALFWIDTH, TypeVar.DOUBLEWIDTH]
|
||||
v = [()] + [(x,) for x in funcs] + list(product(*[funcs, funcs]))
|
||||
|
||||
# For each pair of derived variables
|
||||
for (i1, i2) in product(v, v):
|
||||
# Compute the derived sets for each starting with a full typeset
|
||||
full_ts = TypeSet(lanes=True, floats=True, ints=True, bools=True)
|
||||
ts1 = reduce(lambda ts, func: ts.image(func), i1, full_ts)
|
||||
ts2 = reduce(lambda ts, func: ts.image(func), i2, full_ts)
|
||||
|
||||
# Compute intersection
|
||||
intersect = ts1.copy()
|
||||
intersect &= ts2
|
||||
|
||||
# Propagate intersections backward
|
||||
ts1_src = reduce(lambda ts, func: ts.preimage(func),
|
||||
reversed(i1),
|
||||
intersect)
|
||||
ts2_src = reduce(lambda ts, func: ts.preimage(func),
|
||||
reversed(i2),
|
||||
intersect)
|
||||
|
||||
# If the intersection or its propagated forms are empty, then these
|
||||
# two variables can never overlap. For example x.double_vector and
|
||||
# x.lane_of.
|
||||
if (intersect.size() == 0 or ts1_src.size() == 0 or
|
||||
ts2_src.size() == 0):
|
||||
continue
|
||||
|
||||
# Should be safe to create derived tvs from ts1_src and ts2_src
|
||||
tv1 = reduce(lambda tv, func: TypeVar.derived(tv, func),
|
||||
i1,
|
||||
TypeVar.from_typeset(ts1_src))
|
||||
|
||||
tv2 = reduce(lambda tv, func: TypeVar.derived(tv, func),
|
||||
i2,
|
||||
TypeVar.from_typeset(ts2_src))
|
||||
|
||||
# In the absence of AS_BOOL image(preimage(f)) == f so the
|
||||
# typesets of tv1 and tv2 should be exactly intersection
|
||||
assert tv1.get_typeset() == intersect or\
|
||||
has_non_bijective_derived_f(i1)
|
||||
|
||||
assert tv2.get_typeset() == intersect or\
|
||||
has_non_bijective_derived_f(i2)
|
||||
131
cranelift/codegen/meta-python/cdsl/test_xform.py
Normal file
131
cranelift/codegen/meta-python/cdsl/test_xform.py
Normal file
@@ -0,0 +1,131 @@
|
||||
from __future__ import absolute_import
|
||||
from unittest import TestCase
|
||||
from doctest import DocTestSuite
|
||||
from base.instructions import iadd, iadd_imm, iconst, icmp
|
||||
from base.immediates import intcc
|
||||
from . import xform
|
||||
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')
|
||||
z = Var('z')
|
||||
u = Var('u')
|
||||
a = Var('a')
|
||||
b = Var('b')
|
||||
c = Var('c')
|
||||
|
||||
CC1 = Var('CC1')
|
||||
CC2 = Var('CC2')
|
||||
|
||||
|
||||
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)
|
||||
|
||||
def test_subst_imm(self):
|
||||
src = Rtl(a << iconst(x))
|
||||
dst = Rtl(c << iconst(y))
|
||||
assert src.substitution(dst, {}) == {a: c, x: y}
|
||||
|
||||
def test_subst_enum_var(self):
|
||||
src = Rtl(a << icmp(CC1, x, y))
|
||||
dst = Rtl(b << icmp(CC2, z, u))
|
||||
assert src.substitution(dst, {}) == {a: b, CC1: CC2, x: z, y: u}
|
||||
|
||||
def test_subst_enum_const(self):
|
||||
src = Rtl(a << icmp(intcc.eq, x, y))
|
||||
dst = Rtl(b << icmp(intcc.eq, z, u))
|
||||
assert src.substitution(dst, {}) == {a: b, x: z, y: u}
|
||||
|
||||
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, {}) == {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, {}) == {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
|
||||
894
cranelift/codegen/meta-python/cdsl/ti.py
Normal file
894
cranelift/codegen/meta-python/cdsl/ti.py
Normal file
@@ -0,0 +1,894 @@
|
||||
"""
|
||||
Type Inference
|
||||
"""
|
||||
from .typevar import TypeVar
|
||||
from .ast import Def, Var
|
||||
from copy import copy
|
||||
from itertools import product
|
||||
|
||||
try:
|
||||
from typing import Dict, TYPE_CHECKING, Union, Tuple, Optional, Set # noqa
|
||||
from typing import Iterable, List, Any, TypeVar as MTypeVar # noqa
|
||||
from typing import cast
|
||||
from .xform import Rtl, XForm # noqa
|
||||
from .ast import Expr # noqa
|
||||
from .typevar import TypeSet # noqa
|
||||
if TYPE_CHECKING:
|
||||
T = MTypeVar('T')
|
||||
TypeMap = Dict[TypeVar, TypeVar]
|
||||
VarTyping = Dict[Var, TypeVar]
|
||||
except ImportError:
|
||||
TYPE_CHECKING = False
|
||||
pass
|
||||
|
||||
|
||||
class TypeConstraint(object):
|
||||
"""
|
||||
Base class for all runtime-emittable type constraints.
|
||||
"""
|
||||
|
||||
def __init__(self, tv, tc):
|
||||
# type: (TypeVar, Union[TypeVar, TypeSet]) -> None
|
||||
"""
|
||||
Abstract "constructor" for linters
|
||||
"""
|
||||
assert False, "Abstract"
|
||||
|
||||
def translate(self, m):
|
||||
# type: (Union[TypeEnv, TypeMap]) -> TypeConstraint
|
||||
"""
|
||||
Translate any TypeVars in the constraint according to the map or
|
||||
TypeEnv m
|
||||
"""
|
||||
def translate_one(a):
|
||||
# type: (Any) -> Any
|
||||
if (isinstance(a, TypeVar)):
|
||||
return m[a] if isinstance(m, TypeEnv) else subst(a, m)
|
||||
return a
|
||||
|
||||
res = None # type: TypeConstraint
|
||||
res = self.__class__(*tuple(map(translate_one, self._args())))
|
||||
return res
|
||||
|
||||
def __eq__(self, other):
|
||||
# type: (object) -> bool
|
||||
if (not isinstance(other, self.__class__)):
|
||||
return False
|
||||
|
||||
assert isinstance(other, TypeConstraint) # help MyPy figure out other
|
||||
return self._args() == other._args()
|
||||
|
||||
def is_concrete(self):
|
||||
# type: () -> bool
|
||||
"""
|
||||
Return true iff all typevars in the constraint are singletons.
|
||||
"""
|
||||
return [] == list(filter(lambda x: x.singleton_type() is None,
|
||||
self.tvs()))
|
||||
|
||||
def __hash__(self):
|
||||
# type: () -> int
|
||||
return hash(self._args())
|
||||
|
||||
def _args(self):
|
||||
# type: () -> Tuple[Any,...]
|
||||
"""
|
||||
Return a tuple with the exact arguments passed to __init__ to create
|
||||
this object.
|
||||
"""
|
||||
assert False, "Abstract"
|
||||
|
||||
def tvs(self):
|
||||
# type: () -> Iterable[TypeVar]
|
||||
"""
|
||||
Return the typevars contained in this constraint.
|
||||
"""
|
||||
return list(filter(lambda x: isinstance(x, TypeVar), self._args()))
|
||||
|
||||
def is_trivial(self):
|
||||
# type: () -> bool
|
||||
"""
|
||||
Return true if this constrain is statically decidable.
|
||||
"""
|
||||
assert False, "Abstract"
|
||||
|
||||
def eval(self):
|
||||
# type: () -> bool
|
||||
"""
|
||||
Evaluate this constraint. Should only be called when the constraint has
|
||||
been translated to concrete types.
|
||||
"""
|
||||
assert False, "Abstract"
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return (self.__class__.__name__ + '(' +
|
||||
', '.join(map(str, self._args())) + ')')
|
||||
|
||||
|
||||
class TypesEqual(TypeConstraint):
|
||||
"""
|
||||
Constraint specifying that two derived type vars must have the same runtime
|
||||
type.
|
||||
"""
|
||||
def __init__(self, tv1, tv2):
|
||||
# type: (TypeVar, TypeVar) -> None
|
||||
(self.tv1, self.tv2) = sorted([tv1, tv2], key=repr)
|
||||
|
||||
def _args(self):
|
||||
# type: () -> Tuple[Any,...]
|
||||
""" See TypeConstraint._args() """
|
||||
return (self.tv1, self.tv2)
|
||||
|
||||
def is_trivial(self):
|
||||
# type: () -> bool
|
||||
""" See TypeConstraint.is_trivial() """
|
||||
return self.tv1 == self.tv2 or self.is_concrete()
|
||||
|
||||
def eval(self):
|
||||
# type: () -> bool
|
||||
""" See TypeConstraint.eval() """
|
||||
assert self.is_concrete()
|
||||
return self.tv1.singleton_type() == self.tv2.singleton_type()
|
||||
|
||||
|
||||
class InTypeset(TypeConstraint):
|
||||
"""
|
||||
Constraint specifying that a type var must belong to some typeset.
|
||||
"""
|
||||
def __init__(self, tv, ts):
|
||||
# type: (TypeVar, TypeSet) -> None
|
||||
assert not tv.is_derived and tv.name.startswith("typeof_")
|
||||
self.tv = tv
|
||||
self.ts = ts
|
||||
|
||||
def _args(self):
|
||||
# type: () -> Tuple[Any,...]
|
||||
""" See TypeConstraint._args() """
|
||||
return (self.tv, self.ts)
|
||||
|
||||
def is_trivial(self):
|
||||
# type: () -> bool
|
||||
""" See TypeConstraint.is_trivial() """
|
||||
tv_ts = self.tv.get_typeset().copy()
|
||||
|
||||
# Trivially True
|
||||
if (tv_ts.issubset(self.ts)):
|
||||
return True
|
||||
|
||||
# Trivially false
|
||||
tv_ts &= self.ts
|
||||
if (tv_ts.size() == 0):
|
||||
return True
|
||||
|
||||
return self.is_concrete()
|
||||
|
||||
def eval(self):
|
||||
# type: () -> bool
|
||||
""" See TypeConstraint.eval() """
|
||||
assert self.is_concrete()
|
||||
return self.tv.get_typeset().issubset(self.ts)
|
||||
|
||||
|
||||
class WiderOrEq(TypeConstraint):
|
||||
"""
|
||||
Constraint specifying that a type var tv1 must be wider than or equal to
|
||||
type var tv2 at runtime. This requires that:
|
||||
1) They have the same number of lanes
|
||||
2) In a lane tv1 has at least as many bits as tv2.
|
||||
"""
|
||||
def __init__(self, tv1, tv2):
|
||||
# type: (TypeVar, TypeVar) -> None
|
||||
self.tv1 = tv1
|
||||
self.tv2 = tv2
|
||||
|
||||
def _args(self):
|
||||
# type: () -> Tuple[Any,...]
|
||||
""" See TypeConstraint._args() """
|
||||
return (self.tv1, self.tv2)
|
||||
|
||||
def is_trivial(self):
|
||||
# type: () -> bool
|
||||
""" See TypeConstraint.is_trivial() """
|
||||
# Trivially true
|
||||
if (self.tv1 == self.tv2):
|
||||
return True
|
||||
|
||||
ts1 = self.tv1.get_typeset()
|
||||
ts2 = self.tv2.get_typeset()
|
||||
|
||||
def set_wider_or_equal(s1, s2):
|
||||
# type: (Set[int], Set[int]) -> bool
|
||||
return len(s1) > 0 and len(s2) > 0 and min(s1) >= max(s2)
|
||||
|
||||
# Trivially True
|
||||
if set_wider_or_equal(ts1.ints, ts2.ints) and\
|
||||
set_wider_or_equal(ts1.floats, ts2.floats) and\
|
||||
set_wider_or_equal(ts1.bools, ts2.bools):
|
||||
return True
|
||||
|
||||
def set_narrower(s1, s2):
|
||||
# type: (Set[int], Set[int]) -> bool
|
||||
return len(s1) > 0 and len(s2) > 0 and min(s1) < max(s2)
|
||||
|
||||
# Trivially False
|
||||
if set_narrower(ts1.ints, ts2.ints) and\
|
||||
set_narrower(ts1.floats, ts2.floats) and\
|
||||
set_narrower(ts1.bools, ts2.bools):
|
||||
return True
|
||||
|
||||
# Trivially False
|
||||
if len(ts1.lanes.intersection(ts2.lanes)) == 0:
|
||||
return True
|
||||
|
||||
return self.is_concrete()
|
||||
|
||||
def eval(self):
|
||||
# type: () -> bool
|
||||
""" See TypeConstraint.eval() """
|
||||
assert self.is_concrete()
|
||||
typ1 = self.tv1.singleton_type()
|
||||
typ2 = self.tv2.singleton_type()
|
||||
|
||||
return typ1.wider_or_equal(typ2)
|
||||
|
||||
|
||||
class SameWidth(TypeConstraint):
|
||||
"""
|
||||
Constraint specifying that two types have the same width. E.g. i32x2 has
|
||||
the same width as i64x1, i16x4, f32x2, f64, b1x64 etc.
|
||||
"""
|
||||
def __init__(self, tv1, tv2):
|
||||
# type: (TypeVar, TypeVar) -> None
|
||||
self.tv1 = tv1
|
||||
self.tv2 = tv2
|
||||
|
||||
def _args(self):
|
||||
# type: () -> Tuple[Any,...]
|
||||
""" See TypeConstraint._args() """
|
||||
return (self.tv1, self.tv2)
|
||||
|
||||
def is_trivial(self):
|
||||
# type: () -> bool
|
||||
""" See TypeConstraint.is_trivial() """
|
||||
# Trivially true
|
||||
if (self.tv1 == self.tv2):
|
||||
return True
|
||||
|
||||
ts1 = self.tv1.get_typeset()
|
||||
ts2 = self.tv2.get_typeset()
|
||||
|
||||
# Trivially False
|
||||
if len(ts1.widths().intersection(ts2.widths())) == 0:
|
||||
return True
|
||||
|
||||
return self.is_concrete()
|
||||
|
||||
def eval(self):
|
||||
# type: () -> bool
|
||||
""" See TypeConstraint.eval() """
|
||||
assert self.is_concrete()
|
||||
typ1 = self.tv1.singleton_type()
|
||||
typ2 = self.tv2.singleton_type()
|
||||
|
||||
return (typ1.width() == typ2.width())
|
||||
|
||||
|
||||
class TypeEnv(object):
|
||||
"""
|
||||
Class encapsulating the necessary book keeping for type inference.
|
||||
:attribute type_map: dict holding the equivalence relations between tvs
|
||||
:attribute constraints: a list of accumulated constraints - tuples
|
||||
(tv1, tv2)) where tv1 and tv2 are equal
|
||||
:attribute ranks: dictionary recording the (optional) ranks for tvs.
|
||||
'rank' is a partial ordering on TVs based on their
|
||||
origin. See comments in rank() and register().
|
||||
:attribute vars: a set containing all known Vars
|
||||
:attribute idx: counter used to get fresh ids
|
||||
"""
|
||||
|
||||
RANK_SINGLETON = 5
|
||||
RANK_INPUT = 4
|
||||
RANK_INTERMEDIATE = 3
|
||||
RANK_OUTPUT = 2
|
||||
RANK_TEMP = 1
|
||||
RANK_INTERNAL = 0
|
||||
|
||||
def __init__(self, arg=None):
|
||||
# type: (Optional[Tuple[TypeMap, List[TypeConstraint]]]) -> None
|
||||
self.ranks = {} # type: Dict[TypeVar, int]
|
||||
self.vars = set() # type: Set[Var]
|
||||
|
||||
if arg is None:
|
||||
self.type_map = {} # type: TypeMap
|
||||
self.constraints = [] # type: List[TypeConstraint]
|
||||
else:
|
||||
self.type_map, self.constraints = arg
|
||||
|
||||
self.idx = 0
|
||||
|
||||
def __getitem__(self, arg):
|
||||
# type: (Union[TypeVar, Var]) -> TypeVar
|
||||
"""
|
||||
Lookup the canonical representative for a Var/TypeVar.
|
||||
"""
|
||||
if (isinstance(arg, Var)):
|
||||
assert arg in self.vars
|
||||
tv = arg.get_typevar()
|
||||
else:
|
||||
assert (isinstance(arg, TypeVar))
|
||||
tv = arg
|
||||
|
||||
while tv in self.type_map:
|
||||
tv = self.type_map[tv]
|
||||
|
||||
if tv.is_derived:
|
||||
tv = TypeVar.derived(self[tv.base], tv.derived_func)
|
||||
return tv
|
||||
|
||||
def equivalent(self, tv1, tv2):
|
||||
# type: (TypeVar, TypeVar) -> None
|
||||
"""
|
||||
Record a that the free tv1 is part of the same equivalence class as
|
||||
tv2. The canonical representative of the merged class is tv2's
|
||||
canonical representative.
|
||||
"""
|
||||
assert not tv1.is_derived
|
||||
assert self[tv1] == tv1
|
||||
|
||||
# Make sure we don't create cycles
|
||||
if tv2.is_derived:
|
||||
assert self[tv2.base] != tv1
|
||||
|
||||
self.type_map[tv1] = tv2
|
||||
|
||||
def add_constraint(self, constr):
|
||||
# type: (TypeConstraint) -> None
|
||||
"""
|
||||
Add a new constraint
|
||||
"""
|
||||
if (constr in self.constraints):
|
||||
return
|
||||
|
||||
# InTypeset constraints can be expressed by constraining the typeset of
|
||||
# a variable. No need to add them to self.constraints
|
||||
if (isinstance(constr, InTypeset)):
|
||||
self[constr.tv].constrain_types_by_ts(constr.ts)
|
||||
return
|
||||
|
||||
self.constraints.append(constr)
|
||||
|
||||
def get_uid(self):
|
||||
# type: () -> str
|
||||
r = str(self.idx)
|
||||
self.idx += 1
|
||||
return r
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return self.dot()
|
||||
|
||||
def rank(self, tv):
|
||||
# type: (TypeVar) -> int
|
||||
"""
|
||||
Get the rank of tv in the partial order. TVs directly associated with a
|
||||
Var get their rank from the Var (see register()). Internally generated
|
||||
non-derived TVs implicitly get the lowest rank (0). Derived variables
|
||||
get their rank from their free typevar. Singletons have the highest
|
||||
rank. TVs associated with vars in a source pattern have a higher rank
|
||||
than TVs associated with temporary vars.
|
||||
"""
|
||||
default_rank = TypeEnv.RANK_INTERNAL if tv.singleton_type() is None \
|
||||
else TypeEnv.RANK_SINGLETON
|
||||
|
||||
if tv.is_derived:
|
||||
tv = tv.free_typevar()
|
||||
|
||||
return self.ranks.get(tv, default_rank)
|
||||
|
||||
def register(self, v):
|
||||
# type: (Var) -> None
|
||||
"""
|
||||
Register a new Var v. This computes a rank for the associated TypeVar
|
||||
for v, which is used to impose a partial order on type variables.
|
||||
"""
|
||||
self.vars.add(v)
|
||||
|
||||
if v.is_input():
|
||||
r = TypeEnv.RANK_INPUT
|
||||
elif v.is_intermediate():
|
||||
r = TypeEnv.RANK_INTERMEDIATE
|
||||
elif v.is_output():
|
||||
r = TypeEnv.RANK_OUTPUT
|
||||
else:
|
||||
assert(v.is_temp())
|
||||
r = TypeEnv.RANK_TEMP
|
||||
|
||||
self.ranks[v.get_typevar()] = r
|
||||
|
||||
def free_typevars(self):
|
||||
# type: () -> List[TypeVar]
|
||||
"""
|
||||
Get the free typevars in the current type env.
|
||||
"""
|
||||
tvs = set([self[tv].free_typevar() for tv in self.type_map.keys()])
|
||||
tvs = tvs.union(set([self[v].free_typevar() for v in self.vars]))
|
||||
# Filter out None here due to singleton type vars
|
||||
return sorted(filter(lambda x: x is not None, tvs),
|
||||
key=lambda x: x.name)
|
||||
|
||||
def normalize(self):
|
||||
# type: () -> None
|
||||
"""
|
||||
Normalize by:
|
||||
- collapsing any roots that don't correspond to a concrete TV AND
|
||||
have a single TV derived from them or equivalent to them
|
||||
|
||||
E.g. if we have a root of the tree that looks like:
|
||||
|
||||
typeof_a typeof_b
|
||||
\\ /
|
||||
typeof_x
|
||||
|
|
||||
half_width(1)
|
||||
|
|
||||
1
|
||||
|
||||
we want to collapse the linear path between 1 and typeof_x. The
|
||||
resulting graph is:
|
||||
|
||||
typeof_a typeof_b
|
||||
\\ /
|
||||
typeof_x
|
||||
"""
|
||||
source_tvs = set([v.get_typevar() for v in self.vars])
|
||||
children = {} # type: Dict[TypeVar, Set[TypeVar]]
|
||||
for v in self.type_map.values():
|
||||
if not v.is_derived:
|
||||
continue
|
||||
|
||||
t = v.free_typevar()
|
||||
s = children.get(t, set())
|
||||
s.add(v)
|
||||
children[t] = s
|
||||
|
||||
for (a, b) in self.type_map.items():
|
||||
s = children.get(b, set())
|
||||
s.add(a)
|
||||
children[b] = s
|
||||
|
||||
for r in self.free_typevars():
|
||||
while (r not in source_tvs and r in children and
|
||||
len(children[r]) == 1):
|
||||
child = list(children[r])[0]
|
||||
if child in self.type_map:
|
||||
assert self.type_map[child] == r
|
||||
del self.type_map[child]
|
||||
|
||||
r = child
|
||||
|
||||
def extract(self):
|
||||
# type: () -> TypeEnv
|
||||
"""
|
||||
Extract a clean type environment from self, that only mentions
|
||||
TVs associated with real variables
|
||||
"""
|
||||
vars_tvs = set([v.get_typevar() for v in self.vars])
|
||||
new_type_map = {tv: self[tv] for tv in vars_tvs if tv != self[tv]}
|
||||
|
||||
new_constraints = [] # type: List[TypeConstraint]
|
||||
for constr in self.constraints:
|
||||
constr = constr.translate(self)
|
||||
|
||||
if constr.is_trivial() or constr in new_constraints:
|
||||
continue
|
||||
|
||||
# Sanity: translated constraints should refer to only real vars
|
||||
for arg in constr._args():
|
||||
if (not isinstance(arg, TypeVar)):
|
||||
continue
|
||||
|
||||
arg_free_tv = arg.free_typevar()
|
||||
assert arg_free_tv is None or arg_free_tv in vars_tvs
|
||||
|
||||
new_constraints.append(constr)
|
||||
|
||||
# Sanity: translated typemap should refer to only real vars
|
||||
for (k, v) in new_type_map.items():
|
||||
assert k in vars_tvs
|
||||
assert v.free_typevar() is None or v.free_typevar() in vars_tvs
|
||||
|
||||
t = TypeEnv()
|
||||
t.type_map = new_type_map
|
||||
t.constraints = new_constraints
|
||||
# ranks and vars contain only TVs associated with real vars
|
||||
t.ranks = copy(self.ranks)
|
||||
t.vars = copy(self.vars)
|
||||
return t
|
||||
|
||||
def concrete_typings(self):
|
||||
# type: () -> Iterable[VarTyping]
|
||||
"""
|
||||
Return an iterable over all possible concrete typings permitted by this
|
||||
TypeEnv.
|
||||
"""
|
||||
free_tvs = self.free_typevars()
|
||||
free_tv_iters = [tv.get_typeset().concrete_types() for tv in free_tvs]
|
||||
for concrete_types in product(*free_tv_iters):
|
||||
# Build type substitutions for all free vars
|
||||
m = {tv: TypeVar.singleton(typ)
|
||||
for (tv, typ) in zip(free_tvs, concrete_types)}
|
||||
|
||||
concrete_var_map = {v: subst(self[v.get_typevar()], m)
|
||||
for v in self.vars}
|
||||
|
||||
# Check if constraints are satisfied for this typing
|
||||
failed = None
|
||||
for constr in self.constraints:
|
||||
concrete_constr = constr.translate(m)
|
||||
if not concrete_constr.eval():
|
||||
failed = concrete_constr
|
||||
break
|
||||
|
||||
if (failed is not None):
|
||||
continue
|
||||
|
||||
yield concrete_var_map
|
||||
|
||||
def permits(self, concrete_typing):
|
||||
# type: (VarTyping) -> bool
|
||||
"""
|
||||
Return true iff this TypeEnv permits the (possibly partial) concrete
|
||||
variable type mapping concrete_typing.
|
||||
"""
|
||||
# Each variable has a concrete type, that is a subset of its inferred
|
||||
# typeset.
|
||||
for (v, typ) in concrete_typing.items():
|
||||
assert typ.singleton_type() is not None
|
||||
if not typ.get_typeset().issubset(self[v].get_typeset()):
|
||||
return False
|
||||
|
||||
m = {self[v]: typ for (v, typ) in concrete_typing.items()}
|
||||
|
||||
# Constraints involving vars in concrete_typing are satisfied
|
||||
for constr in self.constraints:
|
||||
try:
|
||||
# If the constraint includes only vars in concrete_typing, we
|
||||
# can translate it using m. Otherwise we encounter a KeyError
|
||||
# and ignore it
|
||||
constr = constr.translate(m)
|
||||
if not constr.eval():
|
||||
return False
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
return True
|
||||
|
||||
def dot(self):
|
||||
# type: () -> str
|
||||
"""
|
||||
Return a representation of self as a graph in dot format.
|
||||
Nodes correspond to TypeVariables.
|
||||
Dotted edges correspond to equivalences between TVS
|
||||
Solid edges correspond to derivation relations between TVs.
|
||||
Dashed edges correspond to equivalence constraints.
|
||||
"""
|
||||
def label(s):
|
||||
# type: (TypeVar) -> str
|
||||
return "\"" + str(s) + "\""
|
||||
|
||||
# Add all registered TVs (as some of them may be singleton nodes not
|
||||
# appearing in the graph
|
||||
nodes = set() # type: Set[TypeVar]
|
||||
edges = set() # type: Set[Tuple[TypeVar, TypeVar, str, str, Optional[str]]] # noqa
|
||||
|
||||
def add_nodes(*args):
|
||||
# type: (*TypeVar) -> None
|
||||
for tv in args:
|
||||
nodes.add(tv)
|
||||
while (tv.is_derived):
|
||||
nodes.add(tv.base)
|
||||
edges.add((tv, tv.base, "solid", "forward",
|
||||
tv.derived_func))
|
||||
tv = tv.base
|
||||
|
||||
for v in self.vars:
|
||||
add_nodes(v.get_typevar())
|
||||
|
||||
for (tv1, tv2) in self.type_map.items():
|
||||
# Add all intermediate TVs appearing in edges
|
||||
add_nodes(tv1, tv2)
|
||||
edges.add((tv1, tv2, "dotted", "forward", None))
|
||||
|
||||
for constr in self.constraints:
|
||||
if isinstance(constr, TypesEqual):
|
||||
add_nodes(constr.tv1, constr.tv2)
|
||||
edges.add((constr.tv1, constr.tv2, "dashed", "none", "equal"))
|
||||
elif isinstance(constr, WiderOrEq):
|
||||
add_nodes(constr.tv1, constr.tv2)
|
||||
edges.add((constr.tv1, constr.tv2, "dashed", "forward", ">="))
|
||||
elif isinstance(constr, SameWidth):
|
||||
add_nodes(constr.tv1, constr.tv2)
|
||||
edges.add((constr.tv1, constr.tv2, "dashed", "none",
|
||||
"same_width"))
|
||||
else:
|
||||
assert False, "Can't display constraint {}".format(constr)
|
||||
|
||||
root_nodes = set([x for x in nodes
|
||||
if x not in self.type_map and not x.is_derived])
|
||||
|
||||
r = "digraph {\n"
|
||||
for n in nodes:
|
||||
r += label(n)
|
||||
if n in root_nodes:
|
||||
r += "[xlabel=\"{}\"]".format(self[n].get_typeset())
|
||||
r += ";\n"
|
||||
|
||||
for (n1, n2, style, direction, elabel) in edges:
|
||||
e = label(n1) + "->" + label(n2)
|
||||
e += "[style={},dir={}".format(style, direction)
|
||||
|
||||
if elabel is not None:
|
||||
e += ",label=\"{}\"".format(elabel)
|
||||
e += "];\n"
|
||||
|
||||
r += e
|
||||
r += "}"
|
||||
|
||||
return r
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
TypingError = str
|
||||
TypingOrError = Union[TypeEnv, TypingError]
|
||||
|
||||
|
||||
def get_error(typing_or_err):
|
||||
# type: (TypingOrError) -> Optional[TypingError]
|
||||
"""
|
||||
Helper function to appease mypy when checking the result of typing.
|
||||
"""
|
||||
if isinstance(typing_or_err, str):
|
||||
if (TYPE_CHECKING):
|
||||
return cast(TypingError, typing_or_err)
|
||||
else:
|
||||
return typing_or_err
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def get_type_env(typing_or_err):
|
||||
# type: (TypingOrError) -> TypeEnv
|
||||
"""
|
||||
Helper function to appease mypy when checking the result of typing.
|
||||
"""
|
||||
assert isinstance(typing_or_err, TypeEnv), \
|
||||
"Unexpected error: {}".format(typing_or_err)
|
||||
|
||||
if (TYPE_CHECKING):
|
||||
return cast(TypeEnv, typing_or_err)
|
||||
else:
|
||||
return typing_or_err
|
||||
|
||||
|
||||
def subst(tv, tv_map):
|
||||
# type: (TypeVar, TypeMap) -> TypeVar
|
||||
"""
|
||||
Perform substition on the input tv using the TypeMap tv_map.
|
||||
"""
|
||||
if tv in tv_map:
|
||||
return tv_map[tv]
|
||||
|
||||
if tv.is_derived:
|
||||
return TypeVar.derived(subst(tv.base, tv_map), tv.derived_func)
|
||||
|
||||
return tv
|
||||
|
||||
|
||||
def normalize_tv(tv):
|
||||
# type: (TypeVar) -> TypeVar
|
||||
"""
|
||||
Normalize a (potentially derived) TV using the following rules:
|
||||
- vector and width derived functions commute
|
||||
{HALF,DOUBLE}VECTOR({HALF,DOUBLE}WIDTH(base)) ->
|
||||
{HALF,DOUBLE}WIDTH({HALF,DOUBLE}VECTOR(base))
|
||||
|
||||
- half/double pairs collapse
|
||||
{HALF,DOUBLE}WIDTH({DOUBLE,HALF}WIDTH(base)) -> base
|
||||
{HALF,DOUBLE}VECTOR({DOUBLE,HALF}VECTOR(base)) -> base
|
||||
"""
|
||||
vector_derives = [TypeVar.HALFVECTOR, TypeVar.DOUBLEVECTOR]
|
||||
width_derives = [TypeVar.HALFWIDTH, TypeVar.DOUBLEWIDTH]
|
||||
|
||||
if not tv.is_derived:
|
||||
return tv
|
||||
|
||||
df = tv.derived_func
|
||||
|
||||
if (tv.base.is_derived):
|
||||
base_df = tv.base.derived_func
|
||||
|
||||
# Reordering: {HALFWIDTH, DOUBLEWIDTH} commute with {HALFVECTOR,
|
||||
# DOUBLEVECTOR}. Arbitrarily pick WIDTH < VECTOR
|
||||
if df in vector_derives and base_df in width_derives:
|
||||
return normalize_tv(
|
||||
TypeVar.derived(
|
||||
TypeVar.derived(tv.base.base, df), base_df))
|
||||
|
||||
# Cancelling: HALFWIDTH, DOUBLEWIDTH and HALFVECTOR, DOUBLEVECTOR
|
||||
# cancel each other. Note: This doesn't hide any over/underflows,
|
||||
# since we 1) assert the safety of each TV in the chain upon its
|
||||
# creation, and 2) the base typeset is only allowed to shrink.
|
||||
|
||||
if (df, base_df) in \
|
||||
[(TypeVar.HALFVECTOR, TypeVar.DOUBLEVECTOR),
|
||||
(TypeVar.DOUBLEVECTOR, TypeVar.HALFVECTOR),
|
||||
(TypeVar.HALFWIDTH, TypeVar.DOUBLEWIDTH),
|
||||
(TypeVar.DOUBLEWIDTH, TypeVar.HALFWIDTH)]:
|
||||
return normalize_tv(tv.base.base)
|
||||
|
||||
return TypeVar.derived(normalize_tv(tv.base), df)
|
||||
|
||||
|
||||
def constrain_fixpoint(tv1, tv2):
|
||||
# type: (TypeVar, TypeVar) -> None
|
||||
"""
|
||||
Given typevars tv1 and tv2 (which could be derived from one another)
|
||||
constrain their typesets to be the same. When one is derived from the
|
||||
other, repeat the constrain process until fixpoint.
|
||||
"""
|
||||
# Constrain tv2's typeset as long as tv1's typeset is changing.
|
||||
while True:
|
||||
old_tv1_ts = tv1.get_typeset().copy()
|
||||
tv2.constrain_types(tv1)
|
||||
if tv1.get_typeset() == old_tv1_ts:
|
||||
break
|
||||
|
||||
old_tv2_ts = tv2.get_typeset().copy()
|
||||
tv1.constrain_types(tv2)
|
||||
assert old_tv2_ts == tv2.get_typeset()
|
||||
|
||||
|
||||
def unify(tv1, tv2, typ):
|
||||
# type: (TypeVar, TypeVar, TypeEnv) -> TypingOrError
|
||||
"""
|
||||
Unify tv1 and tv2 in the current type environment typ, and return an
|
||||
updated type environment or error.
|
||||
"""
|
||||
tv1 = normalize_tv(typ[tv1])
|
||||
tv2 = normalize_tv(typ[tv2])
|
||||
|
||||
# Already unified
|
||||
if tv1 == tv2:
|
||||
return typ
|
||||
|
||||
if typ.rank(tv2) < typ.rank(tv1):
|
||||
return unify(tv2, tv1, typ)
|
||||
|
||||
constrain_fixpoint(tv1, tv2)
|
||||
|
||||
if (tv1.get_typeset().size() == 0 or tv2.get_typeset().size() == 0):
|
||||
return "Error: empty type created when unifying {} and {}"\
|
||||
.format(tv1, tv2)
|
||||
|
||||
# Free -> Derived(Free)
|
||||
if not tv1.is_derived:
|
||||
typ.equivalent(tv1, tv2)
|
||||
return typ
|
||||
|
||||
if (tv1.is_derived and TypeVar.is_bijection(tv1.derived_func)):
|
||||
inv_f = TypeVar.inverse_func(tv1.derived_func)
|
||||
return unify(tv1.base, normalize_tv(TypeVar.derived(tv2, inv_f)), typ)
|
||||
|
||||
typ.add_constraint(TypesEqual(tv1, tv2))
|
||||
return typ
|
||||
|
||||
|
||||
def move_first(l, i):
|
||||
# type: (List[T], int) -> List[T]
|
||||
return [l[i]] + l[:i] + l[i+1:]
|
||||
|
||||
|
||||
def ti_def(definition, typ):
|
||||
# type: (Def, TypeEnv) -> TypingOrError
|
||||
"""
|
||||
Perform type inference on one Def in the current type environment typ and
|
||||
return an updated type environment or error.
|
||||
|
||||
At a high level this works by creating fresh copies of each formal type var
|
||||
in the Def's instruction's signature, and unifying the formal tv with the
|
||||
corresponding actual tv.
|
||||
"""
|
||||
expr = definition.expr
|
||||
inst = expr.inst
|
||||
|
||||
# Create a dict m mapping each free typevar in the signature of definition
|
||||
# to a fresh copy of itself.
|
||||
free_formal_tvs = inst.all_typevars()
|
||||
m = {tv: tv.get_fresh_copy(str(typ.get_uid())) for tv in free_formal_tvs}
|
||||
|
||||
# Update m with any explicitly bound type vars
|
||||
for (idx, bound_typ) in enumerate(expr.typevars):
|
||||
m[free_formal_tvs[idx]] = TypeVar.singleton(bound_typ)
|
||||
|
||||
# Get fresh copies for each typevar in the signature (both free and
|
||||
# derived)
|
||||
fresh_formal_tvs = \
|
||||
[subst(inst.outs[i].typevar, m) for i in inst.value_results] +\
|
||||
[subst(inst.ins[i].typevar, m) for i in inst.value_opnums]
|
||||
|
||||
# Get the list of actual Vars
|
||||
actual_vars = [] # type: List[Expr]
|
||||
actual_vars += [definition.defs[i] for i in inst.value_results]
|
||||
actual_vars += [expr.args[i] for i in inst.value_opnums]
|
||||
|
||||
# Get the list of the actual TypeVars
|
||||
actual_tvs = []
|
||||
for v in actual_vars:
|
||||
assert(isinstance(v, Var))
|
||||
# Register with TypeEnv that this typevar corresponds ot variable v,
|
||||
# and thus has a given rank
|
||||
typ.register(v)
|
||||
actual_tvs.append(v.get_typevar())
|
||||
|
||||
# Make sure we unify the control typevar first.
|
||||
if inst.is_polymorphic:
|
||||
idx = fresh_formal_tvs.index(m[inst.ctrl_typevar])
|
||||
fresh_formal_tvs = move_first(fresh_formal_tvs, idx)
|
||||
actual_tvs = move_first(actual_tvs, idx)
|
||||
|
||||
# Unify each actual typevar with the corresponding fresh formal tv
|
||||
for (actual_tv, formal_tv) in zip(actual_tvs, fresh_formal_tvs):
|
||||
typ_or_err = unify(actual_tv, formal_tv, typ)
|
||||
err = get_error(typ_or_err)
|
||||
if (err):
|
||||
return "fail ti on {} <: {}: ".format(actual_tv, formal_tv) + err
|
||||
|
||||
typ = get_type_env(typ_or_err)
|
||||
|
||||
# Add any instruction specific constraints
|
||||
for constr in inst.constraints:
|
||||
typ.add_constraint(constr.translate(m))
|
||||
|
||||
return typ
|
||||
|
||||
|
||||
def ti_rtl(rtl, typ):
|
||||
# type: (Rtl, TypeEnv) -> TypingOrError
|
||||
"""
|
||||
Perform type inference on an Rtl in a starting type env typ. Return an
|
||||
updated type environment or error.
|
||||
"""
|
||||
for (i, d) in enumerate(rtl.rtl):
|
||||
assert (isinstance(d, Def))
|
||||
typ_or_err = ti_def(d, typ)
|
||||
err = get_error(typ_or_err) # type: Optional[TypingError]
|
||||
if (err):
|
||||
return "On line {}: ".format(i) + err
|
||||
|
||||
typ = get_type_env(typ_or_err)
|
||||
|
||||
return typ
|
||||
|
||||
|
||||
def ti_xform(xform, typ):
|
||||
# type: (XForm, TypeEnv) -> TypingOrError
|
||||
"""
|
||||
Perform type inference on an Rtl in a starting type env typ. Return an
|
||||
updated type environment or error.
|
||||
"""
|
||||
typ_or_err = ti_rtl(xform.src, typ)
|
||||
err = get_error(typ_or_err) # type: Optional[TypingError]
|
||||
if (err):
|
||||
return "In src pattern: " + err
|
||||
|
||||
typ = get_type_env(typ_or_err)
|
||||
|
||||
typ_or_err = ti_rtl(xform.dst, typ)
|
||||
err = get_error(typ_or_err)
|
||||
if (err):
|
||||
return "In dst pattern: " + err
|
||||
|
||||
typ = get_type_env(typ_or_err)
|
||||
|
||||
return get_type_env(typ_or_err)
|
||||
348
cranelift/codegen/meta-python/cdsl/types.py
Normal file
348
cranelift/codegen/meta-python/cdsl/types.py
Normal file
@@ -0,0 +1,348 @@
|
||||
"""Cranelift ValueType hierarchy"""
|
||||
from __future__ import absolute_import
|
||||
import math
|
||||
|
||||
try:
|
||||
from typing import Dict, List, cast, TYPE_CHECKING # noqa
|
||||
except ImportError:
|
||||
TYPE_CHECKING = False
|
||||
pass
|
||||
|
||||
|
||||
# Numbering scheme for value types:
|
||||
#
|
||||
# 0: Void
|
||||
# 0x01-0x6f: Special types
|
||||
# 0x70-0x7f: Lane types
|
||||
# 0x80-0xff: Vector types
|
||||
#
|
||||
# Vector types are encoded with the lane type in the low 4 bits and log2(lanes)
|
||||
# in the high 4 bits, giving a range of 2-256 lanes.
|
||||
LANE_BASE = 0x70
|
||||
|
||||
|
||||
# ValueType instances (i8, i32, ...) are provided in the `base.types` module.
|
||||
class ValueType(object):
|
||||
"""
|
||||
A concrete SSA value type.
|
||||
|
||||
All SSA values have a type that is described by an instance of `ValueType`
|
||||
or one of its subclasses.
|
||||
"""
|
||||
|
||||
# Map name -> ValueType.
|
||||
_registry = dict() # type: Dict[str, ValueType]
|
||||
|
||||
# List of all the lane types.
|
||||
all_lane_types = list() # type: List[LaneType]
|
||||
|
||||
# List of all the special types (neither lanes nor vectors).
|
||||
all_special_types = list() # type: List[SpecialType]
|
||||
|
||||
def __init__(self, name, membytes, doc):
|
||||
# type: (str, int, str) -> None
|
||||
self.name = name
|
||||
self.number = None # type: int
|
||||
self.membytes = membytes
|
||||
self.__doc__ = doc
|
||||
assert name not in ValueType._registry
|
||||
ValueType._registry[name] = self
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
return self.name
|
||||
|
||||
def rust_name(self):
|
||||
# type: () -> str
|
||||
return 'ir::types::' + self.name.upper()
|
||||
|
||||
@staticmethod
|
||||
def by_name(name):
|
||||
# type: (str) -> ValueType
|
||||
if name in ValueType._registry:
|
||||
return ValueType._registry[name]
|
||||
else:
|
||||
raise AttributeError("No type named '{}'".format(name))
|
||||
|
||||
def lane_bits(self):
|
||||
# type: () -> int
|
||||
"""Return the number of bits in a lane."""
|
||||
assert False, "Abstract"
|
||||
|
||||
def lane_count(self):
|
||||
# type: () -> int
|
||||
"""Return the number of lanes."""
|
||||
assert False, "Abstract"
|
||||
|
||||
def width(self):
|
||||
# type: () -> int
|
||||
"""Return the total number of bits of an instance of this type."""
|
||||
return self.lane_count() * self.lane_bits()
|
||||
|
||||
def wider_or_equal(self, other):
|
||||
# type: (ValueType) -> bool
|
||||
"""
|
||||
Return true iff:
|
||||
1. self and other have equal number of lanes
|
||||
2. each lane in self has at least as many bits as a lane in other
|
||||
"""
|
||||
return (self.lane_count() == other.lane_count() and
|
||||
self.lane_bits() >= other.lane_bits())
|
||||
|
||||
|
||||
class LaneType(ValueType):
|
||||
"""
|
||||
A concrete scalar type that can appear as a vector lane too.
|
||||
|
||||
Also tracks a unique set of :py:class:`VectorType` instances with this type
|
||||
as the lane type.
|
||||
"""
|
||||
|
||||
def __init__(self, name, membytes, doc):
|
||||
# type: (str, int, str) -> None
|
||||
super(LaneType, self).__init__(name, membytes, doc)
|
||||
self._vectors = dict() # type: Dict[int, VectorType]
|
||||
# Assign numbers starting from LANE_BASE.
|
||||
n = len(ValueType.all_lane_types)
|
||||
ValueType.all_lane_types.append(self)
|
||||
assert n < 16, 'Too many lane types'
|
||||
self.number = LANE_BASE + n
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return 'LaneType({})'.format(self.name)
|
||||
|
||||
def by(self, lanes):
|
||||
# type: (int) -> VectorType
|
||||
"""
|
||||
Get a vector type with this type as the lane type.
|
||||
|
||||
For example, ``i32.by(4)`` returns the :obj:`i32x4` type.
|
||||
"""
|
||||
if lanes in self._vectors:
|
||||
return self._vectors[lanes]
|
||||
else:
|
||||
v = VectorType(self, lanes)
|
||||
self._vectors[lanes] = v
|
||||
return v
|
||||
|
||||
def lane_count(self):
|
||||
# type: () -> int
|
||||
"""Return the number of lanes."""
|
||||
return 1
|
||||
|
||||
|
||||
class VectorType(ValueType):
|
||||
"""
|
||||
A concrete SIMD vector type.
|
||||
|
||||
A vector type has a lane type which is an instance of :class:`LaneType`,
|
||||
and a positive number of lanes.
|
||||
"""
|
||||
|
||||
def __init__(self, base, lanes):
|
||||
# type: (LaneType, int) -> None
|
||||
super(VectorType, self).__init__(
|
||||
name='{}x{}'.format(base.name, lanes),
|
||||
membytes=lanes*base.membytes,
|
||||
doc="""
|
||||
A SIMD vector with {} lanes containing a `{}` each.
|
||||
""".format(lanes, base.name))
|
||||
assert lanes <= 256, "Too many lanes"
|
||||
self.base = base
|
||||
self.lanes = lanes
|
||||
self.number = 16*int(math.log(lanes, 2)) + base.number
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return ('VectorType(base={}, lanes={})'
|
||||
.format(self.base.name, self.lanes))
|
||||
|
||||
def lane_count(self):
|
||||
# type: () -> int
|
||||
"""Return the number of lanes."""
|
||||
return self.lanes
|
||||
|
||||
def lane_bits(self):
|
||||
# type: () -> int
|
||||
"""Return the number of bits in a lane."""
|
||||
return self.base.lane_bits()
|
||||
|
||||
|
||||
class SpecialType(ValueType):
|
||||
"""
|
||||
A concrete scalar type that is neither a vector nor a lane type.
|
||||
|
||||
Special types cannot be used to form vectors.
|
||||
"""
|
||||
|
||||
def __init__(self, name, membytes, doc):
|
||||
# type: (str, int, str) -> None
|
||||
super(SpecialType, self).__init__(name, membytes, doc)
|
||||
# Assign numbers starting from 1. (0 is INVALID)
|
||||
ValueType.all_special_types.append(self)
|
||||
self.number = len(ValueType.all_special_types)
|
||||
assert self.number < LANE_BASE, 'Too many special types'
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return 'SpecialType({})'.format(self.name)
|
||||
|
||||
def lane_count(self):
|
||||
# type: () -> int
|
||||
"""Return the number of lanes."""
|
||||
return 1
|
||||
|
||||
|
||||
class IntType(LaneType):
|
||||
"""A concrete scalar integer type."""
|
||||
|
||||
def __init__(self, bits):
|
||||
# type: (int) -> None
|
||||
assert bits > 0, 'IntType must have positive number of bits'
|
||||
warning = ""
|
||||
if bits < 32:
|
||||
warning += "\nWARNING: "
|
||||
warning += "arithmetic on {}bit integers is incomplete".format(
|
||||
bits)
|
||||
super(IntType, self).__init__(
|
||||
name='i{:d}'.format(bits),
|
||||
membytes=bits // 8,
|
||||
doc="An integer type with {} bits.{}".format(bits, warning))
|
||||
self.bits = bits
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return 'IntType(bits={})'.format(self.bits)
|
||||
|
||||
@staticmethod
|
||||
def with_bits(bits):
|
||||
# type: (int) -> IntType
|
||||
typ = ValueType.by_name('i{:d}'.format(bits))
|
||||
if TYPE_CHECKING:
|
||||
return cast(IntType, typ)
|
||||
else:
|
||||
return typ
|
||||
|
||||
def lane_bits(self):
|
||||
# type: () -> int
|
||||
"""Return the number of bits in a lane."""
|
||||
return self.bits
|
||||
|
||||
|
||||
class FloatType(LaneType):
|
||||
"""A concrete scalar floating point type."""
|
||||
|
||||
def __init__(self, bits, doc):
|
||||
# type: (int, str) -> None
|
||||
assert bits > 0, 'FloatType must have positive number of bits'
|
||||
super(FloatType, self).__init__(
|
||||
name='f{:d}'.format(bits),
|
||||
membytes=bits // 8,
|
||||
doc=doc)
|
||||
self.bits = bits
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return 'FloatType(bits={})'.format(self.bits)
|
||||
|
||||
@staticmethod
|
||||
def with_bits(bits):
|
||||
# type: (int) -> FloatType
|
||||
typ = ValueType.by_name('f{:d}'.format(bits))
|
||||
if TYPE_CHECKING:
|
||||
return cast(FloatType, typ)
|
||||
else:
|
||||
return typ
|
||||
|
||||
def lane_bits(self):
|
||||
# type: () -> int
|
||||
"""Return the number of bits in a lane."""
|
||||
return self.bits
|
||||
|
||||
|
||||
class BoolType(LaneType):
|
||||
"""A concrete scalar boolean type."""
|
||||
|
||||
def __init__(self, bits):
|
||||
# type: (int) -> None
|
||||
assert bits > 0, 'BoolType must have positive number of bits'
|
||||
super(BoolType, self).__init__(
|
||||
name='b{:d}'.format(bits),
|
||||
membytes=bits // 8,
|
||||
doc="A boolean type with {} bits.".format(bits))
|
||||
self.bits = bits
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return 'BoolType(bits={})'.format(self.bits)
|
||||
|
||||
@staticmethod
|
||||
def with_bits(bits):
|
||||
# type: (int) -> BoolType
|
||||
typ = ValueType.by_name('b{:d}'.format(bits))
|
||||
if TYPE_CHECKING:
|
||||
return cast(BoolType, typ)
|
||||
else:
|
||||
return typ
|
||||
|
||||
def lane_bits(self):
|
||||
# type: () -> int
|
||||
"""Return the number of bits in a lane."""
|
||||
return self.bits
|
||||
|
||||
|
||||
class FlagsType(SpecialType):
|
||||
"""
|
||||
A type representing CPU flags.
|
||||
|
||||
Flags can't be stored in memory.
|
||||
"""
|
||||
|
||||
def __init__(self, name, doc):
|
||||
# type: (str, str) -> None
|
||||
super(FlagsType, self).__init__(name, 0, doc)
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return 'FlagsType({})'.format(self.name)
|
||||
|
||||
|
||||
class BVType(ValueType):
|
||||
"""A flat bitvector type. Used for semantics description only."""
|
||||
|
||||
def __init__(self, bits):
|
||||
# type: (int) -> None
|
||||
assert bits > 0, 'Must have positive number of bits'
|
||||
super(BVType, self).__init__(
|
||||
name='bv{:d}'.format(bits),
|
||||
membytes=bits // 8,
|
||||
doc="A bitvector type with {} bits.".format(bits))
|
||||
self.bits = bits
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return 'BVType(bits={})'.format(self.bits)
|
||||
|
||||
@staticmethod
|
||||
def with_bits(bits):
|
||||
# type: (int) -> BVType
|
||||
name = 'bv{:d}'.format(bits)
|
||||
if name not in ValueType._registry:
|
||||
return BVType(bits)
|
||||
|
||||
typ = ValueType.by_name(name)
|
||||
if TYPE_CHECKING:
|
||||
return cast(BVType, typ)
|
||||
else:
|
||||
return typ
|
||||
|
||||
def lane_bits(self):
|
||||
# type: () -> int
|
||||
"""Return the number of bits in a lane."""
|
||||
return self.bits
|
||||
|
||||
def lane_count(self):
|
||||
# type: () -> int
|
||||
"""Return the number of lane. For BVtypes always 1."""
|
||||
return 1
|
||||
906
cranelift/codegen/meta-python/cdsl/typevar.py
Normal file
906
cranelift/codegen/meta-python/cdsl/typevar.py
Normal file
@@ -0,0 +1,906 @@
|
||||
"""
|
||||
Type variables for Parametric polymorphism.
|
||||
|
||||
Cranelift instructions and instruction transformations can be specified to be
|
||||
polymorphic by using type variables.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
import math
|
||||
from . import types, is_power_of_two
|
||||
from copy import copy
|
||||
|
||||
try:
|
||||
from typing import Tuple, Union, Iterable, Any, Set, TYPE_CHECKING # noqa
|
||||
if TYPE_CHECKING:
|
||||
from srcgen import Formatter # noqa
|
||||
Interval = Tuple[int, int]
|
||||
# An Interval where `True` means 'everything'
|
||||
BoolInterval = Union[bool, Interval]
|
||||
# Set of special types: None, False, True, or iterable.
|
||||
SpecialSpec = Union[bool, Iterable[types.SpecialType]]
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
MAX_LANES = 256
|
||||
MAX_BITS = 64
|
||||
MAX_BITVEC = MAX_BITS * MAX_LANES
|
||||
|
||||
|
||||
def int_log2(x):
|
||||
# type: (int) -> int
|
||||
return int(math.log(x, 2))
|
||||
|
||||
|
||||
def intersect(a, b):
|
||||
# type: (Interval, Interval) -> Interval
|
||||
"""
|
||||
Given two `(min, max)` inclusive intervals, compute their intersection.
|
||||
|
||||
Use `(None, None)` to represent the empty interval on input and output.
|
||||
"""
|
||||
if a[0] is None or b[0] is None:
|
||||
return (None, None)
|
||||
lo = max(a[0], b[0])
|
||||
assert lo is not None
|
||||
hi = min(a[1], b[1])
|
||||
assert hi is not None
|
||||
if lo <= hi:
|
||||
return (lo, hi)
|
||||
else:
|
||||
return (None, None)
|
||||
|
||||
|
||||
def is_empty(intv):
|
||||
# type: (Interval) -> bool
|
||||
return intv is None or intv is False or intv == (None, None)
|
||||
|
||||
|
||||
def encode_bitset(vals, size):
|
||||
# type: (Iterable[int], int) -> int
|
||||
"""
|
||||
Encode a set of values (each between 0 and size) as a bitset of width size.
|
||||
"""
|
||||
res = 0
|
||||
assert is_power_of_two(size) and size <= 64
|
||||
for v in vals:
|
||||
assert 0 <= v and v < size
|
||||
res |= 1 << v
|
||||
return res
|
||||
|
||||
|
||||
def pp_set(s):
|
||||
# type: (Iterable[Any]) -> str
|
||||
"""
|
||||
Return a consistent string representation of a set (ordering is fixed)
|
||||
"""
|
||||
return '{' + ', '.join([repr(x) for x in sorted(s)]) + '}'
|
||||
|
||||
|
||||
def decode_interval(intv, full_range, default=None):
|
||||
# type: (BoolInterval, Interval, int) -> Interval
|
||||
"""
|
||||
Decode an interval specification which can take the following values:
|
||||
|
||||
True
|
||||
Use the `full_range`.
|
||||
`False` or `None`
|
||||
An empty interval
|
||||
(lo, hi)
|
||||
An explicit interval
|
||||
"""
|
||||
if isinstance(intv, tuple):
|
||||
# mypy bug here: 'builtins.None' object is not iterable
|
||||
lo, hi = intv
|
||||
assert is_power_of_two(lo)
|
||||
assert is_power_of_two(hi)
|
||||
assert lo <= hi
|
||||
assert lo >= full_range[0]
|
||||
assert hi <= full_range[1]
|
||||
return intv
|
||||
|
||||
if intv:
|
||||
return full_range
|
||||
else:
|
||||
return (default, default)
|
||||
|
||||
|
||||
def interval_to_set(intv):
|
||||
# type: (Interval) -> Set
|
||||
if is_empty(intv):
|
||||
return set()
|
||||
|
||||
(lo, hi) = intv
|
||||
assert is_power_of_two(lo)
|
||||
assert is_power_of_two(hi)
|
||||
assert lo <= hi
|
||||
return set([2**i for i in range(int_log2(lo), int_log2(hi)+1)])
|
||||
|
||||
|
||||
def legal_bool(bits):
|
||||
# type: (int) -> bool
|
||||
"""
|
||||
True iff bits is a legal bit width for a bool type.
|
||||
bits == 1 || bits \\in { 8, 16, .. MAX_BITS }
|
||||
"""
|
||||
return bits == 1 or \
|
||||
(bits >= 8 and bits <= MAX_BITS and is_power_of_two(bits))
|
||||
|
||||
|
||||
class TypeSet(object):
|
||||
"""
|
||||
A set of types.
|
||||
|
||||
We don't allow arbitrary subsets of types, but use a parametrized approach
|
||||
instead.
|
||||
|
||||
Objects of this class can be used as dictionary keys.
|
||||
|
||||
Parametrized type sets are specified in terms of ranges:
|
||||
|
||||
- The permitted range of vector lanes, where 1 indicates a scalar type.
|
||||
- The permitted range of integer types.
|
||||
- The permitted range of floating point types, and
|
||||
- The permitted range of boolean types.
|
||||
|
||||
The ranges are inclusive from smallest bit-width to largest bit-width.
|
||||
|
||||
A typeset representing scalar integer types `i8` through `i32`:
|
||||
|
||||
>>> TypeSet(ints=(8, 32))
|
||||
TypeSet(lanes={1}, ints={8, 16, 32})
|
||||
|
||||
Passing `True` instead of a range selects all available scalar types:
|
||||
|
||||
>>> TypeSet(ints=True)
|
||||
TypeSet(lanes={1}, ints={8, 16, 32, 64})
|
||||
>>> TypeSet(floats=True)
|
||||
TypeSet(lanes={1}, floats={32, 64})
|
||||
>>> TypeSet(bools=True)
|
||||
TypeSet(lanes={1}, bools={1, 8, 16, 32, 64})
|
||||
|
||||
Similarly, passing `True` for the lanes selects all possible scalar and
|
||||
vector types:
|
||||
|
||||
>>> TypeSet(lanes=True, ints=True)
|
||||
TypeSet(lanes={1, 2, 4, 8, 16, 32, 64, 128, 256}, ints={8, 16, 32, 64})
|
||||
|
||||
Finally, a type set can contain special types (derived from `SpecialType`)
|
||||
which can't appear as lane types.
|
||||
|
||||
:param lanes: `(min, max)` inclusive range of permitted vector lane counts.
|
||||
:param ints: `(min, max)` inclusive range of permitted scalar integer
|
||||
widths.
|
||||
:param floats: `(min, max)` inclusive range of permitted scalar floating
|
||||
point widths.
|
||||
:param bools: `(min, max)` inclusive range of permitted scalar boolean
|
||||
widths.
|
||||
:param bitvecs : `(min, max)` inclusive range of permitted bitvector
|
||||
widths.
|
||||
:param specials: Sequence of special types to appear in the set.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
lanes=None, # type: BoolInterval
|
||||
ints=None, # type: BoolInterval
|
||||
floats=None, # type: BoolInterval
|
||||
bools=None, # type: BoolInterval
|
||||
bitvecs=None, # type: BoolInterval
|
||||
specials=None # type: SpecialSpec
|
||||
):
|
||||
# type: (...) -> None
|
||||
self.lanes = interval_to_set(decode_interval(lanes, (1, MAX_LANES), 1))
|
||||
self.ints = interval_to_set(decode_interval(ints, (8, MAX_BITS)))
|
||||
self.floats = interval_to_set(decode_interval(floats, (32, 64)))
|
||||
self.bools = interval_to_set(decode_interval(bools, (1, MAX_BITS)))
|
||||
self.bools = set(filter(legal_bool, self.bools))
|
||||
self.bitvecs = interval_to_set(decode_interval(bitvecs,
|
||||
(1, MAX_BITVEC)))
|
||||
# Allow specials=None, specials=True, specials=(...)
|
||||
self.specials = set() # type: Set[types.SpecialType]
|
||||
if isinstance(specials, bool):
|
||||
if specials:
|
||||
self.specials = set(types.ValueType.all_special_types)
|
||||
elif specials:
|
||||
self.specials = set(specials)
|
||||
|
||||
def copy(self):
|
||||
# type: (TypeSet) -> TypeSet
|
||||
"""
|
||||
Return a copy of our self.
|
||||
"""
|
||||
n = TypeSet()
|
||||
n.lanes = copy(self.lanes)
|
||||
n.ints = copy(self.ints)
|
||||
n.floats = copy(self.floats)
|
||||
n.bools = copy(self.bools)
|
||||
n.bitvecs = copy(self.bitvecs)
|
||||
n.specials = copy(self.specials)
|
||||
return n
|
||||
|
||||
def typeset_key(self):
|
||||
# type: () -> Tuple[Tuple, Tuple, Tuple, Tuple, Tuple, Tuple]
|
||||
"""Key tuple used for hashing and equality."""
|
||||
return (tuple(sorted(list(self.lanes))),
|
||||
tuple(sorted(list(self.ints))),
|
||||
tuple(sorted(list(self.floats))),
|
||||
tuple(sorted(list(self.bools))),
|
||||
tuple(sorted(list(self.bitvecs))),
|
||||
tuple(sorted(s.name for s in self.specials)))
|
||||
|
||||
def __hash__(self):
|
||||
# type: () -> int
|
||||
h = hash(self.typeset_key())
|
||||
assert h == getattr(self, 'prev_hash', h), "TypeSet changed"
|
||||
self.prev_hash = h
|
||||
return h
|
||||
|
||||
def __eq__(self, other):
|
||||
# type: (object) -> bool
|
||||
if isinstance(other, TypeSet):
|
||||
return self.typeset_key() == other.typeset_key()
|
||||
else:
|
||||
return False
|
||||
|
||||
def __ne__(self, other):
|
||||
# type: (object) -> bool
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
s = 'TypeSet(lanes={}'.format(pp_set(self.lanes))
|
||||
if len(self.ints) > 0:
|
||||
s += ', ints={}'.format(pp_set(self.ints))
|
||||
if len(self.floats) > 0:
|
||||
s += ', floats={}'.format(pp_set(self.floats))
|
||||
if len(self.bools) > 0:
|
||||
s += ', bools={}'.format(pp_set(self.bools))
|
||||
if len(self.bitvecs) > 0:
|
||||
s += ', bitvecs={}'.format(pp_set(self.bitvecs))
|
||||
if len(self.specials) > 0:
|
||||
s += ', specials=[{}]'.format(pp_set(self.specials))
|
||||
return s + ')'
|
||||
|
||||
def emit_fields(self, fmt):
|
||||
# type: (Formatter) -> None
|
||||
"""Emit field initializers for this typeset."""
|
||||
assert len(self.bitvecs) == 0, "Bitvector types are not emitable."
|
||||
fmt.comment(repr(self))
|
||||
|
||||
fields = (('lanes', 16),
|
||||
('ints', 8),
|
||||
('floats', 8),
|
||||
('bools', 8))
|
||||
|
||||
for (field, bits) in fields:
|
||||
vals = [int_log2(x) for x in getattr(self, field)]
|
||||
fmt.line('{}: BitSet::<u{}>({}),'
|
||||
.format(field, bits, encode_bitset(vals, bits)))
|
||||
|
||||
def __iand__(self, other):
|
||||
# type: (TypeSet) -> TypeSet
|
||||
"""
|
||||
Intersect self with other type set.
|
||||
|
||||
>>> a = TypeSet(lanes=True, ints=(16, 32))
|
||||
>>> a
|
||||
TypeSet(lanes={1, 2, 4, 8, 16, 32, 64, 128, 256}, ints={16, 32})
|
||||
>>> b = TypeSet(lanes=(4, 16), ints=True)
|
||||
>>> a &= b
|
||||
>>> a
|
||||
TypeSet(lanes={4, 8, 16}, ints={16, 32})
|
||||
|
||||
>>> a = TypeSet(lanes=True, bools=(1, 8))
|
||||
>>> b = TypeSet(lanes=True, bools=(16, 32))
|
||||
>>> a &= b
|
||||
>>> a
|
||||
TypeSet(lanes={1, 2, 4, 8, 16, 32, 64, 128, 256})
|
||||
"""
|
||||
self.lanes.intersection_update(other.lanes)
|
||||
self.ints.intersection_update(other.ints)
|
||||
self.floats.intersection_update(other.floats)
|
||||
self.bools.intersection_update(other.bools)
|
||||
self.bitvecs.intersection_update(other.bitvecs)
|
||||
self.specials.intersection_update(other.specials)
|
||||
|
||||
return self
|
||||
|
||||
def issubset(self, other):
|
||||
# type: (TypeSet) -> bool
|
||||
"""
|
||||
Return true iff self is a subset of other
|
||||
"""
|
||||
return self.lanes.issubset(other.lanes) and \
|
||||
self.ints.issubset(other.ints) and \
|
||||
self.floats.issubset(other.floats) and \
|
||||
self.bools.issubset(other.bools) and \
|
||||
self.bitvecs.issubset(other.bitvecs) and \
|
||||
self.specials.issubset(other.specials)
|
||||
|
||||
def lane_of(self):
|
||||
# type: () -> TypeSet
|
||||
"""
|
||||
Return a TypeSet describing the image of self across lane_of
|
||||
"""
|
||||
new = self.copy()
|
||||
new.lanes = set([1])
|
||||
new.bitvecs = set()
|
||||
return new
|
||||
|
||||
def as_bool(self):
|
||||
# type: () -> TypeSet
|
||||
"""
|
||||
Return a TypeSet describing the image of self across as_bool
|
||||
"""
|
||||
new = self.copy()
|
||||
new.ints = set()
|
||||
new.floats = set()
|
||||
new.bitvecs = set()
|
||||
|
||||
if len(self.lanes.difference(set([1]))) > 0:
|
||||
new.bools = self.ints.union(self.floats).union(self.bools)
|
||||
|
||||
if 1 in self.lanes:
|
||||
new.bools.add(1)
|
||||
return new
|
||||
|
||||
def half_width(self):
|
||||
# type: () -> TypeSet
|
||||
"""
|
||||
Return a TypeSet describing the image of self across halfwidth
|
||||
"""
|
||||
new = self.copy()
|
||||
new.ints = set([x//2 for x in self.ints if x > 8])
|
||||
new.floats = set([x//2 for x in self.floats if x > 32])
|
||||
new.bools = set([x//2 for x in self.bools if x > 8])
|
||||
new.bitvecs = set([x//2 for x in self.bitvecs if x > 1])
|
||||
new.specials = set()
|
||||
|
||||
return new
|
||||
|
||||
def double_width(self):
|
||||
# type: () -> TypeSet
|
||||
"""
|
||||
Return a TypeSet describing the image of self across doublewidth
|
||||
"""
|
||||
new = self.copy()
|
||||
new.ints = set([x*2 for x in self.ints if x < MAX_BITS])
|
||||
new.floats = set([x*2 for x in self.floats if x < MAX_BITS])
|
||||
new.bools = set(filter(legal_bool,
|
||||
set([x*2 for x in self.bools if x < MAX_BITS])))
|
||||
new.bitvecs = set([x*2 for x in self.bitvecs if x < MAX_BITVEC])
|
||||
new.specials = set()
|
||||
|
||||
return new
|
||||
|
||||
def half_vector(self):
|
||||
# type: () -> TypeSet
|
||||
"""
|
||||
Return a TypeSet describing the image of self across halfvector
|
||||
"""
|
||||
new = self.copy()
|
||||
new.bitvecs = set()
|
||||
new.lanes = set([x//2 for x in self.lanes if x > 1])
|
||||
new.specials = set()
|
||||
|
||||
return new
|
||||
|
||||
def double_vector(self):
|
||||
# type: () -> TypeSet
|
||||
"""
|
||||
Return a TypeSet describing the image of self across doublevector
|
||||
"""
|
||||
new = self.copy()
|
||||
new.bitvecs = set()
|
||||
new.lanes = set([x*2 for x in self.lanes if x < MAX_LANES])
|
||||
new.specials = set()
|
||||
|
||||
return new
|
||||
|
||||
def to_bitvec(self):
|
||||
# type: () -> TypeSet
|
||||
"""
|
||||
Return a TypeSet describing the image of self across to_bitvec
|
||||
"""
|
||||
assert len(self.bitvecs) == 0
|
||||
all_scalars = self.ints.union(self.floats.union(self.bools))
|
||||
|
||||
new = self.copy()
|
||||
new.lanes = set([1])
|
||||
new.ints = set()
|
||||
new.bools = set()
|
||||
new.floats = set()
|
||||
new.bitvecs = set([lane_w * nlanes for lane_w in all_scalars
|
||||
for nlanes in self.lanes])
|
||||
new.specials = set()
|
||||
|
||||
return new
|
||||
|
||||
def image(self, func):
|
||||
# type: (str) -> TypeSet
|
||||
"""
|
||||
Return the image of self across the derived function func
|
||||
"""
|
||||
if (func == TypeVar.LANEOF):
|
||||
return self.lane_of()
|
||||
elif (func == TypeVar.ASBOOL):
|
||||
return self.as_bool()
|
||||
elif (func == TypeVar.HALFWIDTH):
|
||||
return self.half_width()
|
||||
elif (func == TypeVar.DOUBLEWIDTH):
|
||||
return self.double_width()
|
||||
elif (func == TypeVar.HALFVECTOR):
|
||||
return self.half_vector()
|
||||
elif (func == TypeVar.DOUBLEVECTOR):
|
||||
return self.double_vector()
|
||||
elif (func == TypeVar.TOBITVEC):
|
||||
return self.to_bitvec()
|
||||
else:
|
||||
assert False, "Unknown derived function: " + func
|
||||
|
||||
def preimage(self, func):
|
||||
# type: (str) -> TypeSet
|
||||
"""
|
||||
Return the inverse image of self across the derived function func
|
||||
"""
|
||||
# The inverse of the empty set is always empty
|
||||
if (self.size() == 0):
|
||||
return self
|
||||
|
||||
if (func == TypeVar.LANEOF):
|
||||
new = self.copy()
|
||||
new.bitvecs = set()
|
||||
new.lanes = set([2**i for i in range(0, int_log2(MAX_LANES)+1)])
|
||||
return new
|
||||
elif (func == TypeVar.ASBOOL):
|
||||
new = self.copy()
|
||||
new.bitvecs = set()
|
||||
|
||||
if 1 not in self.bools:
|
||||
new.ints = self.bools.difference(set([1]))
|
||||
new.floats = self.bools.intersection(set([32, 64]))
|
||||
# If b1 is not in our typeset, than lanes=1 cannot be in the
|
||||
# pre-image, as as_bool() of scalars is always b1.
|
||||
new.lanes = self.lanes.difference(set([1]))
|
||||
else:
|
||||
new.ints = set([2**x for x in range(3, 7)])
|
||||
new.floats = set([32, 64])
|
||||
|
||||
return new
|
||||
elif (func == TypeVar.HALFWIDTH):
|
||||
return self.double_width()
|
||||
elif (func == TypeVar.DOUBLEWIDTH):
|
||||
return self.half_width()
|
||||
elif (func == TypeVar.HALFVECTOR):
|
||||
return self.double_vector()
|
||||
elif (func == TypeVar.DOUBLEVECTOR):
|
||||
return self.half_vector()
|
||||
elif (func == TypeVar.TOBITVEC):
|
||||
new = TypeSet()
|
||||
|
||||
# Start with all possible lanes/ints/floats/bools
|
||||
lanes = interval_to_set(decode_interval(True, (1, MAX_LANES), 1))
|
||||
ints = interval_to_set(decode_interval(True, (8, MAX_BITS)))
|
||||
floats = interval_to_set(decode_interval(True, (32, 64)))
|
||||
bools = interval_to_set(decode_interval(True, (1, MAX_BITS)))
|
||||
|
||||
# See which combinations have a size that appears in self.bitvecs
|
||||
has_t = set() # type: Set[Tuple[str, int, int]]
|
||||
for l in lanes:
|
||||
for i in ints:
|
||||
if i * l in self.bitvecs:
|
||||
has_t.add(('i', i, l))
|
||||
for i in bools:
|
||||
if i * l in self.bitvecs:
|
||||
has_t.add(('b', i, l))
|
||||
for i in floats:
|
||||
if i * l in self.bitvecs:
|
||||
has_t.add(('f', i, l))
|
||||
|
||||
for (t, width, lane) in has_t:
|
||||
new.lanes.add(lane)
|
||||
if (t == 'i'):
|
||||
new.ints.add(width)
|
||||
elif (t == 'b'):
|
||||
new.bools.add(width)
|
||||
else:
|
||||
assert t == 'f'
|
||||
new.floats.add(width)
|
||||
|
||||
return new
|
||||
else:
|
||||
assert False, "Unknown derived function: " + func
|
||||
|
||||
def size(self):
|
||||
# type: () -> int
|
||||
"""
|
||||
Return the number of concrete types represented by this typeset
|
||||
"""
|
||||
return (len(self.lanes) * (len(self.ints) + len(self.floats) +
|
||||
len(self.bools) + len(self.bitvecs)) +
|
||||
len(self.specials))
|
||||
|
||||
def concrete_types(self):
|
||||
# type: () -> Iterable[types.ValueType]
|
||||
def by(scalar, lanes):
|
||||
# type: (types.LaneType, int) -> types.ValueType
|
||||
if (lanes == 1):
|
||||
return scalar
|
||||
else:
|
||||
return scalar.by(lanes)
|
||||
|
||||
for nlanes in self.lanes:
|
||||
for bits in self.ints:
|
||||
yield by(types.IntType.with_bits(bits), nlanes)
|
||||
for bits in self.floats:
|
||||
yield by(types.FloatType.with_bits(bits), nlanes)
|
||||
for bits in self.bools:
|
||||
yield by(types.BoolType.with_bits(bits), nlanes)
|
||||
for bits in self.bitvecs:
|
||||
assert nlanes == 1
|
||||
yield types.BVType.with_bits(bits)
|
||||
|
||||
for spec in self.specials:
|
||||
yield spec
|
||||
|
||||
def get_singleton(self):
|
||||
# type: () -> types.ValueType
|
||||
"""
|
||||
Return the singleton type represented by self. Can only call on
|
||||
typesets containing 1 type.
|
||||
"""
|
||||
types = list(self.concrete_types())
|
||||
assert len(types) == 1
|
||||
return types[0]
|
||||
|
||||
def widths(self):
|
||||
# type: () -> Set[int]
|
||||
""" Return a set of the widths of all possible types in self"""
|
||||
scalar_w = self.ints.union(self.floats.union(self.bools))
|
||||
scalar_w = scalar_w.union(self.bitvecs)
|
||||
return set(w * l for l in self.lanes for w in scalar_w)
|
||||
|
||||
|
||||
class TypeVar(object):
|
||||
"""
|
||||
Type variables can be used in place of concrete types when defining
|
||||
instructions. This makes the instructions *polymorphic*.
|
||||
|
||||
A type variable is restricted to vary over a subset of the value types.
|
||||
This subset is specified by a set of flags that control the permitted base
|
||||
types and whether the type variable can assume scalar or vector types, or
|
||||
both.
|
||||
|
||||
:param name: Short name of type variable used in instruction descriptions.
|
||||
:param doc: Documentation string.
|
||||
:param ints: Allow all integer base types, or `(min, max)` bit-range.
|
||||
:param floats: Allow all floating point base types, or `(min, max)`
|
||||
bit-range.
|
||||
:param bools: Allow all boolean base types, or `(min, max)` bit-range.
|
||||
:param scalars: Allow type variable to assume scalar types.
|
||||
:param simd: Allow type variable to assume vector types, or `(min, max)`
|
||||
lane count range.
|
||||
:param bitvecs: Allow all BitVec base types, or `(min, max)` bit-range.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name, # type: str
|
||||
doc, # type: str
|
||||
ints=False, # type: BoolInterval
|
||||
floats=False, # type: BoolInterval
|
||||
bools=False, # type: BoolInterval
|
||||
scalars=True, # type: bool
|
||||
simd=False, # type: BoolInterval
|
||||
bitvecs=False, # type: BoolInterval
|
||||
base=None, # type: TypeVar
|
||||
derived_func=None, # type: str
|
||||
specials=None # type: SpecialSpec
|
||||
):
|
||||
# type: (...) -> None
|
||||
self.name = name
|
||||
self.__doc__ = doc
|
||||
self.is_derived = isinstance(base, TypeVar)
|
||||
if base:
|
||||
assert self.is_derived
|
||||
assert derived_func
|
||||
self.base = base
|
||||
self.derived_func = derived_func
|
||||
self.name = '{}({})'.format(derived_func, base.name)
|
||||
else:
|
||||
min_lanes = 1 if scalars else 2
|
||||
lanes = decode_interval(simd, (min_lanes, MAX_LANES), 1)
|
||||
self.type_set = TypeSet(
|
||||
lanes=lanes,
|
||||
ints=ints,
|
||||
floats=floats,
|
||||
bools=bools,
|
||||
bitvecs=bitvecs,
|
||||
specials=specials)
|
||||
|
||||
@staticmethod
|
||||
def singleton(typ):
|
||||
# type: (types.ValueType) -> TypeVar
|
||||
"""Create a type variable that can only assume a single type."""
|
||||
scalar = None # type: types.ValueType
|
||||
if isinstance(typ, types.VectorType):
|
||||
scalar = typ.base
|
||||
lanes = (typ.lanes, typ.lanes)
|
||||
elif isinstance(typ, types.LaneType):
|
||||
scalar = typ
|
||||
lanes = (1, 1)
|
||||
elif isinstance(typ, types.SpecialType):
|
||||
return TypeVar(typ.name, typ.__doc__, specials=[typ])
|
||||
else:
|
||||
assert isinstance(typ, types.BVType)
|
||||
scalar = typ
|
||||
lanes = (1, 1)
|
||||
|
||||
ints = None
|
||||
floats = None
|
||||
bools = None
|
||||
bitvecs = None
|
||||
|
||||
if isinstance(scalar, types.IntType):
|
||||
ints = (scalar.bits, scalar.bits)
|
||||
elif isinstance(scalar, types.FloatType):
|
||||
floats = (scalar.bits, scalar.bits)
|
||||
elif isinstance(scalar, types.BoolType):
|
||||
bools = (scalar.bits, scalar.bits)
|
||||
elif isinstance(scalar, types.BVType):
|
||||
bitvecs = (scalar.bits, scalar.bits)
|
||||
|
||||
tv = TypeVar(
|
||||
typ.name, typ.__doc__,
|
||||
ints=ints, floats=floats, bools=bools,
|
||||
bitvecs=bitvecs, simd=lanes)
|
||||
return tv
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
return "`{}`".format(self.name)
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
if self.is_derived:
|
||||
return (
|
||||
'TypeVar({}, base={}, derived_func={})'
|
||||
.format(self.name, self.base, self.derived_func))
|
||||
else:
|
||||
return (
|
||||
'TypeVar({}, {})'
|
||||
.format(self.name, self.type_set))
|
||||
|
||||
def __hash__(self):
|
||||
# type: () -> int
|
||||
if (not self.is_derived):
|
||||
return object.__hash__(self)
|
||||
|
||||
return hash((self.derived_func, self.base))
|
||||
|
||||
def __eq__(self, other):
|
||||
# type: (object) -> bool
|
||||
if not isinstance(other, TypeVar):
|
||||
return False
|
||||
if self.is_derived and other.is_derived:
|
||||
return (
|
||||
self.derived_func == other.derived_func and
|
||||
self.base == other.base)
|
||||
else:
|
||||
return self is other
|
||||
|
||||
def __ne__(self, other):
|
||||
# type: (object) -> bool
|
||||
return not self.__eq__(other)
|
||||
|
||||
# Supported functions for derived type variables.
|
||||
# The names here must match the method names on `ir::types::Type`.
|
||||
# The camel_case of the names must match `enum OperandConstraint` in
|
||||
# `instructions.rs`.
|
||||
LANEOF = 'lane_of'
|
||||
ASBOOL = 'as_bool'
|
||||
HALFWIDTH = 'half_width'
|
||||
DOUBLEWIDTH = 'double_width'
|
||||
HALFVECTOR = 'half_vector'
|
||||
DOUBLEVECTOR = 'double_vector'
|
||||
TOBITVEC = 'to_bitvec'
|
||||
|
||||
@staticmethod
|
||||
def is_bijection(func):
|
||||
# type: (str) -> bool
|
||||
return func in [
|
||||
TypeVar.HALFWIDTH,
|
||||
TypeVar.DOUBLEWIDTH,
|
||||
TypeVar.HALFVECTOR,
|
||||
TypeVar.DOUBLEVECTOR]
|
||||
|
||||
@staticmethod
|
||||
def inverse_func(func):
|
||||
# type: (str) -> str
|
||||
return {
|
||||
TypeVar.HALFWIDTH: TypeVar.DOUBLEWIDTH,
|
||||
TypeVar.DOUBLEWIDTH: TypeVar.HALFWIDTH,
|
||||
TypeVar.HALFVECTOR: TypeVar.DOUBLEVECTOR,
|
||||
TypeVar.DOUBLEVECTOR: TypeVar.HALFVECTOR
|
||||
}[func]
|
||||
|
||||
@staticmethod
|
||||
def derived(base, derived_func):
|
||||
# type: (TypeVar, str) -> TypeVar
|
||||
"""Create a type variable that is a function of another."""
|
||||
|
||||
# Safety checks to avoid over/underflows.
|
||||
ts = base.get_typeset()
|
||||
|
||||
assert len(ts.specials) == 0, "Can't derive from special types"
|
||||
|
||||
if derived_func == TypeVar.HALFWIDTH:
|
||||
if len(ts.ints) > 0:
|
||||
assert min(ts.ints) > 8, "Can't halve all integer types"
|
||||
if len(ts.floats) > 0:
|
||||
assert min(ts.floats) > 32, "Can't halve all float types"
|
||||
if len(ts.bools) > 0:
|
||||
assert min(ts.bools) > 8, "Can't halve all boolean types"
|
||||
elif derived_func == TypeVar.DOUBLEWIDTH:
|
||||
if len(ts.ints) > 0:
|
||||
assert max(ts.ints) < MAX_BITS,\
|
||||
"Can't double all integer types."
|
||||
if len(ts.floats) > 0:
|
||||
assert max(ts.floats) < MAX_BITS,\
|
||||
"Can't double all float types."
|
||||
if len(ts.bools) > 0:
|
||||
assert max(ts.bools) < MAX_BITS, "Can't double all bool types."
|
||||
elif derived_func == TypeVar.HALFVECTOR:
|
||||
assert min(ts.lanes) > 1, "Can't halve a scalar type"
|
||||
elif derived_func == TypeVar.DOUBLEVECTOR:
|
||||
assert max(ts.lanes) < MAX_LANES, "Can't double 256 lanes."
|
||||
|
||||
return TypeVar(None, None, base=base, derived_func=derived_func)
|
||||
|
||||
@staticmethod
|
||||
def from_typeset(ts):
|
||||
# type: (TypeSet) -> TypeVar
|
||||
""" Create a type variable from a type set."""
|
||||
tv = TypeVar(None, None)
|
||||
tv.type_set = ts
|
||||
return tv
|
||||
|
||||
def lane_of(self):
|
||||
# type: () -> TypeVar
|
||||
"""
|
||||
Return a derived type variable that is the scalar lane type of this
|
||||
type variable.
|
||||
|
||||
When this type variable assumes a scalar type, the derived type will be
|
||||
the same scalar type.
|
||||
"""
|
||||
return TypeVar.derived(self, self.LANEOF)
|
||||
|
||||
def as_bool(self):
|
||||
# type: () -> TypeVar
|
||||
"""
|
||||
Return a derived type variable that has the same vector geometry as
|
||||
this type variable, but with boolean lanes. Scalar types map to `b1`.
|
||||
"""
|
||||
return TypeVar.derived(self, self.ASBOOL)
|
||||
|
||||
def half_width(self):
|
||||
# type: () -> TypeVar
|
||||
"""
|
||||
Return a derived type variable that has the same number of vector lanes
|
||||
as this one, but the lanes are half the width.
|
||||
"""
|
||||
return TypeVar.derived(self, self.HALFWIDTH)
|
||||
|
||||
def double_width(self):
|
||||
# type: () -> TypeVar
|
||||
"""
|
||||
Return a derived type variable that has the same number of vector lanes
|
||||
as this one, but the lanes are double the width.
|
||||
"""
|
||||
return TypeVar.derived(self, self.DOUBLEWIDTH)
|
||||
|
||||
def half_vector(self):
|
||||
# type: () -> TypeVar
|
||||
"""
|
||||
Return a derived type variable that has half the number of vector lanes
|
||||
as this one, with the same lane type.
|
||||
"""
|
||||
return TypeVar.derived(self, self.HALFVECTOR)
|
||||
|
||||
def double_vector(self):
|
||||
# type: () -> TypeVar
|
||||
"""
|
||||
Return a derived type variable that has twice the number of vector
|
||||
lanes as this one, with the same lane type.
|
||||
"""
|
||||
return TypeVar.derived(self, self.DOUBLEVECTOR)
|
||||
|
||||
def to_bitvec(self):
|
||||
# type: () -> TypeVar
|
||||
"""
|
||||
Return a derived type variable that represent a flat bitvector with
|
||||
the same size as self
|
||||
"""
|
||||
return TypeVar.derived(self, self.TOBITVEC)
|
||||
|
||||
def singleton_type(self):
|
||||
# type: () -> types.ValueType
|
||||
"""
|
||||
If the associated typeset has a single type return it. Otherwise return
|
||||
None
|
||||
"""
|
||||
ts = self.get_typeset()
|
||||
if ts.size() != 1:
|
||||
return None
|
||||
|
||||
return ts.get_singleton()
|
||||
|
||||
def free_typevar(self):
|
||||
# type: () -> TypeVar
|
||||
"""
|
||||
Get the free type variable controlling this one.
|
||||
"""
|
||||
if self.is_derived:
|
||||
return self.base.free_typevar()
|
||||
elif self.singleton_type() is not None:
|
||||
# A singleton type variable is not a proper free variable.
|
||||
return None
|
||||
else:
|
||||
return self
|
||||
|
||||
def rust_expr(self):
|
||||
# type: () -> str
|
||||
"""
|
||||
Get a Rust expression that computes the type of this type variable.
|
||||
"""
|
||||
if self.is_derived:
|
||||
return '{}.{}()'.format(
|
||||
self.base.rust_expr(), self.derived_func)
|
||||
elif self.singleton_type():
|
||||
return self.singleton_type().rust_name()
|
||||
else:
|
||||
return self.name
|
||||
|
||||
def constrain_types_by_ts(self, ts):
|
||||
# type: (TypeSet) -> None
|
||||
"""
|
||||
Constrain the range of types this variable can assume to a subset of
|
||||
those in the typeset ts.
|
||||
"""
|
||||
if not self.is_derived:
|
||||
self.type_set &= ts
|
||||
else:
|
||||
self.base.constrain_types_by_ts(ts.preimage(self.derived_func))
|
||||
|
||||
def constrain_types(self, other):
|
||||
# type: (TypeVar) -> None
|
||||
"""
|
||||
Constrain the range of types this variable can assume to a subset of
|
||||
those `other` can assume.
|
||||
"""
|
||||
if self is other:
|
||||
return
|
||||
|
||||
self.constrain_types_by_ts(other.get_typeset())
|
||||
|
||||
def get_typeset(self):
|
||||
# type: () -> TypeSet
|
||||
"""
|
||||
Returns the typeset for this TV. If the TV is derived, computes it
|
||||
recursively from the derived function and the base's typeset.
|
||||
"""
|
||||
if not self.is_derived:
|
||||
return self.type_set
|
||||
else:
|
||||
return self.base.get_typeset().image(self.derived_func)
|
||||
|
||||
def get_fresh_copy(self, name):
|
||||
# type: (str) -> TypeVar
|
||||
"""
|
||||
Get a fresh copy of self. Can only be called on free typevars.
|
||||
"""
|
||||
assert not self.is_derived
|
||||
tv = TypeVar.from_typeset(self.type_set.copy())
|
||||
tv.name = name
|
||||
return tv
|
||||
423
cranelift/codegen/meta-python/cdsl/xform.py
Normal file
423
cranelift/codegen/meta-python/cdsl/xform.py
Normal file
@@ -0,0 +1,423 @@
|
||||
"""
|
||||
Instruction transformations.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from .ast import Def, Var, Apply
|
||||
from .ti import ti_xform, TypeEnv, get_type_env, TypeConstraint
|
||||
from collections import OrderedDict
|
||||
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, VarAtomMap # noqa
|
||||
from .isa import TargetISA # noqa
|
||||
from .typevar import TypeVar # noqa
|
||||
from .instructions import ConstrList, Instruction # noqa
|
||||
DefApply = Union[Def, Apply]
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def canonicalize_defapply(node):
|
||||
# type: (DefApply) -> Def
|
||||
"""
|
||||
Canonicalize a `Def` or `Apply` node into a `Def`.
|
||||
|
||||
An `Apply` becomes a `Def` with an empty list of defs.
|
||||
"""
|
||||
if isinstance(node, Apply):
|
||||
return Def((), node)
|
||||
else:
|
||||
return node
|
||||
|
||||
|
||||
class Rtl(object):
|
||||
"""
|
||||
Register Transfer Language list.
|
||||
|
||||
An RTL object contains a list of register assignments in the form of `Def`
|
||||
objects.
|
||||
|
||||
An RTL list can represent both a source pattern to be matched, or a
|
||||
destination pattern to be inserted.
|
||||
"""
|
||||
|
||||
def __init__(self, *args):
|
||||
# type: (*DefApply) -> None
|
||||
self.rtl = tuple(map(canonicalize_defapply, args))
|
||||
|
||||
def copy(self, m):
|
||||
# type: (VarAtomMap) -> Rtl
|
||||
"""
|
||||
Return a copy of this rtl with all Vars substituted with copies or
|
||||
according to m. Update m as necessary.
|
||||
"""
|
||||
return Rtl(*[d.copy(m) for d in self.rtl])
|
||||
|
||||
def vars(self):
|
||||
# type: () -> Set[Var]
|
||||
"""Return the set of all Vars in self that correspond to SSA values"""
|
||||
return reduce(lambda x, y: x.union(y),
|
||||
[d.vars() for d in self.rtl],
|
||||
set([]))
|
||||
|
||||
def definitions(self):
|
||||
# type: () -> Set[Var]
|
||||
""" Return the set of all Vars defined in self"""
|
||||
return reduce(lambda x, y: x.union(y),
|
||||
[d.definitions() for d in self.rtl],
|
||||
set([]))
|
||||
|
||||
def free_vars(self):
|
||||
# type: () -> Set[Var]
|
||||
"""Return the set of free Vars corresp. to SSA vals used in self"""
|
||||
def flow_f(s, d):
|
||||
# type: (Set[Var], Def) -> Set[Var]
|
||||
"""Compute the change in the set of free vars across a Def"""
|
||||
s = s.difference(set(d.defs))
|
||||
uses = set(d.expr.args[i] for i in d.expr.inst.value_opnums)
|
||||
for v in uses:
|
||||
assert isinstance(v, Var)
|
||||
s.add(v)
|
||||
|
||||
return s
|
||||
|
||||
return reduce(flow_f, reversed(self.rtl), set([]))
|
||||
|
||||
def substitution(self, other, s):
|
||||
# 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
|
||||
they have the same sequence of Defs, that agree structurally.
|
||||
"""
|
||||
if len(self.rtl) != len(other.rtl):
|
||||
return None
|
||||
|
||||
for i in range(len(self.rtl)):
|
||||
s = self.rtl[i].substitution(other.rtl[i], s)
|
||||
|
||||
if s is None:
|
||||
return None
|
||||
|
||||
return s
|
||||
|
||||
def is_concrete(self):
|
||||
# type: (Rtl) -> bool
|
||||
"""Return True iff every Var in the self has a singleton type."""
|
||||
return all(v.get_typevar().singleton_type() is not None
|
||||
for v in self.vars())
|
||||
|
||||
def cleanup_concrete_rtl(self):
|
||||
# type: (Rtl) -> None
|
||||
"""
|
||||
Given that there is only 1 possible concrete typing T for self, assign
|
||||
a singleton TV with type t=T[v] for each Var v \\in self. Its an error
|
||||
to call this on an Rtl with more than 1 possible typing. This modifies
|
||||
the Rtl in-place.
|
||||
"""
|
||||
from .ti import ti_rtl, TypeEnv
|
||||
# 1) Infer the types of all vars in res
|
||||
typenv = get_type_env(ti_rtl(self, TypeEnv()))
|
||||
typenv.normalize()
|
||||
typenv = typenv.extract()
|
||||
|
||||
# 2) Make sure there is only one possible type assignment
|
||||
typings = list(typenv.concrete_typings())
|
||||
assert len(typings) == 1
|
||||
typing = typings[0]
|
||||
|
||||
# 3) Assign the only possible type to each variable.
|
||||
for v in typenv.vars:
|
||||
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):
|
||||
"""
|
||||
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 pattern may optionally have a sequence of
|
||||
TypeConstraints, that additionally limit the set of cases when it applies.
|
||||
|
||||
A legalization pattern must have a source pattern containing only a single
|
||||
instruction.
|
||||
|
||||
>>> from base.instructions 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, src), Var(a, src, dst)],
|
||||
c << iconst(v)
|
||||
a << iadd(x, c)
|
||||
=>
|
||||
a << iadd_imm(x, v)
|
||||
)
|
||||
"""
|
||||
|
||||
def __init__(self, src, dst, constraints=None):
|
||||
# type: (Rtl, Rtl, Optional[ConstrList]) -> None
|
||||
self.src = src
|
||||
self.dst = dst
|
||||
# Variables that are inputs to the source pattern.
|
||||
self.inputs = list() # type: List[Var]
|
||||
# Variables defined in either src or dst.
|
||||
self.defs = list() # type: List[Var]
|
||||
|
||||
# Rewrite variables in src and dst RTL lists to our own copies.
|
||||
# Map name -> private Var.
|
||||
symtab = dict() # type: Dict[str, Var]
|
||||
self._rewrite_rtl(src, symtab, Var.SRCCTX)
|
||||
num_src_inputs = len(self.inputs)
|
||||
self._rewrite_rtl(dst, symtab, Var.DSTCTX)
|
||||
# Needed for testing type inference on XForms
|
||||
self.symtab = symtab
|
||||
|
||||
# Check for inconsistently used inputs.
|
||||
for i in self.inputs:
|
||||
if not i.is_input():
|
||||
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:]))
|
||||
|
||||
# Perform type inference and cleanup
|
||||
raw_ti = get_type_env(ti_xform(self, TypeEnv()))
|
||||
raw_ti.normalize()
|
||||
self.ti = raw_ti.extract()
|
||||
|
||||
def interp_tv(tv):
|
||||
# type: (TypeVar) -> TypeVar
|
||||
""" Convert typevars according to symtab """
|
||||
if not tv.name.startswith("typeof_"):
|
||||
return tv
|
||||
return symtab[tv.name[len("typeof_"):]].get_typevar()
|
||||
|
||||
self.constraints = [] # type: List[TypeConstraint]
|
||||
if constraints is not None:
|
||||
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()}
|
||||
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
|
||||
free_typevars = set(self.ti.free_typevars())
|
||||
src_vars = set(self.inputs).union(
|
||||
[x for x in self.defs if not x.is_temp()])
|
||||
src_tvs = set([v.get_typevar() for v in src_vars])
|
||||
if (not free_typevars.issubset(src_tvs)):
|
||||
raise AssertionError(
|
||||
"Some free vars don't appear in src - {}"
|
||||
.format(free_typevars.difference(src_tvs)))
|
||||
|
||||
# Update the type vars for each Var to their inferred values
|
||||
for v in self.inputs + self.defs:
|
||||
v.set_typevar(self.ti[v.get_typevar()])
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
s = "XForm(inputs={}, defs={},\n ".format(self.inputs, self.defs)
|
||||
s += '\n '.join(str(n) for n in self.src.rtl)
|
||||
s += '\n=>\n '
|
||||
s += '\n '.join(str(n) for n in self.dst.rtl)
|
||||
s += '\n)'
|
||||
return s
|
||||
|
||||
def _rewrite_rtl(self, rtl, symtab, context):
|
||||
# type: (Rtl, Dict[str, Var], int) -> None
|
||||
for line in rtl.rtl:
|
||||
if isinstance(line, Def):
|
||||
line.defs = tuple(
|
||||
self._rewrite_defs(line, symtab, context))
|
||||
expr = line.expr
|
||||
else:
|
||||
expr = line
|
||||
self._rewrite_expr(expr, symtab, context)
|
||||
|
||||
def _rewrite_expr(self, expr, symtab, context):
|
||||
# type: (Apply, Dict[str, Var], int) -> None
|
||||
"""
|
||||
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, line, symtab, context):
|
||||
# type: (Def, Dict[str, Var], int) -> Iterable[Var]
|
||||
"""
|
||||
Given a tuple of symbols defined in a Def, rewrite them to local
|
||||
symbols. Yield the new locals.
|
||||
"""
|
||||
for sym in line.defs:
|
||||
name = str(sym)
|
||||
if name in symtab:
|
||||
var = symtab[name]
|
||||
if var.get_def(context):
|
||||
raise AssertionError("'{}' multiply defined".format(name))
|
||||
else:
|
||||
var = Var(name)
|
||||
symtab[name] = var
|
||||
self.defs.append(var)
|
||||
var.set_def(context, line)
|
||||
yield var
|
||||
|
||||
def _rewrite_uses(self, expr, stack, symtab, context):
|
||||
# type: (Apply, List[Apply], Dict[str, Var], int) -> Iterable[Expr]
|
||||
"""
|
||||
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.append(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 consistently as a def or input.
|
||||
if not var.is_input() and not var.get_def(context):
|
||||
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
|
||||
|
||||
def verify_legalize(self):
|
||||
# type: () -> None
|
||||
"""
|
||||
Verify that this is a valid legalization XForm.
|
||||
|
||||
- The source pattern must describe a single instruction.
|
||||
- All values defined in the output pattern must be defined in the
|
||||
destination pattern.
|
||||
"""
|
||||
assert len(self.src.rtl) == 1, "Legalize needs single instruction."
|
||||
for d in self.src.rtl[0].defs:
|
||||
if not d.is_output():
|
||||
raise AssertionError(
|
||||
'{} not defined in dest pattern'.format(d))
|
||||
|
||||
def apply(self, r, suffix=None):
|
||||
# type: (Rtl, str) -> Rtl
|
||||
"""
|
||||
Given a concrete Rtl r s.t. r matches self.src, return the
|
||||
corresponding concrete self.dst. If suffix is provided, any temporary
|
||||
defs are renamed with '.suffix' appended to their old name.
|
||||
"""
|
||||
assert r.is_concrete()
|
||||
s = self.src.substitution(r, {}) # type: VarAtomMap
|
||||
assert s is not None
|
||||
|
||||
if (suffix is not None):
|
||||
for v in self.dst.vars():
|
||||
if v.is_temp():
|
||||
assert v not in s
|
||||
s[v] = Var(v.name + '.' + suffix)
|
||||
|
||||
dst = self.dst.copy(s)
|
||||
dst.cleanup_concrete_rtl()
|
||||
return dst
|
||||
|
||||
|
||||
class XFormGroup(object):
|
||||
"""
|
||||
A group of related transformations.
|
||||
|
||||
:param isa: A target ISA whose instructions are allowed.
|
||||
:param chain: A next level group to try if this one doesn't match.
|
||||
"""
|
||||
|
||||
def __init__(self, name, doc, isa=None, chain=None):
|
||||
# type: (str, str, TargetISA, XFormGroup) -> None
|
||||
self.xforms = list() # type: List[XForm]
|
||||
self.custom = OrderedDict() # type: OrderedDict[Instruction, str]
|
||||
self.name = name
|
||||
self.__doc__ = doc
|
||||
self.isa = isa
|
||||
self.chain = chain
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
if self.isa:
|
||||
return '{}.{}'.format(self.isa.name, self.name)
|
||||
else:
|
||||
return self.name
|
||||
|
||||
def rust_name(self):
|
||||
# type: () -> str
|
||||
"""
|
||||
Get the Rust name of this function implementing this transform.
|
||||
"""
|
||||
if self.isa:
|
||||
# This is a function in the same module as the LEGALIZE_ACTION
|
||||
# table referring to it.
|
||||
return self.name
|
||||
else:
|
||||
return 'crate::legalizer::{}'.format(self.name)
|
||||
|
||||
def legalize(self, src, dst):
|
||||
# type: (Union[Def, Apply], Rtl) -> None
|
||||
"""
|
||||
Add a legalization pattern to this group.
|
||||
|
||||
:param src: Single `Def` or `Apply` to be legalized.
|
||||
:param dst: `Rtl` list of replacement instructions.
|
||||
"""
|
||||
xform = XForm(Rtl(src), dst)
|
||||
xform.verify_legalize()
|
||||
self.xforms.append(xform)
|
||||
|
||||
def custom_legalize(self, inst, funcname):
|
||||
# type: (Instruction, str) -> None
|
||||
"""
|
||||
Add a custom legalization action for `inst`.
|
||||
|
||||
The `funcname` parameter is the fully qualified name of a Rust function
|
||||
which takes the same arguments as the `isa::Legalize` actions.
|
||||
|
||||
The custom function will be called to legalize `inst` and any return
|
||||
value is ignored.
|
||||
"""
|
||||
assert inst not in self.custom, "Duplicate custom_legalize"
|
||||
self.custom[inst] = funcname
|
||||
32
cranelift/codegen/meta-python/check.sh
Executable file
32
cranelift/codegen/meta-python/check.sh
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
topdir=$(dirname "$0")
|
||||
cd "$topdir"
|
||||
|
||||
function runif {
|
||||
if type "$1" > /dev/null 2>&1; then
|
||||
version=$("$1" --version 2>&1)
|
||||
echo " === $1: $version ==="
|
||||
"$@"
|
||||
else
|
||||
echo "$1 not found"
|
||||
fi
|
||||
}
|
||||
|
||||
# Style linting.
|
||||
runif flake8 .
|
||||
|
||||
# Type checking.
|
||||
# TODO: Re-enable mypy on Travis osx. Pip currently installs mypy into a
|
||||
# directory which is not in the PATH.
|
||||
if [ "${TRAVIS_OS_NAME:-other}" != "osx" ]; then
|
||||
runif mypy --py2 build.py
|
||||
fi
|
||||
|
||||
# Python unit tests.
|
||||
runif python2.7 -m unittest discover
|
||||
|
||||
# Then run the unit tests again with Python 3.
|
||||
# We get deprecation warnings about assertRaisesRegexp which was renamed in
|
||||
# Python 3, but there doesn't seem to be an easy workaround.
|
||||
runif python3 -Wignore:Deprecation -m unittest discover
|
||||
63
cranelift/codegen/meta-python/constant_hash.py
Normal file
63
cranelift/codegen/meta-python/constant_hash.py
Normal file
@@ -0,0 +1,63 @@
|
||||
"""
|
||||
Generate constant hash tables.
|
||||
|
||||
The `constant_hash` module can generate constant pre-populated hash tables. We
|
||||
don't attempt perfect hashing, but simply generate an open addressed
|
||||
quadratically probed hash table.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl import next_power_of_two
|
||||
|
||||
try:
|
||||
from typing import Any, List, Iterable, Callable # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def simple_hash(s):
|
||||
# type: (str) -> int
|
||||
"""
|
||||
Compute a primitive hash of a string.
|
||||
|
||||
Example:
|
||||
>>> "0x%x" % simple_hash("Hello")
|
||||
'0x2fa70c01'
|
||||
>>> "0x%x" % simple_hash("world")
|
||||
'0x5b0c31d5'
|
||||
"""
|
||||
h = 5381
|
||||
for c in s:
|
||||
h = ((h ^ ord(c)) + ((h >> 6) + (h << 26))) & 0xffffffff
|
||||
return h
|
||||
|
||||
|
||||
def compute_quadratic(items, hash_function):
|
||||
# type: (Iterable[Any], Callable[[Any], int]) -> List[Any]
|
||||
"""
|
||||
Compute an open addressed, quadratically probed hash table containing
|
||||
`items`. The returned table is a list containing the elements of the
|
||||
iterable `items` and `None` in unused slots.
|
||||
|
||||
:param items: Iterable set of items to place in hash table.
|
||||
:param hash_function: Hash function which takes an item and returns a
|
||||
number.
|
||||
|
||||
Simple example (see hash values above, they collide on slot 1):
|
||||
>>> compute_quadratic(['Hello', 'world'], simple_hash)
|
||||
[None, 'Hello', 'world', None]
|
||||
"""
|
||||
|
||||
items = list(items)
|
||||
# Table size must be a power of two. Aim for >20% unused slots.
|
||||
size = next_power_of_two(int(1.20*len(items)))
|
||||
table = [None] * size # type: List[Any]
|
||||
|
||||
for i in items:
|
||||
h = hash_function(i) % size
|
||||
s = 0
|
||||
while table[h] is not None:
|
||||
s += 1
|
||||
h = (h + s) % size
|
||||
table[h] = i
|
||||
|
||||
return table
|
||||
170
cranelift/codegen/meta-python/gen_binemit.py
Normal file
170
cranelift/codegen/meta-python/gen_binemit.py
Normal file
@@ -0,0 +1,170 @@
|
||||
"""
|
||||
Generate binary emission code for each ISA.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from cdsl.registers import RegClass, Stack
|
||||
import srcgen
|
||||
|
||||
try:
|
||||
from typing import Sequence, List # noqa
|
||||
from cdsl.isa import TargetISA, EncRecipe, OperandConstraint # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def gen_recipe(recipe, fmt):
|
||||
# type: (EncRecipe, srcgen.Formatter) -> None
|
||||
"""
|
||||
Generate code to handle a single recipe.
|
||||
|
||||
- Unpack the instruction data, knowing the format.
|
||||
- Determine register locations for operands with register constraints.
|
||||
- Determine stack slot locations for operands with stack constraints.
|
||||
- Call hand-written code for the actual emission.
|
||||
"""
|
||||
iform = recipe.format
|
||||
nvops = iform.num_value_operands
|
||||
want_args = any(isinstance(i, RegClass) or isinstance(i, Stack)
|
||||
for i in recipe.ins)
|
||||
assert not want_args or nvops > 0 or iform.has_value_list
|
||||
want_outs = any(isinstance(o, RegClass) or isinstance(o, Stack)
|
||||
for o in recipe.outs)
|
||||
|
||||
# Regmove instructions get special treatment.
|
||||
is_regmove = (recipe.format.name in ('RegMove', 'RegSpill', 'RegFill'))
|
||||
|
||||
# First unpack the instruction.
|
||||
with fmt.indented(
|
||||
'if let InstructionData::{} {{'.format(iform.name),
|
||||
'}'):
|
||||
fmt.line('opcode,')
|
||||
for f in iform.imm_fields:
|
||||
fmt.line('{},'.format(f.member))
|
||||
if want_args:
|
||||
if iform.has_value_list or nvops > 1:
|
||||
fmt.line('ref args,')
|
||||
else:
|
||||
fmt.line('arg,')
|
||||
fmt.line('..')
|
||||
fmt.outdented_line('} = func.dfg[inst] {')
|
||||
|
||||
# Pass recipe arguments in this order: inputs, imm_fields, outputs.
|
||||
args = ''
|
||||
|
||||
# Normalize to an `args` array.
|
||||
if want_args and not is_regmove:
|
||||
if iform.has_value_list:
|
||||
fmt.line('let args = args.as_slice(&func.dfg.value_lists);')
|
||||
elif nvops == 1:
|
||||
fmt.line('let args = [arg];')
|
||||
args += unwrap_values(recipe.ins, 'in', 'args', fmt)
|
||||
|
||||
for f in iform.imm_fields:
|
||||
args += ', ' + f.member
|
||||
|
||||
# Unwrap interesting output arguments.
|
||||
if want_outs:
|
||||
if len(recipe.outs) == 1:
|
||||
fmt.line('let results = [func.dfg.first_result(inst)];')
|
||||
else:
|
||||
fmt.line('let results = func.dfg.inst_results(inst);')
|
||||
args += unwrap_values(recipe.outs, 'out', 'results', fmt)
|
||||
|
||||
# Special handling for regmove instructions. Update the register
|
||||
# diversion tracker.
|
||||
if recipe.format.name == 'RegMove':
|
||||
fmt.line('divert.regmove(arg, src, dst);')
|
||||
elif recipe.format.name == 'RegSpill':
|
||||
fmt.line('divert.regspill(arg, src, dst);')
|
||||
elif recipe.format.name == 'RegFill':
|
||||
fmt.line('divert.regfill(arg, src, dst);')
|
||||
|
||||
# Call hand-written code. If the recipe contains a code snippet, use
|
||||
# that. Otherwise cal a recipe function in the target ISA's binemit
|
||||
# module.
|
||||
if recipe.emit is None:
|
||||
fmt.format(
|
||||
'return recipe_{}(func, inst, sink, bits{});',
|
||||
recipe.name.lower(), args)
|
||||
else:
|
||||
fmt.multi_line(recipe.emit)
|
||||
fmt.line('return;')
|
||||
|
||||
|
||||
def unwrap_values(args, prefix, values, fmt):
|
||||
# type: (Sequence[OperandConstraint], str, str, srcgen.Formatter) -> str # noqa
|
||||
"""
|
||||
Emit code that unwraps values living in registers or stack slots.
|
||||
|
||||
:param args: Input or output constraints.
|
||||
:param prefix: Prefix to be used for the generated local variables.
|
||||
:param values: Name of slice containing the values to be unwrapped.
|
||||
:returns: Comma separated list of the generated variables
|
||||
"""
|
||||
varlist = ''
|
||||
for i, cst in enumerate(args):
|
||||
if isinstance(cst, RegClass):
|
||||
v = '{}_reg{}'.format(prefix, i)
|
||||
varlist += ', ' + v
|
||||
fmt.format(
|
||||
'let {} = divert.reg({}[{}], &func.locations);',
|
||||
v, values, i)
|
||||
elif isinstance(cst, Stack):
|
||||
v = '{}_stk{}'.format(prefix, i)
|
||||
varlist += ', ' + v
|
||||
with fmt.indented(
|
||||
'let {} = StackRef::masked('.format(v),
|
||||
').unwrap();'):
|
||||
fmt.format('divert.stack({}[{}], &func.locations),', values, i)
|
||||
fmt.format('{},', cst.stack_base_mask())
|
||||
fmt.line('&func.stack_slots,')
|
||||
return varlist
|
||||
|
||||
|
||||
def gen_isa(isa, fmt):
|
||||
# type: (TargetISA, srcgen.Formatter) -> None
|
||||
"""
|
||||
Generate Binary emission code for `isa`.
|
||||
"""
|
||||
fmt.doc_comment(
|
||||
'''
|
||||
Emit binary machine code for `inst` for the {} ISA.
|
||||
'''.format(isa.name))
|
||||
if len(isa.all_recipes) == 0:
|
||||
# No encoding recipes: Emit a stub.
|
||||
with fmt.indented('pub fn emit_inst<CS: CodeSink + ?Sized>('):
|
||||
fmt.line('func: &Function,')
|
||||
fmt.line('inst: Inst,')
|
||||
fmt.line('_divert: &mut RegDiversions,')
|
||||
fmt.line('_sink: &mut CS,')
|
||||
with fmt.indented(') {', '}'):
|
||||
fmt.line('bad_encoding(func, inst)')
|
||||
else:
|
||||
fmt.line('#[allow(unused_variables, unreachable_code)]')
|
||||
with fmt.indented('pub fn emit_inst<CS: CodeSink + ?Sized>('):
|
||||
fmt.line('func: &Function,')
|
||||
fmt.line('inst: Inst,')
|
||||
fmt.line('divert: &mut RegDiversions,')
|
||||
fmt.line('sink: &mut CS,')
|
||||
with fmt.indented(') {', '}'):
|
||||
fmt.line('let encoding = func.encodings[inst];')
|
||||
fmt.line('let bits = encoding.bits();')
|
||||
with fmt.indented('match func.encodings[inst].recipe() {', '}'):
|
||||
for i, recipe in enumerate(isa.all_recipes):
|
||||
fmt.comment('Recipe {}'.format(recipe.name))
|
||||
with fmt.indented('{} => {{'.format(i), '}'):
|
||||
gen_recipe(recipe, fmt)
|
||||
fmt.line('_ => {},')
|
||||
# Allow for un-encoded ghost instructions.
|
||||
# Verifier checks the details.
|
||||
with fmt.indented('if encoding.is_legal() {', '}'):
|
||||
fmt.line('bad_encoding(func, inst);')
|
||||
|
||||
|
||||
def generate(isas, out_dir):
|
||||
# type: (Sequence[TargetISA], str) -> None
|
||||
for isa in isas:
|
||||
fmt = srcgen.Formatter()
|
||||
gen_isa(isa, fmt)
|
||||
fmt.update_file('binemit-{}.rs'.format(isa.name), out_dir)
|
||||
32
cranelift/codegen/meta-python/gen_build_deps.py
Normal file
32
cranelift/codegen/meta-python/gen_build_deps.py
Normal file
@@ -0,0 +1,32 @@
|
||||
"""
|
||||
Generate build dependencies for Cargo.
|
||||
|
||||
The `build.py` script is invoked by cargo when building cranelift-codegen to
|
||||
generate Rust code from the instruction descriptions. Cargo needs to know when
|
||||
it is necessary to rerun the build script.
|
||||
|
||||
If the build script outputs lines of the form:
|
||||
|
||||
cargo:rerun-if-changed=/path/to/file
|
||||
|
||||
cargo will rerun the build script when those files have changed since the last
|
||||
build.
|
||||
"""
|
||||
from __future__ import absolute_import, print_function
|
||||
import os
|
||||
from os.path import dirname, abspath, join
|
||||
|
||||
try:
|
||||
from typing import Iterable # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def generate():
|
||||
# type: () -> None
|
||||
print("Dependencies from meta language directory:")
|
||||
meta = dirname(abspath(__file__))
|
||||
for (dirpath, _, filenames) in os.walk(meta):
|
||||
for f in filenames:
|
||||
if f.endswith('.py'):
|
||||
print("cargo:rerun-if-changed=" + join(dirpath, f))
|
||||
902
cranelift/codegen/meta-python/gen_encoding.py
Normal file
902
cranelift/codegen/meta-python/gen_encoding.py
Normal file
@@ -0,0 +1,902 @@
|
||||
"""
|
||||
Generate sources for instruction encoding.
|
||||
|
||||
The tables and functions generated here support the `TargetISA::encode()`
|
||||
function which determines if a given instruction is legal, and if so, it's
|
||||
`Encoding` data which consists of a *recipe* and some *encoding* bits.
|
||||
|
||||
The `encode` function doesn't actually generate the binary machine bits. Each
|
||||
recipe has a corresponding hand-written function to do that after registers
|
||||
are allocated.
|
||||
|
||||
This is the information available to us:
|
||||
|
||||
- The instruction to be encoded as an `InstructionData` reference.
|
||||
- The controlling type variable.
|
||||
- The data-flow graph giving us access to the types of all values involved.
|
||||
This is needed for testing any secondary type variables.
|
||||
- A `PredicateView` reference for the ISA-specific settings for evaluating ISA
|
||||
predicates.
|
||||
- The currently active CPU mode is determined by the ISA.
|
||||
|
||||
## Level 1 table lookup
|
||||
|
||||
The CPU mode provides the first table. The key is the instruction's controlling
|
||||
type variable. If the instruction is not polymorphic, use `INVALID` for the
|
||||
type variable. The table values are level 2 tables.
|
||||
|
||||
## Level 2 table lookup
|
||||
|
||||
The level 2 table is keyed by the instruction's opcode. The table values are
|
||||
*encoding lists*.
|
||||
|
||||
The two-level table lookup allows the level 2 tables to be much smaller with
|
||||
good locality. Code in any given function usually only uses a few different
|
||||
types, so many of the level 2 tables will be cold.
|
||||
|
||||
## Encoding lists
|
||||
|
||||
An encoding list is a non-empty sequence of list entries. Each entry has
|
||||
one of these forms:
|
||||
|
||||
1. Recipe + bits. Use this encoding if the recipe predicate is satisfied.
|
||||
2. Recipe + bits, final entry. Use this encoding if the recipe predicate is
|
||||
satisfied. Otherwise, stop with the default legalization code.
|
||||
3. Stop with legalization code.
|
||||
4. Predicate + skip count. Test predicate and skip N entries if it is false.
|
||||
4. Predicate + stop. Test predicate and stop with the default legalization code
|
||||
if it is false.
|
||||
|
||||
The instruction predicate is also used to distinguish between polymorphic
|
||||
instructions with different types for secondary type variables.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
import srcgen
|
||||
from constant_hash import compute_quadratic
|
||||
from unique_table import UniqueSeqTable
|
||||
from collections import OrderedDict, defaultdict
|
||||
import math
|
||||
from itertools import groupby
|
||||
from cdsl.registers import RegClass, Register, Stack
|
||||
from cdsl.predicates import FieldPredicate, TypePredicate
|
||||
from cdsl.settings import SettingGroup
|
||||
from cdsl.formats import instruction_context, InstructionFormat
|
||||
|
||||
try:
|
||||
from typing import Sequence, Set, Tuple, List, Dict, Iterable, DefaultDict, TYPE_CHECKING # noqa
|
||||
if TYPE_CHECKING:
|
||||
from cdsl.isa import TargetISA, OperandConstraint, Encoding, CPUMode, EncRecipe, RecipePred # noqa
|
||||
from cdsl.predicates import PredNode, PredLeaf # noqa
|
||||
from cdsl.types import ValueType # noqa
|
||||
from cdsl.instructions import Instruction # noqa
|
||||
from cdsl.xform import XFormGroup # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def emit_instp(instp, fmt, has_func=False):
|
||||
# type: (PredNode, srcgen.Formatter, bool) -> None
|
||||
"""
|
||||
Emit code for matching an instruction predicate against an
|
||||
`InstructionData` reference called `inst`.
|
||||
|
||||
The generated code is an `if let` pattern match that falls through if the
|
||||
instruction has an unexpected format. This should lead to a panic.
|
||||
"""
|
||||
iform = instp.predicate_context()
|
||||
|
||||
# Deal with pure type check predicates which apply to any instruction.
|
||||
if iform == instruction_context:
|
||||
fmt.line('let args = inst.arguments(&func.dfg.value_lists);')
|
||||
fmt.line(instp.rust_predicate(0))
|
||||
return
|
||||
|
||||
assert isinstance(iform, InstructionFormat)
|
||||
|
||||
# Which fields do we need in the InstructionData pattern match?
|
||||
has_type_check = False
|
||||
# Collect the leaf predicates.
|
||||
leafs = set() # type: Set[PredLeaf]
|
||||
instp.predicate_leafs(leafs)
|
||||
# All the leafs are FieldPredicate or TypePredicate instances. Here we just
|
||||
# care about the field names.
|
||||
fnames = set() # type: Set[str]
|
||||
for p in leafs:
|
||||
if isinstance(p, FieldPredicate):
|
||||
fnames.add(p.field.rust_destructuring_name())
|
||||
else:
|
||||
assert isinstance(p, TypePredicate)
|
||||
has_type_check = True
|
||||
fields = ', '.join(sorted(fnames))
|
||||
|
||||
with fmt.indented(
|
||||
'if let crate::ir::InstructionData::{} {{ {}, .. }} = *inst {{'
|
||||
.format(iform.name, fields), '}'):
|
||||
if has_type_check:
|
||||
# We could implement this if we need to.
|
||||
assert has_func, "Recipe predicates can't check type variables."
|
||||
fmt.line('let args = inst.arguments(&func.dfg.value_lists);')
|
||||
elif has_func:
|
||||
# Silence dead argument warning.
|
||||
fmt.line('let _ = func;')
|
||||
fmt.format('return {};', instp.rust_predicate(0))
|
||||
fmt.line('unreachable!();')
|
||||
|
||||
|
||||
def emit_inst_predicates(instps, fmt):
|
||||
# type: (OrderedDict[PredNode, int], srcgen.Formatter) -> None
|
||||
"""
|
||||
Emit private functions for matching instruction predicates as well as a
|
||||
static `INST_PREDICATES` array indexed by predicate number.
|
||||
"""
|
||||
for instp, number in instps.items():
|
||||
name = 'inst_predicate_{}'.format(number)
|
||||
with fmt.indented(
|
||||
'fn {}(func: &crate::ir::Function, '
|
||||
'inst: &crate::ir::InstructionData)'
|
||||
'-> bool {{'.format(name), '}'):
|
||||
emit_instp(instp, fmt, has_func=True)
|
||||
|
||||
# Generate the static table.
|
||||
with fmt.indented(
|
||||
'pub static INST_PREDICATES: [InstPredicate; {}] = ['
|
||||
.format(len(instps)), '];'):
|
||||
for instp, number in instps.items():
|
||||
fmt.format('inst_predicate_{},', number)
|
||||
|
||||
|
||||
def emit_recipe_predicates(isa, fmt):
|
||||
# type: (TargetISA, srcgen.Formatter) -> None
|
||||
"""
|
||||
Emit private functions for checking recipe predicates as well as a static
|
||||
`RECIPE_PREDICATES` array indexed by recipe number.
|
||||
|
||||
A recipe predicate is a combination of an ISA predicate and an instruction
|
||||
predicates. Many recipes have identical predicates.
|
||||
"""
|
||||
# Table for uniquing recipe predicates. Maps predicate to generated
|
||||
# function name.
|
||||
pname = dict() # type: Dict[RecipePred, str]
|
||||
|
||||
# Generate unique recipe predicates.
|
||||
for rcp in isa.all_recipes:
|
||||
p = rcp.recipe_pred()
|
||||
if p is None or p in pname:
|
||||
continue
|
||||
name = 'recipe_predicate_{}'.format(rcp.name.lower())
|
||||
pname[p] = name
|
||||
isap, instp = p
|
||||
|
||||
# Generate the predicate function.
|
||||
with fmt.indented(
|
||||
'fn {}({}: crate::settings::PredicateView, '
|
||||
'{}: &ir::InstructionData) -> bool {{'
|
||||
.format(
|
||||
name,
|
||||
'isap' if isap else '_',
|
||||
'inst' if instp else '_'), '}'):
|
||||
if isap:
|
||||
n = isa.settings.predicate_number[isap]
|
||||
with fmt.indented('if !isap.test({}) {{'.format(n), '}'):
|
||||
fmt.line('return false;')
|
||||
if instp:
|
||||
emit_instp(instp, fmt)
|
||||
else:
|
||||
fmt.line('true')
|
||||
|
||||
# Generate the static table.
|
||||
with fmt.indented(
|
||||
'pub static RECIPE_PREDICATES: [RecipePredicate; {}] = ['
|
||||
.format(len(isa.all_recipes)), '];'):
|
||||
for rcp in isa.all_recipes:
|
||||
p = rcp.recipe_pred()
|
||||
if p is None:
|
||||
fmt.line('None,')
|
||||
else:
|
||||
fmt.format('Some({}),', pname[p])
|
||||
|
||||
|
||||
# The u16 values in an encoding list entry are interpreted as follows:
|
||||
#
|
||||
# NR = len(all_recipes)
|
||||
#
|
||||
# entry < 2*NR
|
||||
# Try Encoding(entry/2, next_entry) if the recipe predicate is satisfied.
|
||||
# If bit 0 is set, stop with the default legalization code.
|
||||
# If bit 0 is clear, keep going down the list.
|
||||
# entry < PRED_START
|
||||
# Stop with legalization code `entry - 2*NR`.
|
||||
#
|
||||
# Remaining entries are interpreted as (skip, pred) pairs, where:
|
||||
#
|
||||
# skip = (entry - PRED_START) >> PRED_BITS
|
||||
# pred = (entry - PRED_START) & PRED_MASK
|
||||
#
|
||||
# If the predicate is satisfied, keep going. Otherwise skip over the next
|
||||
# `skip` entries. If skip == 0, stop with the default legalization code.
|
||||
#
|
||||
# The `pred` predicate number is interpreted as an instruction predicate if it
|
||||
# is in range, otherwise an ISA predicate.
|
||||
|
||||
|
||||
class Encoder:
|
||||
"""
|
||||
Encoder for the list format above.
|
||||
|
||||
Two parameters are needed:
|
||||
|
||||
:param NR: Number of recipes.
|
||||
:param NI: Number of instruction predicates.
|
||||
"""
|
||||
|
||||
def __init__(self, isa):
|
||||
# type: (TargetISA) -> None
|
||||
self.isa = isa
|
||||
self.NR = len(isa.all_recipes)
|
||||
self.NI = len(isa.instp_number)
|
||||
# u16 encoding list words.
|
||||
self.words = list() # type: List[int]
|
||||
# Documentation comments: Index into `words` + comment.
|
||||
self.docs = list() # type: List[Tuple[int, str]]
|
||||
|
||||
# Encoding lists are represented as u16 arrays.
|
||||
CODE_BITS = 16
|
||||
|
||||
# Beginning of the predicate code words.
|
||||
PRED_START = 0x1000
|
||||
|
||||
# Number of bits used to hold a predicate number (instruction + ISA
|
||||
# predicates.
|
||||
PRED_BITS = 12
|
||||
|
||||
# Mask for extracting the predicate number.
|
||||
PRED_MASK = (1 << PRED_BITS) - 1
|
||||
|
||||
def max_skip(self):
|
||||
# type: () -> int
|
||||
"""The maximum number of entries that a predicate can skip."""
|
||||
return (1 << (self.CODE_BITS - self.PRED_BITS)) - 1
|
||||
|
||||
def recipe(self, enc, final):
|
||||
# type: (Encoding, bool) -> None
|
||||
"""Add a recipe+bits entry to the list."""
|
||||
offset = len(self.words)
|
||||
code = 2 * enc.recipe.number
|
||||
doc = '--> {}'.format(enc)
|
||||
if final:
|
||||
code += 1
|
||||
doc += ' and stop'
|
||||
|
||||
assert(code < self.PRED_START)
|
||||
self.words.extend((code, enc.encbits))
|
||||
self.docs.append((offset, doc))
|
||||
|
||||
def _pred(self, pred, skip, n):
|
||||
# type: (PredNode, int, int) -> None
|
||||
"""Add a predicate entry."""
|
||||
assert n <= self.PRED_MASK
|
||||
code = n | (skip << self.PRED_BITS)
|
||||
code += self.PRED_START
|
||||
assert code < (1 << self.CODE_BITS)
|
||||
|
||||
if skip == 0:
|
||||
doc = 'stop'
|
||||
else:
|
||||
doc = 'skip ' + str(skip)
|
||||
doc = '{} unless {}'.format(doc, pred)
|
||||
|
||||
self.docs.append((len(self.words), doc))
|
||||
self.words.append(code)
|
||||
|
||||
def instp(self, pred, skip):
|
||||
# type: (PredNode, int) -> None
|
||||
"""Add an instruction predicate entry."""
|
||||
number = self.isa.instp_number[pred]
|
||||
self._pred(pred, skip, number)
|
||||
|
||||
def isap(self, pred, skip):
|
||||
# type: (PredNode, int) -> None
|
||||
"""Add an ISA predicate entry."""
|
||||
n = self.isa.settings.predicate_number[pred]
|
||||
# ISA predicates follow the instruction predicates.
|
||||
self._pred(pred, skip, self.NI + n)
|
||||
|
||||
|
||||
class EncNode(object):
|
||||
"""
|
||||
An abstract node in the encoder tree for an instruction.
|
||||
|
||||
This tree is used to simplify the predicates guarding recipe+bits entries.
|
||||
"""
|
||||
|
||||
def size(self):
|
||||
# type: () -> int
|
||||
"""Get the number of list entries needed to encode this tree."""
|
||||
raise NotImplementedError('EncNode.size() is abstract')
|
||||
|
||||
def encode(self, encoder, final):
|
||||
# type: (Encoder, bool) -> None
|
||||
"""Encode this tree."""
|
||||
raise NotImplementedError('EncNode.encode() is abstract')
|
||||
|
||||
def optimize(self):
|
||||
# type: () -> EncNode
|
||||
"""Transform this encoder tree into something simpler."""
|
||||
return self
|
||||
|
||||
def predicate(self):
|
||||
# type: () -> PredNode
|
||||
"""Get the predicate guarding this tree, or `None` for always"""
|
||||
return None
|
||||
|
||||
|
||||
class EncPred(EncNode):
|
||||
"""
|
||||
An encoder tree node which asserts a predicate on its child nodes.
|
||||
|
||||
A `None` predicate is always satisfied.
|
||||
"""
|
||||
|
||||
def __init__(self, pred, children):
|
||||
# type: (PredNode, List[EncNode]) -> None
|
||||
self.pred = pred
|
||||
self.children = children
|
||||
|
||||
def size(self):
|
||||
# type: () -> int
|
||||
s = 1 if self.pred else 0
|
||||
s += sum(c.size() for c in self.children)
|
||||
return s
|
||||
|
||||
def encode(self, encoder, final):
|
||||
# type: (Encoder, bool) -> None
|
||||
if self.pred:
|
||||
skip = 0 if final else self.size() - 1
|
||||
ctx = self.pred.predicate_context()
|
||||
if isinstance(ctx, SettingGroup):
|
||||
encoder.isap(self.pred, skip)
|
||||
else:
|
||||
encoder.instp(self.pred, skip)
|
||||
|
||||
final_idx = len(self.children) - 1 if final else -1
|
||||
for idx, node in enumerate(self.children):
|
||||
node.encode(encoder, idx == final_idx)
|
||||
|
||||
def predicate(self):
|
||||
# type: () -> PredNode
|
||||
return self.pred
|
||||
|
||||
def optimize(self):
|
||||
# type: () -> EncNode
|
||||
"""
|
||||
Optimize a predicate node in the tree by combining child nodes that
|
||||
have identical predicates.
|
||||
"""
|
||||
cnodes = list() # type: List[EncNode]
|
||||
for pred, niter in groupby(
|
||||
map(lambda c: c.optimize(), self.children),
|
||||
key=lambda c: c.predicate()):
|
||||
nodes = list(niter)
|
||||
if pred is None or len(nodes) <= 1:
|
||||
cnodes.extend(nodes)
|
||||
continue
|
||||
|
||||
# We have multiple children with identical predicates.
|
||||
# Group them all into `n0`.
|
||||
n0 = nodes[0]
|
||||
assert isinstance(n0, EncPred)
|
||||
for n in nodes[1:]:
|
||||
assert isinstance(n, EncPred)
|
||||
n0.children.extend(n.children)
|
||||
|
||||
cnodes.append(n0)
|
||||
|
||||
# Finally strip a redundant grouping node.
|
||||
if self.pred is None and len(cnodes) == 1:
|
||||
return cnodes[0]
|
||||
else:
|
||||
self.children = cnodes
|
||||
return self
|
||||
|
||||
|
||||
class EncLeaf(EncNode):
|
||||
"""
|
||||
A leaf in the encoder tree.
|
||||
|
||||
This represents a single `Encoding`, without its predicates (they are
|
||||
represented in the tree by parent nodes.
|
||||
"""
|
||||
|
||||
def __init__(self, encoding):
|
||||
# type: (Encoding) -> None
|
||||
self.encoding = encoding
|
||||
|
||||
def size(self):
|
||||
# type: () -> int
|
||||
# recipe + bits.
|
||||
return 2
|
||||
|
||||
def encode(self, encoder, final):
|
||||
# type: (Encoder, bool) -> None
|
||||
encoder.recipe(self.encoding, final)
|
||||
|
||||
|
||||
class EncList(object):
|
||||
"""
|
||||
List of instructions for encoding a given type + opcode pair.
|
||||
|
||||
An encoding list contains a sequence of predicates and encoding recipes,
|
||||
all encoded as u16 values.
|
||||
|
||||
:param inst: The instruction opcode being encoded.
|
||||
:param ty: Value of the controlling type variable, or `None`.
|
||||
"""
|
||||
|
||||
def __init__(self, inst, ty):
|
||||
# type: (Instruction, ValueType) -> None
|
||||
self.inst = inst
|
||||
self.ty = ty
|
||||
# List of applicable Encoding instances.
|
||||
# These will have different predicates.
|
||||
self.encodings = [] # type: List[Encoding]
|
||||
|
||||
def name(self):
|
||||
# type: () -> str
|
||||
name = self.inst.name
|
||||
if self.ty:
|
||||
name = '{}.{}'.format(name, self.ty.name)
|
||||
if self.encodings:
|
||||
name += ' ({})'.format(self.encodings[0].cpumode)
|
||||
return name
|
||||
|
||||
def encoder_tree(self):
|
||||
# type: () -> EncNode
|
||||
"""
|
||||
Generate an optimized encoder tree for this list. The tree represents
|
||||
all of the encodings with parent nodes for the predicates that need
|
||||
checking.
|
||||
"""
|
||||
forest = list() # type: List[EncNode]
|
||||
for enc in self.encodings:
|
||||
n = EncLeaf(enc) # type: EncNode
|
||||
if enc.instp:
|
||||
n = EncPred(enc.instp, [n])
|
||||
if enc.isap:
|
||||
n = EncPred(enc.isap, [n])
|
||||
forest.append(n)
|
||||
|
||||
return EncPred(None, forest).optimize()
|
||||
|
||||
def encode(self, seq_table, doc_table, isa):
|
||||
# type: (UniqueSeqTable, DefaultDict[int, List[str]], TargetISA) -> None # noqa
|
||||
"""
|
||||
Encode this list as a sequence of u16 numbers.
|
||||
|
||||
Adds the sequence to `seq_table` and records the returned offset as
|
||||
`self.offset`.
|
||||
|
||||
Adds comment lines to `doc_table` keyed by seq_table offsets.
|
||||
"""
|
||||
# Use an encoder object to hold the parameters.
|
||||
encoder = Encoder(isa)
|
||||
tree = self.encoder_tree()
|
||||
tree.encode(encoder, True)
|
||||
|
||||
self.offset = seq_table.add(encoder.words)
|
||||
|
||||
# Add doc comments.
|
||||
doc_table[self.offset].append(
|
||||
'{:06x}: {}'.format(self.offset, self.name()))
|
||||
for pos, doc in encoder.docs:
|
||||
doc_table[self.offset + pos].append(doc)
|
||||
doc_table[self.offset + len(encoder.words)].insert(
|
||||
0, 'end of: {}'.format(self.name()))
|
||||
|
||||
|
||||
class Level2Table(object):
|
||||
"""
|
||||
Level 2 table mapping instruction opcodes to `EncList` objects.
|
||||
|
||||
A level 2 table can be completely empty if it only holds a custom
|
||||
legalization action for `ty`.
|
||||
|
||||
:param ty: Controlling type variable of all entries, or `None`.
|
||||
:param legalize: Default legalize action for `ty`.
|
||||
"""
|
||||
|
||||
def __init__(self, ty, legalize):
|
||||
# type: (ValueType, XFormGroup) -> None
|
||||
self.ty = ty
|
||||
self.legalize = legalize
|
||||
# Maps inst -> EncList
|
||||
self.lists = OrderedDict() # type: OrderedDict[Instruction, EncList]
|
||||
|
||||
def __getitem__(self, inst):
|
||||
# type: (Instruction) -> EncList
|
||||
ls = self.lists.get(inst)
|
||||
if not ls:
|
||||
ls = EncList(inst, self.ty)
|
||||
self.lists[inst] = ls
|
||||
return ls
|
||||
|
||||
def is_empty(self):
|
||||
# type: () -> bool
|
||||
"""
|
||||
Check if this level 2 table is completely empty.
|
||||
|
||||
This can happen if the associated type simply has an overridden
|
||||
legalize action.
|
||||
"""
|
||||
return len(self.lists) == 0
|
||||
|
||||
def enclists(self):
|
||||
# type: () -> Iterable[EncList]
|
||||
return iter(self.lists.values())
|
||||
|
||||
def layout_hashtable(self, level2_hashtables, level2_doc):
|
||||
# type: (List[EncList], DefaultDict[int, List[str]]) -> None
|
||||
"""
|
||||
Compute the hash table mapping opcode -> enclist.
|
||||
|
||||
Append the hash table to `level2_hashtables` and record the offset.
|
||||
"""
|
||||
def hash_func(enclist):
|
||||
# type: (EncList) -> int
|
||||
return enclist.inst.number
|
||||
hash_table = compute_quadratic(self.lists.values(), hash_func)
|
||||
|
||||
self.hash_table_offset = len(level2_hashtables)
|
||||
self.hash_table_len = len(hash_table)
|
||||
|
||||
level2_doc[self.hash_table_offset].append(
|
||||
'{:06x}: {}, {} entries'.format(
|
||||
self.hash_table_offset,
|
||||
self.ty,
|
||||
self.hash_table_len))
|
||||
level2_hashtables.extend(hash_table)
|
||||
|
||||
|
||||
class Level1Table(object):
|
||||
"""
|
||||
Level 1 table mapping types to `Level2` objects.
|
||||
"""
|
||||
|
||||
def __init__(self, cpumode):
|
||||
# type: (CPUMode) -> None
|
||||
self.cpumode = cpumode
|
||||
self.tables = OrderedDict() # type: OrderedDict[ValueType, Level2Table] # noqa
|
||||
|
||||
if cpumode.default_legalize is None:
|
||||
raise AssertionError(
|
||||
'CPU mode {}.{} needs a default legalize action'
|
||||
.format(cpumode.isa, cpumode))
|
||||
self.legalize_code = cpumode.isa.legalize_code(
|
||||
cpumode.default_legalize)
|
||||
|
||||
def __getitem__(self, ty):
|
||||
# type: (ValueType) -> Level2Table
|
||||
tbl = self.tables.get(ty)
|
||||
if not tbl:
|
||||
legalize = self.cpumode.get_legalize_action(ty)
|
||||
# Allocate a legalization code in a predictable order.
|
||||
self.cpumode.isa.legalize_code(legalize)
|
||||
tbl = Level2Table(ty, legalize)
|
||||
self.tables[ty] = tbl
|
||||
return tbl
|
||||
|
||||
def l2tables(self):
|
||||
# type: () -> Iterable[Level2Table]
|
||||
return (l2 for l2 in self.tables.values() if not l2.is_empty())
|
||||
|
||||
|
||||
def make_tables(cpumode):
|
||||
# type: (CPUMode) -> Level1Table
|
||||
"""
|
||||
Generate tables for `cpumode` as described above.
|
||||
"""
|
||||
table = Level1Table(cpumode)
|
||||
for enc in cpumode.encodings:
|
||||
ty = enc.ctrl_typevar()
|
||||
inst = enc.inst
|
||||
table[ty][inst].encodings.append(enc)
|
||||
|
||||
# Ensure there are level 1 table entries for all types with a custom
|
||||
# legalize action.
|
||||
for ty in cpumode.type_legalize.keys():
|
||||
table[ty]
|
||||
|
||||
return table
|
||||
|
||||
|
||||
def encode_enclists(level1, seq_table, doc_table, isa):
|
||||
# type: (Level1Table, UniqueSeqTable, DefaultDict[int, List[str]], TargetISA) -> None # noqa
|
||||
"""
|
||||
Compute encodings and doc comments for encoding lists in `level1`.
|
||||
"""
|
||||
for level2 in level1.l2tables():
|
||||
for enclist in level2.enclists():
|
||||
enclist.encode(seq_table, doc_table, isa)
|
||||
|
||||
|
||||
def emit_enclists(seq_table, doc_table, fmt):
|
||||
# type: (UniqueSeqTable, DefaultDict[int, List[str]], srcgen.Formatter) -> None # noqa
|
||||
with fmt.indented(
|
||||
'pub static ENCLISTS: [u16; {}] = ['.format(len(seq_table.table)),
|
||||
'];'):
|
||||
line = ''
|
||||
for idx, entry in enumerate(seq_table.table):
|
||||
if idx in doc_table:
|
||||
if line:
|
||||
fmt.line(line)
|
||||
line = ''
|
||||
for doc in doc_table[idx]:
|
||||
fmt.comment(doc)
|
||||
line += '{:#06x}, '.format(entry)
|
||||
if line:
|
||||
fmt.line(line)
|
||||
|
||||
|
||||
def encode_level2_hashtables(level1, level2_hashtables, level2_doc):
|
||||
# type: (Level1Table, List[EncList], DefaultDict[int, List[str]]) -> None
|
||||
for level2 in level1.l2tables():
|
||||
level2.layout_hashtable(level2_hashtables, level2_doc)
|
||||
|
||||
|
||||
def emit_level2_hashtables(level2_hashtables, offt, level2_doc, fmt):
|
||||
# type: (List[EncList], str, DefaultDict[int, List[str]], srcgen.Formatter) -> None # noqa
|
||||
"""
|
||||
Emit the big concatenation of level 2 hash tables.
|
||||
"""
|
||||
with fmt.indented(
|
||||
'pub static LEVEL2: [Level2Entry<{}>; {}] = ['
|
||||
.format(offt, len(level2_hashtables)),
|
||||
'];'):
|
||||
for offset, entry in enumerate(level2_hashtables):
|
||||
if offset in level2_doc:
|
||||
for doc in level2_doc[offset]:
|
||||
fmt.comment(doc)
|
||||
if entry:
|
||||
fmt.line(
|
||||
'Level2Entry ' +
|
||||
'{{ opcode: Some(crate::ir::Opcode::{}), '
|
||||
'offset: {:#08x} }},'
|
||||
.format(entry.inst.camel_name, entry.offset))
|
||||
else:
|
||||
fmt.line(
|
||||
'Level2Entry ' +
|
||||
'{ opcode: None, offset: 0 },')
|
||||
|
||||
|
||||
def emit_level1_hashtable(cpumode, level1, offt, fmt):
|
||||
# type: (CPUMode, Level1Table, str, srcgen.Formatter) -> None # noqa
|
||||
"""
|
||||
Emit a level 1 hash table for `cpumode`.
|
||||
"""
|
||||
def hash_func(level2):
|
||||
# type: (Level2Table) -> int
|
||||
return level2.ty.number if level2.ty is not None else 0
|
||||
hash_table = compute_quadratic(level1.tables.values(), hash_func)
|
||||
|
||||
with fmt.indented(
|
||||
'pub static LEVEL1_{}: [Level1Entry<{}>; {}] = ['
|
||||
.format(cpumode.name.upper(), offt, len(hash_table)), '];'):
|
||||
for level2 in hash_table:
|
||||
# Empty hash table entry. Include the default legalization action.
|
||||
if not level2:
|
||||
fmt.format(
|
||||
'Level1Entry {{ ty: crate::ir::types::INVALID, '
|
||||
'log2len: !0, offset: 0, legalize: {} }},',
|
||||
level1.legalize_code)
|
||||
continue
|
||||
|
||||
if level2.ty is not None:
|
||||
tyname = level2.ty.rust_name()
|
||||
else:
|
||||
tyname = 'crate::ir::types::INVALID'
|
||||
|
||||
lcode = cpumode.isa.legalize_code(level2.legalize)
|
||||
|
||||
# Empty level 2 table: Only a specialized legalization action, no
|
||||
# actual table.
|
||||
# Set an offset that is out of bounds, but make sure it doesn't
|
||||
# overflow its type when adding `1<<log2len`.
|
||||
if level2.is_empty():
|
||||
fmt.format(
|
||||
'Level1Entry {{ '
|
||||
'ty: {}, log2len: 0, offset: !0 - 1, '
|
||||
'legalize: {} }}, // {}',
|
||||
tyname, lcode, level2.legalize)
|
||||
continue
|
||||
|
||||
# Proper level 2 hash table.
|
||||
l2l = int(math.log(level2.hash_table_len, 2))
|
||||
assert l2l > 0, "Level2 hash table too small"
|
||||
fmt.format(
|
||||
'Level1Entry {{ '
|
||||
'ty: {}, log2len: {}, offset: {:#08x}, '
|
||||
'legalize: {} }}, // {}',
|
||||
tyname, l2l, level2.hash_table_offset,
|
||||
lcode, level2.legalize)
|
||||
|
||||
|
||||
def offset_type(length):
|
||||
# type: (int) -> str
|
||||
"""
|
||||
Compute an appropriate Rust integer type to use for offsets into a table of
|
||||
the given length.
|
||||
"""
|
||||
if length <= 0x10000:
|
||||
return 'u16'
|
||||
else:
|
||||
assert length <= 0x100000000, "Table too big"
|
||||
return 'u32'
|
||||
|
||||
|
||||
def emit_recipe_names(isa, fmt):
|
||||
# type: (TargetISA, srcgen.Formatter) -> None
|
||||
"""
|
||||
Emit a table of encoding recipe names keyed by recipe number.
|
||||
|
||||
This is used for pretty-printing encodings.
|
||||
"""
|
||||
with fmt.indented(
|
||||
'static RECIPE_NAMES: [&str; {}] = ['
|
||||
.format(len(isa.all_recipes)), '];'):
|
||||
for r in isa.all_recipes:
|
||||
fmt.line('"{}",'.format(r.name))
|
||||
|
||||
|
||||
def emit_recipe_constraints(isa, fmt):
|
||||
# type: (TargetISA, srcgen.Formatter) -> None
|
||||
"""
|
||||
Emit a table of encoding recipe operand constraints keyed by recipe number.
|
||||
|
||||
These are used by the register allocator to pick registers that can be
|
||||
properly encoded.
|
||||
"""
|
||||
with fmt.indented(
|
||||
'static RECIPE_CONSTRAINTS: [RecipeConstraints; {}] = ['
|
||||
.format(len(isa.all_recipes)), '];'):
|
||||
for r in isa.all_recipes:
|
||||
fmt.comment('Constraints for recipe {}:'.format(r.name))
|
||||
tied_i2o, tied_o2i = r.ties()
|
||||
fixed_ins, fixed_outs = r.fixed_ops()
|
||||
with fmt.indented('RecipeConstraints {', '},'):
|
||||
emit_operand_constraints(
|
||||
r, r.ins, 'ins', tied_i2o, fixed_outs, fmt)
|
||||
emit_operand_constraints(
|
||||
r, r.outs, 'outs', tied_o2i, fixed_ins, fmt)
|
||||
fmt.format('fixed_ins: {},', str(bool(fixed_ins)).lower())
|
||||
fmt.format('fixed_outs: {},', str(bool(fixed_outs)).lower())
|
||||
fmt.format('tied_ops: {},', str(bool(tied_i2o)).lower())
|
||||
fmt.format(
|
||||
'clobbers_flags: {},',
|
||||
str(bool(r.clobbers_flags)).lower())
|
||||
|
||||
|
||||
def emit_operand_constraints(
|
||||
recipe, # type: EncRecipe
|
||||
seq, # type: Sequence[OperandConstraint]
|
||||
field, # type: str
|
||||
tied, # type: Dict[int, int]
|
||||
fixops, # type: Set[Register]
|
||||
fmt # type: srcgen.Formatter
|
||||
):
|
||||
# type: (...) -> None
|
||||
"""
|
||||
Emit a struct field initializer for an array of operand constraints.
|
||||
|
||||
:param field: The name of the struct field to emit.
|
||||
:param tied: Map of tied opnums to counterparts.
|
||||
:param fix_ops: Set of fixed operands on the other side of the inst.
|
||||
"""
|
||||
if len(seq) == 0:
|
||||
fmt.line('{}: &[],'.format(field))
|
||||
return
|
||||
with fmt.indented('{}: &['.format(field), '],'):
|
||||
for n, cons in enumerate(seq):
|
||||
with fmt.indented('OperandConstraint {', '},'):
|
||||
if isinstance(cons, RegClass):
|
||||
if n in tied:
|
||||
fmt.format('kind: ConstraintKind::Tied({}),', tied[n])
|
||||
else:
|
||||
fmt.line('kind: ConstraintKind::Reg,')
|
||||
fmt.format('regclass: &{}_DATA,', cons)
|
||||
elif isinstance(cons, Register):
|
||||
assert n not in tied, "Can't tie fixed register operand"
|
||||
# See if this fixed register is also on the other side.
|
||||
t = 'FixedTied' if cons in fixops else 'FixedReg'
|
||||
fmt.format('kind: ConstraintKind::{}({}),', t, cons.unit)
|
||||
fmt.format('regclass: &{}_DATA,', cons.regclass)
|
||||
elif isinstance(cons, int):
|
||||
# This is a tied output constraint. It should never happen
|
||||
# for input constraints.
|
||||
assert cons == tied[n], "Invalid tied constraint"
|
||||
fmt.format('kind: ConstraintKind::Tied({}),', cons)
|
||||
fmt.format('regclass: &{}_DATA,', recipe.ins[cons])
|
||||
elif isinstance(cons, Stack):
|
||||
assert n not in tied, "Can't tie stack operand"
|
||||
fmt.line('kind: ConstraintKind::Stack,')
|
||||
fmt.format('regclass: &{}_DATA,', cons.regclass)
|
||||
else:
|
||||
raise AssertionError(
|
||||
'Unsupported constraint {}'.format(cons))
|
||||
|
||||
|
||||
def emit_recipe_sizing(isa, fmt):
|
||||
# type: (TargetISA, srcgen.Formatter) -> None
|
||||
"""
|
||||
Emit a table of encoding recipe code size information.
|
||||
"""
|
||||
with fmt.indented(
|
||||
'static RECIPE_SIZING: [RecipeSizing; {}] = ['
|
||||
.format(len(isa.all_recipes)), '];'):
|
||||
for r in isa.all_recipes:
|
||||
fmt.comment('Code size information for recipe {}:'.format(r.name))
|
||||
with fmt.indented('RecipeSizing {', '},'):
|
||||
fmt.format('base_size: {},', r.base_size)
|
||||
fmt.format('compute_size: {},', r.compute_size)
|
||||
if r.branch_range:
|
||||
fmt.format(
|
||||
'branch_range: '
|
||||
'Some(BranchRange {{ origin: {}, bits: {} }}),',
|
||||
*r.branch_range)
|
||||
else:
|
||||
fmt.line('branch_range: None,')
|
||||
|
||||
|
||||
def gen_isa(isa, fmt):
|
||||
# type: (TargetISA, srcgen.Formatter) -> None
|
||||
|
||||
# Make the `RECIPE_PREDICATES` table.
|
||||
emit_recipe_predicates(isa, fmt)
|
||||
|
||||
# Make the `INST_PREDICATES` table.
|
||||
emit_inst_predicates(isa.instp_number, fmt)
|
||||
|
||||
# Level1 tables, one per CPU mode
|
||||
level1_tables = dict()
|
||||
|
||||
# Tables for enclists with comments.
|
||||
seq_table = UniqueSeqTable()
|
||||
doc_table = defaultdict(list) # type: DefaultDict[int, List[str]]
|
||||
|
||||
# Single table containing all the level2 hash tables.
|
||||
level2_hashtables = list() # type: List[EncList]
|
||||
level2_doc = defaultdict(list) # type: DefaultDict[int, List[str]]
|
||||
|
||||
for cpumode in isa.cpumodes:
|
||||
level2_doc[len(level2_hashtables)].append(cpumode.name)
|
||||
level1 = make_tables(cpumode)
|
||||
level1_tables[cpumode] = level1
|
||||
encode_enclists(level1, seq_table, doc_table, isa)
|
||||
encode_level2_hashtables(level1, level2_hashtables, level2_doc)
|
||||
|
||||
# Level 1 table encodes offsets into the level 2 table.
|
||||
level1_offt = offset_type(len(level2_hashtables))
|
||||
# Level 2 tables encodes offsets into seq_table.
|
||||
level2_offt = offset_type(len(seq_table.table))
|
||||
|
||||
emit_enclists(seq_table, doc_table, fmt)
|
||||
emit_level2_hashtables(level2_hashtables, level2_offt, level2_doc, fmt)
|
||||
for cpumode in isa.cpumodes:
|
||||
emit_level1_hashtable(
|
||||
cpumode, level1_tables[cpumode], level1_offt, fmt)
|
||||
|
||||
emit_recipe_names(isa, fmt)
|
||||
emit_recipe_constraints(isa, fmt)
|
||||
emit_recipe_sizing(isa, fmt)
|
||||
|
||||
# Finally, tie it all together in an `EncInfo`.
|
||||
with fmt.indented('pub static INFO: isa::EncInfo = isa::EncInfo {', '};'):
|
||||
fmt.line('constraints: &RECIPE_CONSTRAINTS,')
|
||||
fmt.line('sizing: &RECIPE_SIZING,')
|
||||
fmt.line('names: &RECIPE_NAMES,')
|
||||
|
||||
|
||||
def generate(isas, out_dir):
|
||||
# type: (Sequence[TargetISA], str) -> None
|
||||
for isa in isas:
|
||||
fmt = srcgen.Formatter()
|
||||
gen_isa(isa, fmt)
|
||||
fmt.update_file('encoding-{}.rs'.format(isa.name), out_dir)
|
||||
812
cranelift/codegen/meta-python/gen_instr.py
Normal file
812
cranelift/codegen/meta-python/gen_instr.py
Normal file
@@ -0,0 +1,812 @@
|
||||
"""
|
||||
Generate sources with instruction info.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
import srcgen
|
||||
import constant_hash
|
||||
from unique_table import UniqueTable, UniqueSeqTable
|
||||
from cdsl import camel_case
|
||||
from cdsl.operands import ImmediateKind
|
||||
from cdsl.formats import InstructionFormat
|
||||
from cdsl.instructions import Instruction
|
||||
|
||||
# The typing module is only required by mypy, and we don't use these imports
|
||||
# outside type comments.
|
||||
try:
|
||||
from typing import List, Sequence, Set, TYPE_CHECKING # noqa
|
||||
if TYPE_CHECKING:
|
||||
from cdsl.isa import TargetISA # noqa
|
||||
from cdsl.instructions import InstructionGroup # noqa
|
||||
from cdsl.operands import Operand # noqa
|
||||
from cdsl.typevar import TypeVar # noqa
|
||||
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def gen_formats(fmt):
|
||||
# type: (srcgen.Formatter) -> None
|
||||
"""Generate an instruction format enumeration"""
|
||||
|
||||
fmt.doc_comment('''
|
||||
An instruction format
|
||||
|
||||
Every opcode has a corresponding instruction format
|
||||
which is represented by both the `InstructionFormat`
|
||||
and the `InstructionData` enums.
|
||||
''')
|
||||
fmt.line('#[derive(Copy, Clone, PartialEq, Eq, Debug)]')
|
||||
with fmt.indented('pub enum InstructionFormat {', '}'):
|
||||
for f in InstructionFormat.all_formats:
|
||||
fmt.doc_comment(str(f))
|
||||
fmt.line(f.name + ',')
|
||||
fmt.line()
|
||||
|
||||
# Emit a From<InstructionData> which also serves to verify that
|
||||
# InstructionFormat and InstructionData are in sync.
|
||||
with fmt.indented(
|
||||
"impl<'a> From<&'a InstructionData> for InstructionFormat {", '}'):
|
||||
with fmt.indented(
|
||||
"fn from(inst: &'a InstructionData) -> Self {",
|
||||
'}'):
|
||||
m = srcgen.Match('*inst')
|
||||
for f in InstructionFormat.all_formats:
|
||||
m.arm('InstructionData::' + f.name, ['..'],
|
||||
'InstructionFormat::' + f.name)
|
||||
fmt.match(m)
|
||||
fmt.line()
|
||||
|
||||
|
||||
def gen_arguments_method(fmt, is_mut):
|
||||
# type: (srcgen.Formatter, bool) -> None
|
||||
method = 'arguments'
|
||||
mut = ''
|
||||
rslice = 'ref_slice'
|
||||
as_slice = 'as_slice'
|
||||
if is_mut:
|
||||
method += '_mut'
|
||||
mut = 'mut '
|
||||
rslice += '_mut'
|
||||
as_slice = 'as_mut_slice'
|
||||
|
||||
with fmt.indented(
|
||||
'pub fn {f}<\'a>(&\'a {m}self, '
|
||||
'pool: &\'a {m}ir::ValueListPool) -> '
|
||||
'&{m}[Value] {{'
|
||||
.format(f=method, m=mut), '}'):
|
||||
m = srcgen.Match('*self')
|
||||
for f in InstructionFormat.all_formats:
|
||||
n = 'InstructionData::' + f.name
|
||||
|
||||
# Formats with a value list put all of their arguments in the
|
||||
# list. We don't split them up, just return it all as variable
|
||||
# arguments. (I expect the distinction to go away).
|
||||
if f.has_value_list:
|
||||
m.arm(n, ['ref {}args'.format(mut), '..'],
|
||||
'args.{}(pool)'.format(as_slice))
|
||||
continue
|
||||
|
||||
# Fixed args.
|
||||
fields = []
|
||||
if f.num_value_operands == 0:
|
||||
arg = '&{}[]'.format(mut)
|
||||
elif f.num_value_operands == 1:
|
||||
fields.append('ref {}arg'.format(mut))
|
||||
arg = '{}(arg)'.format(rslice)
|
||||
else:
|
||||
args = 'args_arity{}'.format(f.num_value_operands)
|
||||
fields.append('args: ref {}{}'.format(mut, args))
|
||||
arg = args
|
||||
fields.append('..')
|
||||
m.arm(n, fields, arg)
|
||||
fmt.match(m)
|
||||
|
||||
|
||||
def gen_instruction_data(fmt):
|
||||
# type: (srcgen.Formatter) -> None
|
||||
"""
|
||||
Generate the InstructionData enum.
|
||||
|
||||
Every variant must contain an `opcode` field. The size of `InstructionData`
|
||||
should be kept at 16 bytes on 64-bit architectures. If more space is needed
|
||||
to represent an instruction, use a `Box<AuxData>` to store the additional
|
||||
information out of line.
|
||||
"""
|
||||
|
||||
fmt.line('#[derive(Clone, Debug)]')
|
||||
fmt.line('#[allow(missing_docs)]')
|
||||
with fmt.indented('pub enum InstructionData {', '}'):
|
||||
for f in InstructionFormat.all_formats:
|
||||
with fmt.indented('{} {{'.format(f.name), '},'):
|
||||
fmt.line('opcode: Opcode,')
|
||||
if f.typevar_operand is None:
|
||||
pass
|
||||
elif f.has_value_list:
|
||||
fmt.line('args: ValueList,')
|
||||
elif f.num_value_operands == 1:
|
||||
fmt.line('arg: Value,')
|
||||
else:
|
||||
fmt.line('args: [Value; {}],'.format(f.num_value_operands))
|
||||
for field in f.imm_fields:
|
||||
fmt.line(
|
||||
'{}: {},'
|
||||
.format(field.member, field.kind.rust_type))
|
||||
|
||||
|
||||
def gen_instruction_data_impl(fmt):
|
||||
# type: (srcgen.Formatter) -> None
|
||||
"""
|
||||
Generate the boring parts of the InstructionData implementation.
|
||||
|
||||
These methods in `impl InstructionData` can be generated automatically from
|
||||
the instruction formats:
|
||||
|
||||
- `pub fn opcode(&self) -> Opcode`
|
||||
- `pub fn arguments(&self, &pool) -> &[Value]`
|
||||
- `pub fn arguments_mut(&mut self, &pool) -> &mut [Value]`
|
||||
- `pub fn take_value_list(&mut self) -> Option<ir::ValueList>`
|
||||
- `pub fn put_value_list(&mut self, args: ir::ValueList>`
|
||||
- `pub fn eq(&self, &other: Self, &pool) -> bool`
|
||||
- `pub fn hash<H: Hasher>(&self, state: &mut H, &pool)`
|
||||
"""
|
||||
|
||||
# The `opcode` method simply reads the `opcode` members. This is really a
|
||||
# workaround for Rust's enum types missing shared members.
|
||||
with fmt.indented('impl InstructionData {', '}'):
|
||||
fmt.doc_comment('Get the opcode of this instruction.')
|
||||
with fmt.indented('pub fn opcode(&self) -> Opcode {', '}'):
|
||||
m = srcgen.Match('*self')
|
||||
for f in InstructionFormat.all_formats:
|
||||
m.arm('InstructionData::' + f.name, ['opcode', '..'],
|
||||
'opcode')
|
||||
fmt.match(m)
|
||||
fmt.line()
|
||||
|
||||
fmt.doc_comment('Get the controlling type variable operand.')
|
||||
with fmt.indented(
|
||||
'pub fn typevar_operand(&self, pool: &ir::ValueListPool) -> '
|
||||
'Option<Value> {', '}'):
|
||||
m = srcgen.Match('*self')
|
||||
for f in InstructionFormat.all_formats:
|
||||
n = 'InstructionData::' + f.name
|
||||
if f.typevar_operand is None:
|
||||
m.arm(n, ['..'], 'None')
|
||||
elif f.has_value_list:
|
||||
# We keep all arguments in a value list.
|
||||
i = f.typevar_operand
|
||||
m.arm(n, ['ref args', '..'],
|
||||
'args.get({}, pool)'.format(i))
|
||||
elif f.num_value_operands == 1:
|
||||
# We have a single value operand called 'arg'.
|
||||
m.arm(n, ['arg', '..'], 'Some(arg)')
|
||||
else:
|
||||
# We have multiple value operands and an array `args`.
|
||||
# Which `args` index to use?
|
||||
args = 'args_arity{}'.format(f.num_value_operands)
|
||||
m.arm(n, ['args: ref {}'.format(args), '..'],
|
||||
'Some({}[{}])'.format(args, f.typevar_operand))
|
||||
fmt.match(m)
|
||||
fmt.line()
|
||||
|
||||
fmt.doc_comment(
|
||||
"""
|
||||
Get the value arguments to this instruction.
|
||||
""")
|
||||
gen_arguments_method(fmt, False)
|
||||
fmt.line()
|
||||
|
||||
fmt.doc_comment(
|
||||
"""
|
||||
Get mutable references to the value arguments to this
|
||||
instruction.
|
||||
""")
|
||||
gen_arguments_method(fmt, True)
|
||||
fmt.line()
|
||||
|
||||
fmt.doc_comment(
|
||||
"""
|
||||
Take out the value list with all the value arguments and return
|
||||
it.
|
||||
|
||||
This leaves the value list in the instruction empty. Use
|
||||
`put_value_list` to put the value list back.
|
||||
""")
|
||||
with fmt.indented(
|
||||
'pub fn take_value_list(&mut self) -> Option<ir::ValueList> {',
|
||||
'}'):
|
||||
m = srcgen.Match('*self')
|
||||
for f in InstructionFormat.all_formats:
|
||||
n = 'InstructionData::' + f.name
|
||||
if f.has_value_list:
|
||||
m.arm(n, ['ref mut args', '..'], 'Some(args.take())')
|
||||
m.arm('_', [], 'None')
|
||||
fmt.match(m)
|
||||
fmt.line()
|
||||
|
||||
fmt.doc_comment(
|
||||
"""
|
||||
Put back a value list.
|
||||
|
||||
After removing a value list with `take_value_list()`, use this
|
||||
method to put it back. It is required that this instruction has
|
||||
a format that accepts a value list, and that the existing value
|
||||
list is empty. This avoids leaking list pool memory.
|
||||
""")
|
||||
with fmt.indented(
|
||||
'pub fn put_value_list(&mut self, vlist: ir::ValueList) {',
|
||||
'}'):
|
||||
with fmt.indented('let args = match *self {', '};'):
|
||||
for f in InstructionFormat.all_formats:
|
||||
n = 'InstructionData::' + f.name
|
||||
if f.has_value_list:
|
||||
fmt.line(n + ' { ref mut args, .. } => args,')
|
||||
fmt.line('_ => panic!("No value list: {:?}", self),')
|
||||
fmt.line(
|
||||
'debug_assert!(args.is_empty(), "Value list already in use");')
|
||||
fmt.line('*args = vlist;')
|
||||
fmt.line()
|
||||
|
||||
fmt.doc_comment(
|
||||
"""
|
||||
Compare two `InstructionData` for equality.
|
||||
|
||||
This operation requires a reference to a `ValueListPool` to
|
||||
determine if the contents of any `ValueLists` are equal.
|
||||
""")
|
||||
with fmt.indented(
|
||||
'pub fn eq(&self, other: &Self, pool: &ir::ValueListPool)'
|
||||
' -> bool {',
|
||||
'}'):
|
||||
with fmt.indented('if ::core::mem::discriminant(self) != '
|
||||
'::core::mem::discriminant(other) {', '}'):
|
||||
fmt.line('return false;')
|
||||
with fmt.indented('match (self, other) {', '}'):
|
||||
for f in InstructionFormat.all_formats:
|
||||
n = '&InstructionData::' + f.name
|
||||
members = ['opcode']
|
||||
if f.typevar_operand is None:
|
||||
args_eq = None
|
||||
elif f.has_value_list:
|
||||
members.append('args')
|
||||
args_eq = 'args1.as_slice(pool) == ' \
|
||||
'args2.as_slice(pool)'
|
||||
elif f.num_value_operands == 1:
|
||||
members.append('arg')
|
||||
args_eq = 'arg1 == arg2'
|
||||
else:
|
||||
members.append('args')
|
||||
args_eq = 'args1 == args2'
|
||||
for field in f.imm_fields:
|
||||
members.append(field.member)
|
||||
pat1 = ', '.join('{}: ref {}1'.format(x, x)
|
||||
for x in members)
|
||||
pat2 = ', '.join('{}: ref {}2'.format(x, x)
|
||||
for x in members)
|
||||
with fmt.indented('({} {{ {} }}, {} {{ {} }}) => {{'
|
||||
.format(n, pat1, n, pat2), '}'):
|
||||
fmt.line('opcode1 == opcode2')
|
||||
for field in f.imm_fields:
|
||||
fmt.line('&& {}1 == {}2'
|
||||
.format(field.member, field.member))
|
||||
if args_eq is not None:
|
||||
fmt.line('&& {}'.format(args_eq))
|
||||
fmt.line('_ => unreachable!()')
|
||||
fmt.line()
|
||||
|
||||
fmt.doc_comment(
|
||||
"""
|
||||
Hash an `InstructionData`.
|
||||
|
||||
This operation requires a reference to a `ValueListPool` to
|
||||
hash the contents of any `ValueLists`.
|
||||
""")
|
||||
with fmt.indented(
|
||||
'pub fn hash<H: ::core::hash::Hasher>'
|
||||
'(&self, state: &mut H, pool: &ir::ValueListPool) {',
|
||||
'}'):
|
||||
with fmt.indented('match *self {', '}'):
|
||||
for f in InstructionFormat.all_formats:
|
||||
n = 'InstructionData::' + f.name
|
||||
members = ['opcode']
|
||||
if f.typevar_operand is None:
|
||||
args = '&()'
|
||||
elif f.has_value_list:
|
||||
members.append('ref args')
|
||||
args = 'args.as_slice(pool)'
|
||||
elif f.num_value_operands == 1:
|
||||
members.append('ref arg')
|
||||
args = 'arg'
|
||||
else:
|
||||
members.append('ref args')
|
||||
args = 'args'
|
||||
for field in f.imm_fields:
|
||||
members.append(field.member)
|
||||
pat = n + ' { ' + ', '.join(members) + ' }'
|
||||
with fmt.indented(pat + ' => {', '}'):
|
||||
fmt.line('::core::hash::Hash::hash( '
|
||||
'&::core::mem::discriminant(self), state);')
|
||||
fmt.line('::core::hash::Hash::hash(&opcode, state);')
|
||||
for field in f.imm_fields:
|
||||
fmt.line('::core::hash::Hash::hash(&{}, state);'
|
||||
.format(field.member))
|
||||
fmt.line('::core::hash::Hash::hash({}, state);'
|
||||
.format(args))
|
||||
|
||||
|
||||
def collect_instr_groups(isas):
|
||||
# type: (Sequence[TargetISA]) -> List[InstructionGroup]
|
||||
seen = set() # type: Set[InstructionGroup]
|
||||
groups = []
|
||||
for isa in isas:
|
||||
for g in isa.instruction_groups:
|
||||
if g not in seen:
|
||||
groups.append(g)
|
||||
seen.add(g)
|
||||
return groups
|
||||
|
||||
|
||||
def gen_opcodes(groups, fmt):
|
||||
# type: (Sequence[InstructionGroup], srcgen.Formatter) -> Sequence[Instruction] # noqa
|
||||
"""
|
||||
Generate opcode enumerations.
|
||||
|
||||
Return a list of all instructions.
|
||||
"""
|
||||
|
||||
fmt.doc_comment('''
|
||||
An instruction opcode.
|
||||
|
||||
All instructions from all supported ISAs are present.
|
||||
''')
|
||||
fmt.line('#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]')
|
||||
instrs = []
|
||||
|
||||
# We explicitly set the discriminant of the first variant to 1, which
|
||||
# allows us to take advantage of the NonZero optimization, meaning that
|
||||
# wrapping enums can use the 0 discriminant instead of increasing the size
|
||||
# if the whole type, and so SIZEOF(Option<Opcode>>) == SIZEOF(Opcode)
|
||||
is_first_opcode = True
|
||||
with fmt.indented('pub enum Opcode {', '}'):
|
||||
for g in groups:
|
||||
for i in g.instructions:
|
||||
instrs.append(i)
|
||||
i.number = len(instrs)
|
||||
fmt.doc_comment('`{}`. ({})'.format(i, i.format.name))
|
||||
# Document polymorphism.
|
||||
if i.is_polymorphic:
|
||||
if i.use_typevar_operand:
|
||||
opnum = i.value_opnums[i.format.typevar_operand]
|
||||
fmt.doc_comment(
|
||||
'Type inferred from {}.'
|
||||
.format(i.ins[opnum]))
|
||||
# Enum variant itself.
|
||||
if is_first_opcode:
|
||||
fmt.line(i.camel_name + ' = 1,')
|
||||
is_first_opcode = False
|
||||
else:
|
||||
fmt.line(i.camel_name + ',')
|
||||
fmt.line()
|
||||
|
||||
with fmt.indented('impl Opcode {', '}'):
|
||||
for attr in sorted(Instruction.ATTRIBS.keys()):
|
||||
fmt.doc_comment(Instruction.ATTRIBS[attr])
|
||||
with fmt.indented('pub fn {}(self) -> bool {{'
|
||||
.format(attr), '}'):
|
||||
m = srcgen.Match('self')
|
||||
for i in instrs:
|
||||
if getattr(i, attr):
|
||||
m.arm('Opcode::' + i.camel_name, [], 'true')
|
||||
m.arm('_', [], 'false')
|
||||
fmt.match(m)
|
||||
fmt.line()
|
||||
fmt.line()
|
||||
|
||||
# Generate a private opcode_format table.
|
||||
with fmt.indented(
|
||||
'const OPCODE_FORMAT: [InstructionFormat; {}] = ['
|
||||
.format(len(instrs)),
|
||||
'];'):
|
||||
for i in instrs:
|
||||
fmt.format(
|
||||
'InstructionFormat::{}, // {}',
|
||||
i.format.name, i.name)
|
||||
fmt.line()
|
||||
|
||||
# Generate a private opcode_name function.
|
||||
with fmt.indented('fn opcode_name(opc: Opcode) -> &\'static str {', '}'):
|
||||
m = srcgen.Match('opc')
|
||||
for i in instrs:
|
||||
m.arm('Opcode::' + i.camel_name, [], '"{}"'.format(i.name))
|
||||
fmt.match(m)
|
||||
fmt.line()
|
||||
|
||||
# Generate an opcode hash table for looking up opcodes by name.
|
||||
hash_table = constant_hash.compute_quadratic(
|
||||
instrs,
|
||||
lambda i: constant_hash.simple_hash(i.name))
|
||||
with fmt.indented(
|
||||
'const OPCODE_HASH_TABLE: [Option<Opcode>; {}] = ['
|
||||
.format(len(hash_table)), '];'):
|
||||
for i in hash_table:
|
||||
if i is None:
|
||||
fmt.line('None,')
|
||||
else:
|
||||
fmt.format('Some(Opcode::{}),', i.camel_name)
|
||||
fmt.line()
|
||||
return instrs
|
||||
|
||||
|
||||
def get_constraint(op, ctrl_typevar, type_sets):
|
||||
# type: (Operand, TypeVar, UniqueTable) -> str
|
||||
"""
|
||||
Get the value type constraint for an SSA value operand, where
|
||||
`ctrl_typevar` is the controlling type variable.
|
||||
|
||||
Each operand constraint is represented as a string, one of:
|
||||
|
||||
- `Concrete(vt)`, where `vt` is a value type name.
|
||||
- `Free(idx)` where `idx` is an index into `type_sets`.
|
||||
- `Same`, `Lane`, `AsBool` for controlling typevar-derived constraints.
|
||||
"""
|
||||
assert op.is_value()
|
||||
tv = op.typevar
|
||||
|
||||
# A concrete value type.
|
||||
if tv.singleton_type():
|
||||
return 'Concrete({})'.format(tv.singleton_type().rust_name())
|
||||
|
||||
if tv.free_typevar() is not ctrl_typevar:
|
||||
assert not tv.is_derived
|
||||
return 'Free({})'.format(type_sets.add(tv.type_set))
|
||||
|
||||
if tv.is_derived:
|
||||
assert tv.base is ctrl_typevar, "Not derived from ctrl_typevar"
|
||||
return camel_case(tv.derived_func)
|
||||
|
||||
assert tv is ctrl_typevar
|
||||
return 'Same'
|
||||
|
||||
|
||||
# TypeSet indexes are encoded in 8 bits, with `0xff` reserved.
|
||||
typeset_limit = 0xff
|
||||
|
||||
|
||||
def gen_typesets_table(fmt, type_sets):
|
||||
# type: (srcgen.Formatter, UniqueTable) -> None
|
||||
"""
|
||||
Generate the table of ValueTypeSets described by type_sets.
|
||||
"""
|
||||
if len(type_sets.table) == 0:
|
||||
return
|
||||
fmt.comment('Table of value type sets.')
|
||||
assert len(type_sets.table) <= typeset_limit, "Too many type sets"
|
||||
with fmt.indented(
|
||||
'const TYPE_SETS: [ir::instructions::ValueTypeSet; {}] = ['
|
||||
.format(len(type_sets.table)), '];'):
|
||||
for ts in type_sets.table:
|
||||
with fmt.indented('ir::instructions::ValueTypeSet {', '},'):
|
||||
ts.emit_fields(fmt)
|
||||
|
||||
|
||||
def gen_type_constraints(fmt, instrs):
|
||||
# type: (srcgen.Formatter, Sequence[Instruction]) -> None
|
||||
"""
|
||||
Generate value type constraints for all instructions.
|
||||
|
||||
- Emit a compact constant table of ValueTypeSet objects.
|
||||
- Emit a compact constant table of OperandConstraint objects.
|
||||
- Emit an opcode-indexed table of instruction constraints.
|
||||
|
||||
"""
|
||||
|
||||
# Table of TypeSet instances.
|
||||
type_sets = UniqueTable()
|
||||
|
||||
# Table of operand constraint sequences (as tuples). Each operand
|
||||
# constraint is represented as a string, one of:
|
||||
# - `Concrete(vt)`, where `vt` is a value type name.
|
||||
# - `Free(idx)` where `idx` isan index into `type_sets`.
|
||||
# - `Same`, `Lane`, `AsBool` for controlling typevar-derived constraints.
|
||||
operand_seqs = UniqueSeqTable()
|
||||
|
||||
# Preload table with constraints for typical binops.
|
||||
operand_seqs.add(['Same'] * 3)
|
||||
|
||||
fmt.comment('Table of opcode constraints.')
|
||||
with fmt.indented(
|
||||
'const OPCODE_CONSTRAINTS: [OpcodeConstraints; {}] = ['
|
||||
.format(len(instrs)), '];'):
|
||||
for i in instrs:
|
||||
# Collect constraints for the value results, not including
|
||||
# `variable_args` results which are always special cased.
|
||||
constraints = list()
|
||||
ctrl_typevar = None
|
||||
ctrl_typeset = typeset_limit
|
||||
if i.is_polymorphic:
|
||||
ctrl_typevar = i.ctrl_typevar
|
||||
ctrl_typeset = type_sets.add(ctrl_typevar.type_set)
|
||||
for idx in i.value_results:
|
||||
constraints.append(
|
||||
get_constraint(i.outs[idx], ctrl_typevar, type_sets))
|
||||
for opnum in i.value_opnums:
|
||||
constraints.append(
|
||||
get_constraint(i.ins[opnum], ctrl_typevar, type_sets))
|
||||
offset = operand_seqs.add(constraints)
|
||||
fixed_results = len(i.value_results)
|
||||
fixed_values = len(i.value_opnums)
|
||||
# Can the controlling type variable be inferred from the designated
|
||||
# operand?
|
||||
use_typevar_operand = i.is_polymorphic and i.use_typevar_operand
|
||||
# Can the controlling type variable be inferred from the result?
|
||||
use_result = (fixed_results > 0 and
|
||||
i.outs[i.value_results[0]].typevar == ctrl_typevar)
|
||||
# Are we required to use the designated operand instead of the
|
||||
# result?
|
||||
requires_typevar_operand = use_typevar_operand and not use_result
|
||||
fmt.comment(
|
||||
'{}: fixed_results={}, use_typevar_operand={}, '
|
||||
'requires_typevar_operand={}, fixed_values={}'
|
||||
.format(i.camel_name, fixed_results, use_typevar_operand,
|
||||
requires_typevar_operand, fixed_values))
|
||||
fmt.comment('Constraints={}'.format(constraints))
|
||||
if i.is_polymorphic:
|
||||
fmt.comment(
|
||||
'Polymorphic over {}'.format(ctrl_typevar.type_set))
|
||||
# Compute the bit field encoding, c.f. instructions.rs.
|
||||
assert fixed_results < 8, "Bit field encoding too tight"
|
||||
flags = fixed_results
|
||||
if use_typevar_operand:
|
||||
flags |= 8
|
||||
if requires_typevar_operand:
|
||||
flags |= 0x10
|
||||
assert fixed_values < 8, "Bit field encoding too tight"
|
||||
flags |= fixed_values << 5
|
||||
|
||||
with fmt.indented('OpcodeConstraints {', '},'):
|
||||
fmt.line('flags: {:#04x},'.format(flags))
|
||||
fmt.line('typeset_offset: {},'.format(ctrl_typeset))
|
||||
fmt.line('constraint_offset: {},'.format(offset))
|
||||
fmt.line()
|
||||
|
||||
gen_typesets_table(fmt, type_sets)
|
||||
fmt.line()
|
||||
|
||||
fmt.comment('Table of operand constraint sequences.')
|
||||
with fmt.indented(
|
||||
'const OPERAND_CONSTRAINTS: [OperandConstraint; {}] = ['
|
||||
.format(len(operand_seqs.table)), '];'):
|
||||
for c in operand_seqs.table:
|
||||
fmt.line('OperandConstraint::{},'.format(c))
|
||||
|
||||
|
||||
def gen_format_constructor(iform, fmt):
|
||||
# type: (InstructionFormat, srcgen.Formatter) -> None
|
||||
"""
|
||||
Emit a method for creating and inserting an `iform` instruction, where
|
||||
`iform` is an instruction format.
|
||||
|
||||
All instruction formats take an `opcode` argument and a `ctrl_typevar`
|
||||
argument for deducing the result types.
|
||||
"""
|
||||
|
||||
# Construct method arguments.
|
||||
args = ['self', 'opcode: Opcode', 'ctrl_typevar: Type']
|
||||
|
||||
# Normal operand arguments. Start with the immediate operands.
|
||||
for f in iform.imm_fields:
|
||||
args.append('{}: {}'.format(f.member, f.kind.rust_type))
|
||||
# Then the value operands.
|
||||
if iform.has_value_list:
|
||||
# Take all value arguments as a finished value list. The value lists
|
||||
# are created by the individual instruction constructors.
|
||||
args.append('args: ir::ValueList')
|
||||
else:
|
||||
# Take a fixed number of value operands.
|
||||
for i in range(iform.num_value_operands):
|
||||
args.append('arg{}: Value'.format(i))
|
||||
|
||||
proto = '{}({})'.format(iform.name, ', '.join(args))
|
||||
proto += " -> (Inst, &'f mut ir::DataFlowGraph)"
|
||||
|
||||
fmt.doc_comment(str(iform))
|
||||
fmt.line('#[allow(non_snake_case)]')
|
||||
with fmt.indented('fn {} {{'.format(proto), '}'):
|
||||
# Generate the instruction data.
|
||||
with fmt.indented(
|
||||
'let data = ir::InstructionData::{} {{'.format(iform.name),
|
||||
'};'):
|
||||
fmt.line('opcode,')
|
||||
gen_member_inits(iform, fmt)
|
||||
|
||||
fmt.line('self.build(data, ctrl_typevar)')
|
||||
|
||||
|
||||
def gen_member_inits(iform, fmt):
|
||||
# type: (InstructionFormat, srcgen.Formatter) -> None
|
||||
"""
|
||||
Emit member initializers for an `iform` instruction.
|
||||
"""
|
||||
|
||||
# Immediate operands.
|
||||
# We have local variables with the same names as the members.
|
||||
for f in iform.imm_fields:
|
||||
fmt.line('{},'.format(f.member))
|
||||
|
||||
# Value operands.
|
||||
if iform.has_value_list:
|
||||
fmt.line('args,')
|
||||
elif iform.num_value_operands == 1:
|
||||
fmt.line('arg: arg0,')
|
||||
elif iform.num_value_operands > 1:
|
||||
args = ('arg{}'.format(i) for i in range(iform.num_value_operands))
|
||||
fmt.line('args: [{}],'.format(', '.join(args)))
|
||||
|
||||
|
||||
def gen_inst_builder(inst, fmt):
|
||||
# type: (Instruction, srcgen.Formatter) -> None
|
||||
"""
|
||||
Emit a method for generating the instruction `inst`.
|
||||
|
||||
The method will create and insert an instruction, then return the result
|
||||
values, or the instruction reference itself for instructions that don't
|
||||
have results.
|
||||
"""
|
||||
|
||||
# Construct method arguments.
|
||||
if inst.format.has_value_list:
|
||||
args = ['mut self']
|
||||
else:
|
||||
args = ['self']
|
||||
|
||||
# The controlling type variable will be inferred from the input values if
|
||||
# possible. Otherwise, it is the first method argument.
|
||||
if inst.is_polymorphic and not inst.use_typevar_operand:
|
||||
args.append('{}: crate::ir::Type'.format(inst.ctrl_typevar.name))
|
||||
|
||||
tmpl_types = list() # type: List[str]
|
||||
into_args = list() # type: List[str]
|
||||
for op in inst.ins:
|
||||
if isinstance(op.kind, ImmediateKind):
|
||||
t = 'T{}{}'.format(1 + len(tmpl_types), op.kind.name)
|
||||
tmpl_types.append('{}: Into<{}>'.format(t, op.kind.rust_type))
|
||||
into_args.append(op.name)
|
||||
else:
|
||||
t = op.kind.rust_type
|
||||
args.append('{}: {}'.format(op.name, t))
|
||||
|
||||
# Return the inst reference for result-less instructions.
|
||||
if len(inst.value_results) == 0:
|
||||
rtype = 'Inst'
|
||||
elif len(inst.value_results) == 1:
|
||||
rtype = 'Value'
|
||||
else:
|
||||
rvals = ', '.join(len(inst.value_results) * ['Value'])
|
||||
rtype = '({})'.format(rvals)
|
||||
|
||||
if len(tmpl_types) > 0:
|
||||
tmpl = '<{}>'.format(', '.join(tmpl_types))
|
||||
else:
|
||||
tmpl = ''
|
||||
proto = '{}{}({}) -> {}'.format(
|
||||
inst.snake_name(), tmpl, ', '.join(args), rtype)
|
||||
|
||||
fmt.doc_comment('`{}`\n\n{}'.format(inst, inst.blurb()))
|
||||
fmt.line('#[allow(non_snake_case)]')
|
||||
with fmt.indented('fn {} {{'.format(proto), '}'):
|
||||
# Convert all of the `Into<>` arguments.
|
||||
for arg in into_args:
|
||||
fmt.line('let {} = {}.into();'.format(arg, arg))
|
||||
|
||||
# Arguments for instruction constructor.
|
||||
args = ['Opcode::' + inst.camel_name]
|
||||
|
||||
if inst.is_polymorphic and not inst.use_typevar_operand:
|
||||
# This was an explicit method argument.
|
||||
args.append(inst.ctrl_typevar.name)
|
||||
elif not inst.is_polymorphic:
|
||||
# No controlling type variable needed.
|
||||
args.append('types::INVALID')
|
||||
else:
|
||||
assert inst.is_polymorphic and inst.use_typevar_operand
|
||||
# Infer the controlling type variable from the input operands.
|
||||
opnum = inst.value_opnums[inst.format.typevar_operand]
|
||||
fmt.line(
|
||||
'let ctrl_typevar = self.data_flow_graph().value_type({});'
|
||||
.format(inst.ins[opnum].name))
|
||||
# The format constructor will resolve the result types from the
|
||||
# type var.
|
||||
args.append('ctrl_typevar')
|
||||
|
||||
# Now add all of the immediate operands to the constructor arguments.
|
||||
for opnum in inst.imm_opnums:
|
||||
args.append(inst.ins[opnum].name)
|
||||
|
||||
# Finally, the value operands.
|
||||
if inst.format.has_value_list:
|
||||
# We need to build a value list with all the arguments.
|
||||
fmt.line('let mut vlist = ir::ValueList::default();')
|
||||
args.append('vlist')
|
||||
with fmt.indented('{', '}'):
|
||||
fmt.line(
|
||||
'let pool = '
|
||||
'&mut self.data_flow_graph_mut().value_lists;')
|
||||
for op in inst.ins:
|
||||
if op.is_value():
|
||||
fmt.line('vlist.push({}, pool);'.format(op.name))
|
||||
elif op.is_varargs():
|
||||
fmt.line(
|
||||
'vlist.extend({}.iter().cloned(), pool);'
|
||||
.format(op.name))
|
||||
else:
|
||||
# With no value list, we're guaranteed to just have a set of fixed
|
||||
# value operands.
|
||||
for opnum in inst.value_opnums:
|
||||
args.append(inst.ins[opnum].name)
|
||||
|
||||
# Call to the format constructor,
|
||||
fcall = 'self.{}({})'.format(inst.format.name, ', '.join(args))
|
||||
|
||||
if len(inst.value_results) == 0:
|
||||
fmt.line(fcall + '.0')
|
||||
return
|
||||
|
||||
fmt.line('let (inst, dfg) = {};'.format(fcall))
|
||||
|
||||
if len(inst.value_results) == 1:
|
||||
fmt.line('dfg.first_result(inst)')
|
||||
return
|
||||
|
||||
fmt.format(
|
||||
'let results = &dfg.inst_results(inst)[0..{}];',
|
||||
len(inst.value_results))
|
||||
fmt.format('({})', ', '.join(
|
||||
'results[{}]'.format(i) for i in range(len(inst.value_results))))
|
||||
|
||||
|
||||
def gen_builder(insts, fmt):
|
||||
# type: (Sequence[Instruction], srcgen.Formatter) -> None
|
||||
"""
|
||||
Generate a Builder trait with methods for all instructions.
|
||||
"""
|
||||
fmt.doc_comment("""
|
||||
Convenience methods for building instructions.
|
||||
|
||||
The `InstBuilder` trait has one method per instruction opcode for
|
||||
conveniently constructing the instruction with minimum arguments.
|
||||
Polymorphic instructions infer their result types from the input
|
||||
arguments when possible. In some cases, an explicit `ctrl_typevar`
|
||||
argument is required.
|
||||
|
||||
The opcode methods return the new instruction's result values, or
|
||||
the `Inst` itself for instructions that don't have any results.
|
||||
|
||||
There is also a method per instruction format. These methods all
|
||||
return an `Inst`.
|
||||
""")
|
||||
with fmt.indented(
|
||||
"pub trait InstBuilder<'f>: InstBuilderBase<'f> {", '}'):
|
||||
for inst in insts:
|
||||
gen_inst_builder(inst, fmt)
|
||||
for f in InstructionFormat.all_formats:
|
||||
gen_format_constructor(f, fmt)
|
||||
|
||||
|
||||
def generate(isas, out_dir):
|
||||
# type: (Sequence[TargetISA], str) -> None
|
||||
groups = collect_instr_groups(isas)
|
||||
|
||||
# opcodes.rs
|
||||
fmt = srcgen.Formatter()
|
||||
gen_formats(fmt)
|
||||
gen_instruction_data(fmt)
|
||||
fmt.line()
|
||||
gen_instruction_data_impl(fmt)
|
||||
fmt.line()
|
||||
instrs = gen_opcodes(groups, fmt)
|
||||
gen_type_constraints(fmt, instrs)
|
||||
fmt.update_file('opcodes.rs', out_dir)
|
||||
|
||||
# inst_builder.rs
|
||||
fmt = srcgen.Formatter()
|
||||
gen_builder(instrs, fmt)
|
||||
fmt.update_file('inst_builder.rs', out_dir)
|
||||
448
cranelift/codegen/meta-python/gen_legalizer.py
Normal file
448
cranelift/codegen/meta-python/gen_legalizer.py
Normal file
@@ -0,0 +1,448 @@
|
||||
"""
|
||||
Generate legalizer transformations.
|
||||
|
||||
The transformations defined in the `cranelift.legalize` module are all of the
|
||||
macro-expansion form where the input pattern is a single instruction. We
|
||||
generate a Rust function for each `XFormGroup` which takes a `Cursor` pointing
|
||||
at the instruction to be legalized. The expanded destination pattern replaces
|
||||
the input instruction.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from srcgen import Formatter
|
||||
from collections import defaultdict
|
||||
from base import instructions
|
||||
from cdsl.ast import Var
|
||||
from cdsl.ti import ti_rtl, TypeEnv, get_type_env, TypesEqual,\
|
||||
InTypeset, WiderOrEq
|
||||
from unique_table import UniqueTable
|
||||
from gen_instr import gen_typesets_table
|
||||
from cdsl.typevar import TypeVar
|
||||
|
||||
try:
|
||||
from typing import Sequence, List, Dict, Set, DefaultDict # noqa
|
||||
from cdsl.isa import TargetISA # noqa
|
||||
from cdsl.ast import Def, VarAtomMap # noqa
|
||||
from cdsl.xform import XForm, XFormGroup # noqa
|
||||
from cdsl.typevar import TypeSet # noqa
|
||||
from cdsl.ti import TypeConstraint # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def get_runtime_typechecks(xform):
|
||||
# type: (XForm) -> List[TypeConstraint]
|
||||
"""
|
||||
Given a XForm build a list of runtime type checks necessary to determine
|
||||
if it applies. We have 2 types of runtime checks:
|
||||
1) typevar tv belongs to typeset T - needed for free tvs whose
|
||||
typeset is constrained by their use in the dst pattern
|
||||
|
||||
2) tv1 == tv2 where tv1 and tv2 are derived TVs - caused by unification
|
||||
of non-bijective functions
|
||||
"""
|
||||
check_l = [] # type: List[TypeConstraint]
|
||||
|
||||
# 1) Perform ti only on the source RTL. Accumulate any free tvs that have a
|
||||
# different inferred type in src, compared to the type inferred for both
|
||||
# src and dst.
|
||||
symtab = {} # type: VarAtomMap
|
||||
src_copy = xform.src.copy(symtab)
|
||||
src_typenv = get_type_env(ti_rtl(src_copy, TypeEnv()))
|
||||
|
||||
for v in xform.ti.vars:
|
||||
if not v.has_free_typevar():
|
||||
continue
|
||||
|
||||
# In rust the local variable containing a free TV associated with var v
|
||||
# has name typeof_v. We rely on the python TVs having the same name.
|
||||
assert "typeof_{}".format(v) == xform.ti[v].name
|
||||
|
||||
if v not in symtab:
|
||||
# We can have singleton vars defined only on dst. Ignore them
|
||||
assert v.get_typevar().singleton_type() is not None
|
||||
continue
|
||||
|
||||
inner_v = symtab[v]
|
||||
assert isinstance(inner_v, Var)
|
||||
src_ts = src_typenv[inner_v].get_typeset()
|
||||
xform_ts = xform.ti[v].get_typeset()
|
||||
|
||||
assert xform_ts.issubset(src_ts)
|
||||
if src_ts != xform_ts:
|
||||
check_l.append(InTypeset(xform.ti[v], xform_ts))
|
||||
|
||||
# 2,3) Add any constraints that appear in xform.ti
|
||||
check_l.extend(xform.ti.constraints)
|
||||
|
||||
return check_l
|
||||
|
||||
|
||||
def emit_runtime_typecheck(check, fmt, type_sets):
|
||||
# type: (TypeConstraint, Formatter, UniqueTable) -> None
|
||||
"""
|
||||
Emit rust code for the given check.
|
||||
|
||||
The emitted code is a statement redefining the `predicate` variable like
|
||||
this:
|
||||
|
||||
let predicate = predicate && ...
|
||||
"""
|
||||
def build_derived_expr(tv):
|
||||
# type: (TypeVar) -> str
|
||||
"""
|
||||
Build an expression of type Option<Type> corresponding to a concrete
|
||||
type transformed by the sequence of derivation functions in tv.
|
||||
|
||||
We are using Option<Type>, as some constraints may cause an
|
||||
over/underflow on patterns that do not match them. We want to capture
|
||||
this without panicking at runtime.
|
||||
"""
|
||||
if not tv.is_derived:
|
||||
assert tv.name.startswith('typeof_')
|
||||
return "Some({})".format(tv.name)
|
||||
|
||||
base_exp = build_derived_expr(tv.base)
|
||||
if (tv.derived_func == TypeVar.LANEOF):
|
||||
return "{}.map(|t: crate::ir::Type| t.lane_type())"\
|
||||
.format(base_exp)
|
||||
elif (tv.derived_func == TypeVar.ASBOOL):
|
||||
return "{}.map(|t: crate::ir::Type| t.as_bool())".format(base_exp)
|
||||
elif (tv.derived_func == TypeVar.HALFWIDTH):
|
||||
return "{}.and_then(|t: crate::ir::Type| t.half_width())"\
|
||||
.format(base_exp)
|
||||
elif (tv.derived_func == TypeVar.DOUBLEWIDTH):
|
||||
return "{}.and_then(|t: crate::ir::Type| t.double_width())"\
|
||||
.format(base_exp)
|
||||
elif (tv.derived_func == TypeVar.HALFVECTOR):
|
||||
return "{}.and_then(|t: crate::ir::Type| t.half_vector())"\
|
||||
.format(base_exp)
|
||||
elif (tv.derived_func == TypeVar.DOUBLEVECTOR):
|
||||
return "{}.and_then(|t: crate::ir::Type| t.by(2))".format(base_exp)
|
||||
else:
|
||||
assert False, "Unknown derived function {}".format(tv.derived_func)
|
||||
|
||||
if (isinstance(check, InTypeset)):
|
||||
assert not check.tv.is_derived
|
||||
tv = check.tv.name
|
||||
if check.ts not in type_sets.index:
|
||||
type_sets.add(check.ts)
|
||||
ts = type_sets.index[check.ts]
|
||||
fmt.comment("{} must belong to {}".format(tv, check.ts))
|
||||
fmt.format(
|
||||
'let predicate = predicate && TYPE_SETS[{}].contains({});',
|
||||
ts, tv)
|
||||
elif (isinstance(check, TypesEqual)):
|
||||
with fmt.indented(
|
||||
'let predicate = predicate && match ({}, {}) {{'
|
||||
.format(build_derived_expr(check.tv1),
|
||||
build_derived_expr(check.tv2)), '};'):
|
||||
fmt.line('(Some(a), Some(b)) => a == b,')
|
||||
fmt.comment('On overflow, constraint doesn\'t appply')
|
||||
fmt.line('_ => false,')
|
||||
elif (isinstance(check, WiderOrEq)):
|
||||
with fmt.indented(
|
||||
'let predicate = predicate && match ({}, {}) {{'
|
||||
.format(build_derived_expr(check.tv1),
|
||||
build_derived_expr(check.tv2)), '};'):
|
||||
fmt.line('(Some(a), Some(b)) => a.wider_or_equal(b),')
|
||||
fmt.comment('On overflow, constraint doesn\'t appply')
|
||||
fmt.line('_ => false,')
|
||||
else:
|
||||
assert False, "Unknown check {}".format(check)
|
||||
|
||||
|
||||
def unwrap_inst(iref, node, fmt):
|
||||
# type: (str, Def, Formatter) -> bool
|
||||
"""
|
||||
Given a `Def` node, emit code that extracts all the instruction fields from
|
||||
`pos.func.dfg[iref]`.
|
||||
|
||||
Create local variables named after the `Var` instances in `node`.
|
||||
|
||||
Also create a local variable named `predicate` with the value of the
|
||||
evaluated instruction predicate, or `true` if the node has no predicate.
|
||||
|
||||
:param iref: Name of the `Inst` reference to unwrap.
|
||||
:param node: `Def` node providing variable names.
|
||||
:returns: True if the instruction arguments were not detached, expecting a
|
||||
replacement instruction to overwrite the original.
|
||||
"""
|
||||
fmt.comment('Unwrap {}'.format(node))
|
||||
expr = node.expr
|
||||
iform = expr.inst.format
|
||||
nvops = iform.num_value_operands
|
||||
|
||||
# The tuple of locals to extract is the `Var` instances in `expr.args`.
|
||||
arg_names = tuple(
|
||||
arg.name if isinstance(arg, Var) else '_' for arg in expr.args)
|
||||
with fmt.indented(
|
||||
'let ({}, predicate) = if let crate::ir::InstructionData::{} {{'
|
||||
.format(', '.join(map(str, arg_names)), iform.name), '};'):
|
||||
# Fields are encoded directly.
|
||||
for f in iform.imm_fields:
|
||||
fmt.line('{},'.format(f.member))
|
||||
if nvops == 1:
|
||||
fmt.line('arg,')
|
||||
elif iform.has_value_list or nvops > 1:
|
||||
fmt.line('ref args,')
|
||||
fmt.line('..')
|
||||
fmt.outdented_line('} = pos.func.dfg[inst] {')
|
||||
fmt.line('let func = &pos.func;')
|
||||
if iform.has_value_list:
|
||||
fmt.line('let args = args.as_slice(&func.dfg.value_lists);')
|
||||
elif nvops == 1:
|
||||
fmt.line('let args = [arg];')
|
||||
# Generate the values for the tuple.
|
||||
with fmt.indented('(', ')'):
|
||||
for opnum, op in enumerate(expr.inst.ins):
|
||||
if op.is_immediate():
|
||||
n = expr.inst.imm_opnums.index(opnum)
|
||||
fmt.format('{},', iform.imm_fields[n].member)
|
||||
elif op.is_value():
|
||||
n = expr.inst.value_opnums.index(opnum)
|
||||
fmt.format('func.dfg.resolve_aliases(args[{}]),', n)
|
||||
# Evaluate the instruction predicate, if any.
|
||||
instp = expr.inst_predicate_with_ctrl_typevar()
|
||||
fmt.line(instp.rust_predicate(0) if instp else 'true')
|
||||
fmt.outdented_line('} else {')
|
||||
fmt.line('unreachable!("bad instruction format")')
|
||||
|
||||
# Get the types of any variables where it is needed.
|
||||
for opnum in expr.inst.value_opnums:
|
||||
v = expr.args[opnum]
|
||||
if isinstance(v, Var) and v.has_free_typevar():
|
||||
fmt.format('let typeof_{0} = pos.func.dfg.value_type({0});', v)
|
||||
|
||||
# If the node has results, detach the values.
|
||||
# Place the values in locals.
|
||||
replace_inst = False
|
||||
if len(node.defs) > 0:
|
||||
if node.defs == node.defs[0].dst_def.defs:
|
||||
# Special case: The instruction replacing node defines the exact
|
||||
# same values.
|
||||
fmt.comment(
|
||||
'Results handled by {}.'
|
||||
.format(node.defs[0].dst_def))
|
||||
replace_inst = True
|
||||
else:
|
||||
# Boring case: Detach the result values, capture them in locals.
|
||||
for d in node.defs:
|
||||
fmt.line('let {};'.format(d))
|
||||
with fmt.indented('{', '}'):
|
||||
fmt.line('let r = pos.func.dfg.inst_results(inst);')
|
||||
for i in range(len(node.defs)):
|
||||
fmt.line('{} = r[{}];'.format(node.defs[i], i))
|
||||
for d in node.defs:
|
||||
if d.has_free_typevar():
|
||||
fmt.line(
|
||||
'let typeof_{0} = pos.func.dfg.value_type({0});'
|
||||
.format(d))
|
||||
|
||||
return replace_inst
|
||||
|
||||
|
||||
def wrap_tup(seq):
|
||||
# type: (Sequence[object]) -> str
|
||||
tup = tuple(map(str, seq))
|
||||
if len(tup) == 1:
|
||||
return tup[0]
|
||||
else:
|
||||
return '({})'.format(', '.join(tup))
|
||||
|
||||
|
||||
def is_value_split(node):
|
||||
# type: (Def) -> bool
|
||||
"""
|
||||
Determine if `node` represents one of the value splitting instructions:
|
||||
`isplit` or `vsplit. These instructions are lowered specially by the
|
||||
`legalize::split` module.
|
||||
"""
|
||||
if len(node.defs) != 2:
|
||||
return False
|
||||
return node.expr.inst in (instructions.isplit, instructions.vsplit)
|
||||
|
||||
|
||||
def emit_dst_inst(node, fmt):
|
||||
# type: (Def, Formatter) -> None
|
||||
replaced_inst = None # type: str
|
||||
|
||||
if is_value_split(node):
|
||||
# Split instructions are not emitted with the builder, but by calling
|
||||
# special functions in the `legalizer::split` module. These functions
|
||||
# will eliminate concat-split patterns.
|
||||
fmt.line('let curpos = pos.position();')
|
||||
fmt.line('let srcloc = pos.srcloc();')
|
||||
fmt.format(
|
||||
'let {} = split::{}(pos.func, cfg, curpos, srcloc, {});',
|
||||
wrap_tup(node.defs),
|
||||
node.expr.inst.snake_name(),
|
||||
node.expr.args[0])
|
||||
else:
|
||||
if len(node.defs) == 0:
|
||||
# This node doesn't define any values, so just insert the new
|
||||
# instruction.
|
||||
builder = 'pos.ins()'
|
||||
else:
|
||||
src_def0 = node.defs[0].src_def
|
||||
if src_def0 and node.defs == src_def0.defs:
|
||||
# The replacement instruction defines the exact same values as
|
||||
# the source pattern. Unwrapping would have left the results
|
||||
# intact.
|
||||
# Replace the whole instruction.
|
||||
builder = 'let {} = pos.func.dfg.replace(inst)'.format(
|
||||
wrap_tup(node.defs))
|
||||
replaced_inst = 'inst'
|
||||
else:
|
||||
# Insert a new instruction.
|
||||
builder = 'let {} = pos.ins()'.format(wrap_tup(node.defs))
|
||||
# We may want to reuse some of the detached output values.
|
||||
if len(node.defs) == 1 and node.defs[0].is_output():
|
||||
# Reuse the single source result value.
|
||||
builder += '.with_result({})'.format(node.defs[0])
|
||||
elif any(d.is_output() for d in node.defs):
|
||||
# We have some output values to be reused.
|
||||
array = ', '.join(
|
||||
('Some({})'.format(d) if d.is_output()
|
||||
else 'None')
|
||||
for d in node.defs)
|
||||
builder += '.with_results([{}])'.format(array)
|
||||
|
||||
fmt.line('{}.{};'.format(builder, node.expr.rust_builder(node.defs)))
|
||||
|
||||
# If we just replaced an instruction, we need to bump the cursor so
|
||||
# following instructions are inserted *after* the replaced instruction.
|
||||
if replaced_inst:
|
||||
with fmt.indented(
|
||||
'if pos.current_inst() == Some({}) {{'
|
||||
.format(replaced_inst), '}'):
|
||||
fmt.line('pos.next_inst();')
|
||||
|
||||
|
||||
def gen_xform(xform, fmt, type_sets):
|
||||
# type: (XForm, Formatter, UniqueTable) -> None
|
||||
"""
|
||||
Emit code for `xform`, assuming that the opcode of xform's root instruction
|
||||
has already been matched.
|
||||
|
||||
`inst: Inst` is the variable to be replaced. It is pointed to by `pos:
|
||||
Cursor`.
|
||||
`dfg: DataFlowGraph` is available and mutable.
|
||||
"""
|
||||
# Unwrap the source instruction, create local variables for the input
|
||||
# variables.
|
||||
replace_inst = unwrap_inst('inst', xform.src.rtl[0], fmt)
|
||||
|
||||
# Emit any runtime checks.
|
||||
# These will rebind `predicate` emitted by unwrap_inst().
|
||||
for check in get_runtime_typechecks(xform):
|
||||
emit_runtime_typecheck(check, fmt, type_sets)
|
||||
|
||||
# Guard the actual expansion by `predicate`.
|
||||
with fmt.indented('if predicate {', '}'):
|
||||
# If we're going to delete `inst`, we need to detach its results first
|
||||
# so they can be reattached during pattern expansion.
|
||||
if not replace_inst:
|
||||
fmt.line('pos.func.dfg.clear_results(inst);')
|
||||
|
||||
# Emit the destination pattern.
|
||||
for dst in xform.dst.rtl:
|
||||
emit_dst_inst(dst, fmt)
|
||||
|
||||
# Delete the original instruction if we didn't have an opportunity to
|
||||
# replace it.
|
||||
if not replace_inst:
|
||||
fmt.line('let removed = pos.remove_inst();')
|
||||
fmt.line('debug_assert_eq!(removed, inst);')
|
||||
fmt.line('return true;')
|
||||
|
||||
|
||||
def gen_xform_group(xgrp, fmt, type_sets):
|
||||
# type: (XFormGroup, Formatter, UniqueTable) -> None
|
||||
fmt.doc_comment("Legalize `inst`.")
|
||||
fmt.line('#[allow(unused_variables,unused_assignments,non_snake_case)]')
|
||||
with fmt.indented('pub fn {}('.format(xgrp.name)):
|
||||
fmt.line('inst: crate::ir::Inst,')
|
||||
fmt.line('func: &mut crate::ir::Function,')
|
||||
fmt.line('cfg: &mut crate::flowgraph::ControlFlowGraph,')
|
||||
fmt.line('isa: &crate::isa::TargetIsa,')
|
||||
with fmt.indented(') -> bool {', '}'):
|
||||
fmt.line('use crate::ir::InstBuilder;')
|
||||
fmt.line('use crate::cursor::{Cursor, FuncCursor};')
|
||||
fmt.line('let mut pos = FuncCursor::new(func).at_inst(inst);')
|
||||
fmt.line('pos.use_srcloc(inst);')
|
||||
|
||||
# Group the xforms by opcode so we can generate a big switch.
|
||||
# Preserve ordering.
|
||||
xforms = defaultdict(list) # type: DefaultDict[str, List[XForm]]
|
||||
for xform in xgrp.xforms:
|
||||
inst = xform.src.rtl[0].expr.inst
|
||||
xforms[inst.camel_name].append(xform)
|
||||
|
||||
with fmt.indented('{', '}'):
|
||||
with fmt.indented('match pos.func.dfg[inst].opcode() {', '}'):
|
||||
for camel_name in sorted(xforms.keys()):
|
||||
with fmt.indented(
|
||||
'ir::Opcode::{} => {{'.format(camel_name), '}'):
|
||||
for xform in xforms[camel_name]:
|
||||
gen_xform(xform, fmt, type_sets)
|
||||
|
||||
# Emit the custom transforms. The Rust compiler will complain
|
||||
# about any overlap with the normal xforms.
|
||||
for inst, funcname in xgrp.custom.items():
|
||||
with fmt.indented(
|
||||
'ir::Opcode::{} => {{'
|
||||
.format(inst.camel_name), '}'):
|
||||
fmt.format('{}(inst, pos.func, cfg, isa);', funcname)
|
||||
fmt.line('return true;')
|
||||
|
||||
# We'll assume there are uncovered opcodes.
|
||||
fmt.line('_ => {},')
|
||||
|
||||
# If we fall through, nothing was expanded. Call the chain if any.
|
||||
if xgrp.chain:
|
||||
fmt.format('{}(inst, pos.func, cfg, isa)', xgrp.chain.rust_name())
|
||||
else:
|
||||
fmt.line('false')
|
||||
|
||||
|
||||
def gen_isa(isa, fmt, shared_groups):
|
||||
# type: (TargetISA, Formatter, Set[XFormGroup]) -> None
|
||||
"""
|
||||
Generate legalization functions for `isa` and add any shared `XFormGroup`s
|
||||
encountered to `shared_groups`.
|
||||
|
||||
Generate `TYPE_SETS` and `LEGALIZE_ACTION` tables.
|
||||
"""
|
||||
type_sets = UniqueTable()
|
||||
for xgrp in isa.legalize_codes.keys():
|
||||
if xgrp.isa is None:
|
||||
shared_groups.add(xgrp)
|
||||
else:
|
||||
assert xgrp.isa == isa
|
||||
gen_xform_group(xgrp, fmt, type_sets)
|
||||
|
||||
gen_typesets_table(fmt, type_sets)
|
||||
|
||||
with fmt.indented(
|
||||
'pub static LEGALIZE_ACTIONS: [isa::Legalize; {}] = ['
|
||||
.format(len(isa.legalize_codes)), '];'):
|
||||
for xgrp in isa.legalize_codes.keys():
|
||||
fmt.format('{},', xgrp.rust_name())
|
||||
|
||||
|
||||
def generate(isas, out_dir):
|
||||
# type: (Sequence[TargetISA], str) -> None
|
||||
shared_groups = set() # type: Set[XFormGroup]
|
||||
|
||||
for isa in isas:
|
||||
fmt = Formatter()
|
||||
gen_isa(isa, fmt, shared_groups)
|
||||
fmt.update_file('legalize-{}.rs'.format(isa.name), out_dir)
|
||||
|
||||
# Shared xform groups.
|
||||
fmt = Formatter()
|
||||
type_sets = UniqueTable()
|
||||
for xgrp in sorted(shared_groups, key=lambda g: g.name):
|
||||
gen_xform_group(xgrp, fmt, type_sets)
|
||||
gen_typesets_table(fmt, type_sets)
|
||||
fmt.update_file('legalizer.rs', out_dir)
|
||||
335
cranelift/codegen/meta-python/gen_settings.py
Normal file
335
cranelift/codegen/meta-python/gen_settings.py
Normal file
@@ -0,0 +1,335 @@
|
||||
"""
|
||||
Generate sources with settings.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
import srcgen
|
||||
from unique_table import UniqueSeqTable
|
||||
import constant_hash
|
||||
from cdsl import camel_case
|
||||
from cdsl.settings import BoolSetting, NumSetting, EnumSetting
|
||||
from base import settings
|
||||
|
||||
try:
|
||||
from typing import Sequence, Set, Tuple, List, Union, TYPE_CHECKING # noqa
|
||||
if TYPE_CHECKING:
|
||||
from cdsl.isa import TargetISA # noqa
|
||||
from cdsl.settings import Setting, Preset, SettingGroup # noqa
|
||||
from cdsl.predicates import Predicate, PredContext # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def gen_to_and_from_str(ty, values, fmt):
|
||||
# type: (str, Tuple[str, ...], srcgen.Formatter) -> None
|
||||
"""
|
||||
Emit Display and FromStr implementations for enum settings.
|
||||
"""
|
||||
with fmt.indented('impl fmt::Display for {} {{'.format(ty), '}'):
|
||||
with fmt.indented(
|
||||
'fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {',
|
||||
'}'):
|
||||
with fmt.indented('f.write_str(match *self {', '})'):
|
||||
for v in values:
|
||||
fmt.line('{}::{} => "{}",'
|
||||
.format(ty, camel_case(v), v))
|
||||
|
||||
with fmt.indented('impl str::FromStr for {} {{'.format(ty), '}'):
|
||||
fmt.line('type Err = ();')
|
||||
with fmt.indented(
|
||||
'fn from_str(s: &str) -> Result<Self, Self::Err> {',
|
||||
'}'):
|
||||
with fmt.indented('match s {', '}'):
|
||||
for v in values:
|
||||
fmt.line('"{}" => Ok({}::{}),'
|
||||
.format(v, ty, camel_case(v)))
|
||||
fmt.line('_ => Err(()),')
|
||||
|
||||
|
||||
def gen_enum_types(sgrp, fmt):
|
||||
# type: (SettingGroup, srcgen.Formatter) -> None
|
||||
"""
|
||||
Emit enum types for any enum settings.
|
||||
"""
|
||||
for setting in sgrp.settings:
|
||||
if not isinstance(setting, EnumSetting):
|
||||
continue
|
||||
ty = camel_case(setting.name)
|
||||
fmt.doc_comment('Values for `{}`.'.format(setting))
|
||||
fmt.line('#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]')
|
||||
with fmt.indented('pub enum {} {{'.format(ty), '}'):
|
||||
for v in setting.values:
|
||||
fmt.doc_comment('`{}`.'.format(v))
|
||||
fmt.line(camel_case(v) + ',')
|
||||
|
||||
gen_to_and_from_str(ty, setting.values, fmt)
|
||||
|
||||
|
||||
def gen_getter(setting, sgrp, fmt):
|
||||
# type: (Setting, SettingGroup, srcgen.Formatter) -> None
|
||||
"""
|
||||
Emit a getter function for `setting`.
|
||||
"""
|
||||
fmt.doc_comment(setting.__doc__)
|
||||
|
||||
if isinstance(setting, BoolSetting):
|
||||
proto = 'pub fn {}(&self) -> bool'.format(setting.name)
|
||||
with fmt.indented(proto + ' {', '}'):
|
||||
fmt.line(
|
||||
'self.numbered_predicate({})'
|
||||
.format(sgrp.predicate_number[setting]))
|
||||
elif isinstance(setting, NumSetting):
|
||||
proto = 'pub fn {}(&self) -> u8'.format(setting.name)
|
||||
with fmt.indented(proto + ' {', '}'):
|
||||
fmt.line('self.bytes[{}]'.format(setting.byte_offset))
|
||||
elif isinstance(setting, EnumSetting):
|
||||
ty = camel_case(setting.name)
|
||||
proto = 'pub fn {}(&self) -> {}'.format(setting.name, ty)
|
||||
with fmt.indented(proto + ' {', '}'):
|
||||
m = srcgen.Match('self.bytes[{}]'.format(setting.byte_offset))
|
||||
for i, v in enumerate(setting.values):
|
||||
m.arm(str(i), [], '{}::{}'.format(ty, camel_case(v)))
|
||||
m.arm('_', [], 'panic!("Invalid enum value")')
|
||||
fmt.match(m)
|
||||
else:
|
||||
raise AssertionError("Unknown setting kind")
|
||||
|
||||
|
||||
def gen_pred_getter(name, pred, sgrp, fmt):
|
||||
# type: (str, Predicate, SettingGroup, srcgen.Formatter) -> None
|
||||
"""
|
||||
Emit a getter for a named pre-computed predicate.
|
||||
"""
|
||||
fmt.doc_comment('Computed predicate `{}`.'.format(pred.rust_predicate(0)))
|
||||
proto = 'pub fn {}(&self) -> bool'.format(name)
|
||||
with fmt.indented(proto + ' {', '}'):
|
||||
fmt.line(
|
||||
'self.numbered_predicate({})'
|
||||
.format(sgrp.predicate_number[pred]))
|
||||
|
||||
|
||||
def gen_getters(sgrp, fmt):
|
||||
# type: (SettingGroup, srcgen.Formatter) -> None
|
||||
"""
|
||||
Emit getter functions for all the settings in fmt.
|
||||
"""
|
||||
fmt.doc_comment("User-defined settings.")
|
||||
fmt.line('#[allow(dead_code)]')
|
||||
with fmt.indented('impl Flags {', '}'):
|
||||
fmt.doc_comment('Get a view of the boolean predicates.')
|
||||
with fmt.indented(
|
||||
'pub fn predicate_view(&self) -> '
|
||||
'crate::settings::PredicateView {', '}'):
|
||||
fmt.format(
|
||||
'crate::settings::PredicateView::new(&self.bytes[{}..])',
|
||||
sgrp.boolean_offset)
|
||||
if sgrp.settings:
|
||||
fmt.doc_comment('Dynamic numbered predicate getter.')
|
||||
with fmt.indented(
|
||||
'fn numbered_predicate(&self, p: usize) -> bool {', '}'):
|
||||
fmt.line(
|
||||
'self.bytes[{} + p / 8] & (1 << (p % 8)) != 0'
|
||||
.format(sgrp.boolean_offset))
|
||||
for setting in sgrp.settings:
|
||||
gen_getter(setting, sgrp, fmt)
|
||||
for name, pred in sgrp.named_predicates.items():
|
||||
gen_pred_getter(name, pred, sgrp, fmt)
|
||||
|
||||
|
||||
def gen_descriptors(sgrp, fmt):
|
||||
# type: (SettingGroup, srcgen.Formatter) -> None
|
||||
"""
|
||||
Generate the DESCRIPTORS, ENUMERATORS, and PRESETS tables.
|
||||
"""
|
||||
|
||||
enums = UniqueSeqTable()
|
||||
|
||||
with fmt.indented(
|
||||
'static DESCRIPTORS: [detail::Descriptor; {}] = ['
|
||||
.format(len(sgrp.settings) + len(sgrp.presets)),
|
||||
'];'):
|
||||
for idx, setting in enumerate(sgrp.settings):
|
||||
setting.descriptor_index = idx
|
||||
with fmt.indented('detail::Descriptor {', '},'):
|
||||
fmt.line('name: "{}",'.format(setting.name))
|
||||
fmt.line('offset: {},'.format(setting.byte_offset))
|
||||
if isinstance(setting, BoolSetting):
|
||||
fmt.line(
|
||||
'detail: detail::Detail::Bool {{ bit: {} }},'
|
||||
.format(setting.bit_offset))
|
||||
elif isinstance(setting, NumSetting):
|
||||
fmt.line('detail: detail::Detail::Num,')
|
||||
elif isinstance(setting, EnumSetting):
|
||||
offs = enums.add(setting.values)
|
||||
fmt.line(
|
||||
'detail: detail::Detail::Enum ' +
|
||||
'{{ last: {}, enumerators: {} }},'
|
||||
.format(len(setting.values)-1, offs))
|
||||
else:
|
||||
raise AssertionError("Unknown setting kind")
|
||||
|
||||
for idx, preset in enumerate(sgrp.presets):
|
||||
preset.descriptor_index = len(sgrp.settings) + idx
|
||||
with fmt.indented('detail::Descriptor {', '},'):
|
||||
fmt.line('name: "{}",'.format(preset.name))
|
||||
fmt.line('offset: {},'.format(idx * sgrp.settings_size))
|
||||
fmt.line('detail: detail::Detail::Preset,')
|
||||
|
||||
with fmt.indented(
|
||||
'static ENUMERATORS: [&str; {}] = ['
|
||||
.format(len(enums.table)),
|
||||
'];'):
|
||||
for txt in enums.table:
|
||||
fmt.line('"{}",'.format(txt))
|
||||
|
||||
def hash_setting(s):
|
||||
# type: (Union[Setting, Preset]) -> int
|
||||
return constant_hash.simple_hash(s.name)
|
||||
|
||||
hash_elems = [] # type: List[Union[Setting, Preset]]
|
||||
hash_elems.extend(sgrp.settings)
|
||||
hash_elems.extend(sgrp.presets)
|
||||
hash_table = constant_hash.compute_quadratic(hash_elems, hash_setting)
|
||||
with fmt.indented(
|
||||
'static HASH_TABLE: [u16; {}] = ['
|
||||
.format(len(hash_table)),
|
||||
'];'):
|
||||
for h in hash_table:
|
||||
if h is None:
|
||||
fmt.line('0xffff,')
|
||||
else:
|
||||
fmt.line('{},'.format(h.descriptor_index))
|
||||
|
||||
with fmt.indented(
|
||||
'static PRESETS: [(u8, u8); {}] = ['
|
||||
.format(len(sgrp.presets) * sgrp.settings_size),
|
||||
'];'):
|
||||
for preset in sgrp.presets:
|
||||
fmt.comment(preset.name)
|
||||
for mask, value in preset.layout():
|
||||
fmt.format('(0b{:08b}, 0b{:08b}),', mask, value)
|
||||
|
||||
|
||||
def gen_template(sgrp, fmt):
|
||||
# type: (SettingGroup, srcgen.Formatter) -> None
|
||||
"""
|
||||
Emit a Template constant.
|
||||
"""
|
||||
v = [0] * sgrp.settings_size
|
||||
for setting in sgrp.settings:
|
||||
v[setting.byte_offset] |= setting.default_byte()
|
||||
|
||||
with fmt.indented(
|
||||
'static TEMPLATE: detail::Template = detail::Template {', '};'):
|
||||
fmt.line('name: "{}",'.format(sgrp.name))
|
||||
fmt.line('descriptors: &DESCRIPTORS,')
|
||||
fmt.line('enumerators: &ENUMERATORS,')
|
||||
fmt.line('hash_table: &HASH_TABLE,')
|
||||
vs = ', '.join('{:#04x}'.format(x) for x in v)
|
||||
fmt.line('defaults: &[{}],'.format(vs))
|
||||
fmt.line('presets: &PRESETS,')
|
||||
|
||||
fmt.doc_comment(
|
||||
'Create a `settings::Builder` for the {} settings group.'
|
||||
.format(sgrp.name))
|
||||
with fmt.indented('pub fn builder() -> Builder {', '}'):
|
||||
fmt.line('Builder::new(&TEMPLATE)')
|
||||
|
||||
|
||||
def gen_display(sgrp, fmt):
|
||||
# type: (SettingGroup, srcgen.Formatter) -> None
|
||||
"""
|
||||
Generate the Display impl for Flags.
|
||||
"""
|
||||
with fmt.indented('impl fmt::Display for Flags {', '}'):
|
||||
with fmt.indented(
|
||||
'fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {',
|
||||
'}'):
|
||||
fmt.line('writeln!(f, "[{}]")?;'.format(sgrp.name))
|
||||
with fmt.indented('for d in &DESCRIPTORS {', '}'):
|
||||
with fmt.indented('if !d.detail.is_preset() {', '}'):
|
||||
fmt.line('write!(f, "{} = ", d.name)?;')
|
||||
fmt.line(
|
||||
'TEMPLATE.format_toml_value(d.detail, ' +
|
||||
'self.bytes[d.offset as usize], f)?;')
|
||||
fmt.line('writeln!(f)?;')
|
||||
fmt.line('Ok(())')
|
||||
|
||||
|
||||
def gen_constructor(sgrp, fmt):
|
||||
# type: (SettingGroup, srcgen.Formatter) -> None
|
||||
"""
|
||||
Generate a Flags constructor.
|
||||
"""
|
||||
|
||||
with fmt.indented('impl Flags {', '}'):
|
||||
args = 'builder: Builder'
|
||||
if sgrp.parent:
|
||||
p = sgrp.parent
|
||||
args = '{}: &{}::Flags, {}'.format(p.name, p.qual_mod, args)
|
||||
fmt.doc_comment('Create flags {} settings group.'.format(sgrp.name))
|
||||
fmt.line('#[allow(unused_variables)]')
|
||||
with fmt.indented(
|
||||
'pub fn new({}) -> Self {{'.format(args), '}'):
|
||||
fmt.line('let bvec = builder.state_for("{}");'.format(sgrp.name))
|
||||
fmt.line(
|
||||
'let mut {} = Self {{ bytes: [0; {}] }};'
|
||||
.format(sgrp.name, sgrp.byte_size()))
|
||||
fmt.line(
|
||||
'debug_assert_eq!(bvec.len(), {});'
|
||||
.format(sgrp.settings_size))
|
||||
fmt.line(
|
||||
'{}.bytes[0..{}].copy_from_slice(&bvec);'
|
||||
.format(sgrp.name, sgrp.settings_size))
|
||||
|
||||
# Now compute the predicates.
|
||||
for pred, number in sgrp.predicate_number.items():
|
||||
# Don't compute our own settings.
|
||||
if number < sgrp.boolean_settings:
|
||||
continue
|
||||
fmt.comment('Precompute #{}.'.format(number))
|
||||
with fmt.indented(
|
||||
'if {} {{'.format(pred.rust_predicate(0)),
|
||||
'}'):
|
||||
fmt.line(
|
||||
'{}.bytes[{}] |= 1 << {};'
|
||||
.format(
|
||||
sgrp.name,
|
||||
sgrp.boolean_offset + number // 8,
|
||||
number % 8))
|
||||
|
||||
fmt.line(sgrp.name)
|
||||
|
||||
|
||||
def gen_group(sgrp, fmt):
|
||||
# type: (SettingGroup, srcgen.Formatter) -> None
|
||||
"""
|
||||
Generate a Flags struct representing `sgrp`.
|
||||
"""
|
||||
fmt.line('#[derive(Clone)]')
|
||||
fmt.doc_comment('Flags group `{}`.'.format(sgrp.name))
|
||||
with fmt.indented('pub struct Flags {', '}'):
|
||||
fmt.line('bytes: [u8; {}],'.format(sgrp.byte_size()))
|
||||
|
||||
gen_constructor(sgrp, fmt)
|
||||
gen_enum_types(sgrp, fmt)
|
||||
gen_getters(sgrp, fmt)
|
||||
gen_descriptors(sgrp, fmt)
|
||||
gen_template(sgrp, fmt)
|
||||
gen_display(sgrp, fmt)
|
||||
|
||||
|
||||
def generate(isas, out_dir):
|
||||
# type: (Sequence[TargetISA], str) -> None
|
||||
# Generate shared settings.
|
||||
fmt = srcgen.Formatter()
|
||||
settings.group.qual_mod = 'settings'
|
||||
gen_group(settings.group, fmt)
|
||||
fmt.update_file('settings.rs', out_dir)
|
||||
|
||||
# Generate ISA-specific settings.
|
||||
for isa in isas:
|
||||
isa.settings.qual_mod = 'isa::{}::settings'.format(
|
||||
isa.settings.name)
|
||||
fmt = srcgen.Formatter()
|
||||
gen_group(isa.settings, fmt)
|
||||
fmt.update_file('settings-{}.rs'.format(isa.name), out_dir)
|
||||
24
cranelift/codegen/meta-python/isa/__init__.py
Normal file
24
cranelift/codegen/meta-python/isa/__init__.py
Normal file
@@ -0,0 +1,24 @@
|
||||
"""
|
||||
Cranelift target ISA definitions
|
||||
--------------------------------
|
||||
|
||||
The :py:mod:`isa` package contains sub-packages for each target instruction set
|
||||
architecture supported by Cranelift.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.isa import TargetISA # noqa
|
||||
from . import riscv, x86, arm32, arm64
|
||||
|
||||
try:
|
||||
from typing import List # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def all_isas():
|
||||
# type: () -> List[TargetISA]
|
||||
"""
|
||||
Get a list of all the supported target ISAs. Each target ISA is represented
|
||||
as a :py:class:`cranelift.TargetISA` instance.
|
||||
"""
|
||||
return [riscv.ISA, x86.ISA, arm32.ISA, arm64.ISA]
|
||||
15
cranelift/codegen/meta-python/isa/arm32/__init__.py
Normal file
15
cranelift/codegen/meta-python/isa/arm32/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
ARM 32-bit Architecture
|
||||
-----------------------
|
||||
|
||||
This target ISA generates code for ARMv7 and ARMv8 CPUs in 32-bit mode
|
||||
(AArch32). We support both ARM and Thumb2 instruction encodings.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from . import defs
|
||||
from . import settings, registers # noqa
|
||||
from cdsl.isa import TargetISA # noqa
|
||||
|
||||
# Re-export the primary target ISA definition.
|
||||
ISA = defs.ISA.finish() # type: TargetISA
|
||||
19
cranelift/codegen/meta-python/isa/arm32/defs.py
Normal file
19
cranelift/codegen/meta-python/isa/arm32/defs.py
Normal file
@@ -0,0 +1,19 @@
|
||||
"""
|
||||
ARM 32-bit definitions.
|
||||
|
||||
Commonly used definitions.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.isa import TargetISA, CPUMode
|
||||
import base.instructions
|
||||
from base.legalize import narrow
|
||||
|
||||
ISA = TargetISA('arm32', [base.instructions.GROUP]) # type: TargetISA
|
||||
|
||||
# CPU modes for 32-bit ARM and Thumb2.
|
||||
A32 = CPUMode('A32', ISA)
|
||||
T32 = CPUMode('T32', ISA)
|
||||
|
||||
# TODO: Refine these.
|
||||
A32.legalize_type(narrow)
|
||||
T32.legalize_type(narrow)
|
||||
45
cranelift/codegen/meta-python/isa/arm32/registers.py
Normal file
45
cranelift/codegen/meta-python/isa/arm32/registers.py
Normal file
@@ -0,0 +1,45 @@
|
||||
"""
|
||||
ARM32 register banks.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.registers import RegBank, RegClass
|
||||
from .defs import ISA
|
||||
|
||||
|
||||
# Define the larger float bank first to avoid the alignment gap.
|
||||
FloatRegs = RegBank(
|
||||
'FloatRegs', ISA, r"""
|
||||
Floating point registers.
|
||||
|
||||
The floating point register units correspond to the S-registers, but
|
||||
extended as if there were 64 registers.
|
||||
|
||||
- S registers are one unit each.
|
||||
- D registers are two units each, even D16 and above.
|
||||
- Q registers are 4 units each.
|
||||
""",
|
||||
units=64, prefix='s')
|
||||
|
||||
# Special register units:
|
||||
# - r15 is the program counter.
|
||||
# - r14 is the link register.
|
||||
# - r13 is usually the stack pointer.
|
||||
IntRegs = RegBank(
|
||||
'IntRegs', ISA,
|
||||
'General purpose registers',
|
||||
units=16, prefix='r')
|
||||
|
||||
FlagRegs = RegBank(
|
||||
'FlagRegs', ISA,
|
||||
'Flag registers',
|
||||
units=1,
|
||||
pressure_tracking=False,
|
||||
names=['nzcv'])
|
||||
|
||||
GPR = RegClass(IntRegs)
|
||||
S = RegClass(FloatRegs, count=32)
|
||||
D = RegClass(FloatRegs, width=2)
|
||||
Q = RegClass(FloatRegs, width=4)
|
||||
FLAG = RegClass(FlagRegs)
|
||||
|
||||
RegClass.extract_names(globals())
|
||||
11
cranelift/codegen/meta-python/isa/arm32/settings.py
Normal file
11
cranelift/codegen/meta-python/isa/arm32/settings.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""
|
||||
ARM32 settings.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.settings import SettingGroup
|
||||
import base.settings as shared
|
||||
from .defs import ISA
|
||||
|
||||
ISA.settings = SettingGroup('arm32', parent=shared.group)
|
||||
|
||||
ISA.settings.close(globals())
|
||||
14
cranelift/codegen/meta-python/isa/arm64/__init__.py
Normal file
14
cranelift/codegen/meta-python/isa/arm64/__init__.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""
|
||||
ARM 64-bit Architecture
|
||||
-----------------------
|
||||
|
||||
ARMv8 CPUs running the Aarch64 architecture.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from . import defs
|
||||
from . import settings, registers # noqa
|
||||
from cdsl.isa import TargetISA # noqa
|
||||
|
||||
# Re-export the primary target ISA definition.
|
||||
ISA = defs.ISA.finish() # type: TargetISA
|
||||
15
cranelift/codegen/meta-python/isa/arm64/defs.py
Normal file
15
cranelift/codegen/meta-python/isa/arm64/defs.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""
|
||||
ARM64 definitions.
|
||||
|
||||
Commonly used definitions.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.isa import TargetISA, CPUMode
|
||||
import base.instructions
|
||||
from base.legalize import narrow
|
||||
|
||||
ISA = TargetISA('arm64', [base.instructions.GROUP]) # type: TargetISA
|
||||
A64 = CPUMode('A64', ISA)
|
||||
|
||||
# TODO: Refine these
|
||||
A64.legalize_type(narrow)
|
||||
32
cranelift/codegen/meta-python/isa/arm64/registers.py
Normal file
32
cranelift/codegen/meta-python/isa/arm64/registers.py
Normal file
@@ -0,0 +1,32 @@
|
||||
"""
|
||||
Aarch64 register banks.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.registers import RegBank, RegClass
|
||||
from .defs import ISA
|
||||
|
||||
|
||||
# The `x31` regunit serves as the stack pointer / zero register depending on
|
||||
# context. We reserve it and don't model the difference.
|
||||
IntRegs = RegBank(
|
||||
'IntRegs', ISA,
|
||||
'General purpose registers',
|
||||
units=32, prefix='x')
|
||||
|
||||
FloatRegs = RegBank(
|
||||
'FloatRegs', ISA,
|
||||
'Floating point registers',
|
||||
units=32, prefix='v')
|
||||
|
||||
FlagRegs = RegBank(
|
||||
'FlagRegs', ISA,
|
||||
'Flag registers',
|
||||
units=1,
|
||||
pressure_tracking=False,
|
||||
names=['nzcv'])
|
||||
|
||||
GPR = RegClass(IntRegs)
|
||||
FPR = RegClass(FloatRegs)
|
||||
FLAG = RegClass(FlagRegs)
|
||||
|
||||
RegClass.extract_names(globals())
|
||||
11
cranelift/codegen/meta-python/isa/arm64/settings.py
Normal file
11
cranelift/codegen/meta-python/isa/arm64/settings.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""
|
||||
ARM64 settings.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.settings import SettingGroup
|
||||
import base.settings as shared
|
||||
from .defs import ISA
|
||||
|
||||
ISA.settings = SettingGroup('arm64', parent=shared.group)
|
||||
|
||||
ISA.settings.close(globals())
|
||||
33
cranelift/codegen/meta-python/isa/riscv/__init__.py
Normal file
33
cranelift/codegen/meta-python/isa/riscv/__init__.py
Normal file
@@ -0,0 +1,33 @@
|
||||
"""
|
||||
RISC-V Target
|
||||
-------------
|
||||
|
||||
`RISC-V <https://riscv.org/>`_ is an open instruction set architecture
|
||||
originally developed at UC Berkeley. It is a RISC-style ISA with either a
|
||||
32-bit (RV32I) or 64-bit (RV32I) base instruction set and a number of optional
|
||||
extensions:
|
||||
|
||||
RV32M / RV64M
|
||||
Integer multiplication and division.
|
||||
|
||||
RV32A / RV64A
|
||||
Atomics.
|
||||
|
||||
RV32F / RV64F
|
||||
Single-precision IEEE floating point.
|
||||
|
||||
RV32D / RV64D
|
||||
Double-precision IEEE floating point.
|
||||
|
||||
RV32G / RV64G
|
||||
General purpose instruction sets. This represents the union of the I, M, A,
|
||||
F, and D instruction sets listed above.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from . import defs
|
||||
from . import encodings, settings, registers # noqa
|
||||
from cdsl.isa import TargetISA # noqa
|
||||
|
||||
# Re-export the primary target ISA definition.
|
||||
ISA = defs.ISA.finish() # type: TargetISA
|
||||
14
cranelift/codegen/meta-python/isa/riscv/defs.py
Normal file
14
cranelift/codegen/meta-python/isa/riscv/defs.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""
|
||||
RISC-V definitions.
|
||||
|
||||
Commonly used definitions.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.isa import TargetISA, CPUMode
|
||||
import base.instructions
|
||||
|
||||
ISA = TargetISA('riscv', [base.instructions.GROUP]) # type: TargetISA
|
||||
|
||||
# CPU modes for 32-bit and 64-bit operation.
|
||||
RV32 = CPUMode('RV32', ISA)
|
||||
RV64 = CPUMode('RV64', ISA)
|
||||
162
cranelift/codegen/meta-python/isa/riscv/encodings.py
Normal file
162
cranelift/codegen/meta-python/isa/riscv/encodings.py
Normal file
@@ -0,0 +1,162 @@
|
||||
"""
|
||||
RISC-V Encodings.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from base import instructions as base
|
||||
from base.immediates import intcc
|
||||
from .defs import RV32, RV64
|
||||
from .recipes import OPIMM, OPIMM32, OP, OP32, LUI, BRANCH, JALR, JAL
|
||||
from .recipes import LOAD, STORE
|
||||
from .recipes import R, Rshamt, Ricmp, Ii, Iz, Iicmp, Iret, Icall, Icopy
|
||||
from .recipes import U, UJ, UJcall, SB, SBzero, GPsp, GPfi, Irmov
|
||||
from .settings import use_m
|
||||
from cdsl.ast import Var
|
||||
from base.legalize import narrow, expand
|
||||
|
||||
RV32.legalize_monomorphic(expand)
|
||||
RV32.legalize_type(
|
||||
default=narrow,
|
||||
i32=expand,
|
||||
f32=expand,
|
||||
f64=expand)
|
||||
|
||||
RV64.legalize_monomorphic(expand)
|
||||
RV64.legalize_type(
|
||||
default=narrow,
|
||||
i32=expand,
|
||||
i64=expand,
|
||||
f32=expand,
|
||||
f64=expand)
|
||||
|
||||
# Dummies for instruction predicates.
|
||||
x = Var('x')
|
||||
y = Var('y')
|
||||
dest = Var('dest')
|
||||
args = Var('args')
|
||||
|
||||
# Basic arithmetic binary instructions are encoded in an R-type instruction.
|
||||
for inst, inst_imm, f3, f7 in [
|
||||
(base.iadd, base.iadd_imm, 0b000, 0b0000000),
|
||||
(base.isub, None, 0b000, 0b0100000),
|
||||
(base.bxor, base.bxor_imm, 0b100, 0b0000000),
|
||||
(base.bor, base.bor_imm, 0b110, 0b0000000),
|
||||
(base.band, base.band_imm, 0b111, 0b0000000)
|
||||
]:
|
||||
RV32.enc(inst.i32, R, OP(f3, f7))
|
||||
RV64.enc(inst.i64, R, OP(f3, f7))
|
||||
|
||||
# Immediate versions for add/xor/or/and.
|
||||
if inst_imm:
|
||||
RV32.enc(inst_imm.i32, Ii, OPIMM(f3))
|
||||
RV64.enc(inst_imm.i64, Ii, OPIMM(f3))
|
||||
|
||||
# 32-bit ops in RV64.
|
||||
RV64.enc(base.iadd.i32, R, OP32(0b000, 0b0000000))
|
||||
RV64.enc(base.isub.i32, R, OP32(0b000, 0b0100000))
|
||||
# There are no andiw/oriw/xoriw variations.
|
||||
RV64.enc(base.iadd_imm.i32, Ii, OPIMM32(0b000))
|
||||
|
||||
# Use iadd_imm with %x0 to materialize constants.
|
||||
RV32.enc(base.iconst.i32, Iz, OPIMM(0b000))
|
||||
RV64.enc(base.iconst.i32, Iz, OPIMM(0b000))
|
||||
RV64.enc(base.iconst.i64, Iz, OPIMM(0b000))
|
||||
|
||||
# Dynamic shifts have the same masking semantics as the clif base instructions.
|
||||
for inst, inst_imm, f3, f7 in [
|
||||
(base.ishl, base.ishl_imm, 0b001, 0b0000000),
|
||||
(base.ushr, base.ushr_imm, 0b101, 0b0000000),
|
||||
(base.sshr, base.sshr_imm, 0b101, 0b0100000),
|
||||
]:
|
||||
RV32.enc(inst.i32.i32, R, OP(f3, f7))
|
||||
RV64.enc(inst.i64.i64, R, OP(f3, f7))
|
||||
RV64.enc(inst.i32.i32, R, OP32(f3, f7))
|
||||
# Allow i32 shift amounts in 64-bit shifts.
|
||||
RV64.enc(inst.i64.i32, R, OP(f3, f7))
|
||||
RV64.enc(inst.i32.i64, R, OP32(f3, f7))
|
||||
|
||||
# Immediate shifts.
|
||||
RV32.enc(inst_imm.i32, Rshamt, OPIMM(f3, f7))
|
||||
RV64.enc(inst_imm.i64, Rshamt, OPIMM(f3, f7))
|
||||
RV64.enc(inst_imm.i32, Rshamt, OPIMM32(f3, f7))
|
||||
|
||||
# Signed and unsigned integer 'less than'. There are no 'w' variants for
|
||||
# comparing 32-bit numbers in RV64.
|
||||
RV32.enc(base.icmp.i32(intcc.slt, x, y), Ricmp, OP(0b010, 0b0000000))
|
||||
RV64.enc(base.icmp.i64(intcc.slt, x, y), Ricmp, OP(0b010, 0b0000000))
|
||||
RV32.enc(base.icmp.i32(intcc.ult, x, y), Ricmp, OP(0b011, 0b0000000))
|
||||
RV64.enc(base.icmp.i64(intcc.ult, x, y), Ricmp, OP(0b011, 0b0000000))
|
||||
|
||||
RV32.enc(base.icmp_imm.i32(intcc.slt, x, y), Iicmp, OPIMM(0b010))
|
||||
RV64.enc(base.icmp_imm.i64(intcc.slt, x, y), Iicmp, OPIMM(0b010))
|
||||
RV32.enc(base.icmp_imm.i32(intcc.ult, x, y), Iicmp, OPIMM(0b011))
|
||||
RV64.enc(base.icmp_imm.i64(intcc.ult, x, y), Iicmp, OPIMM(0b011))
|
||||
|
||||
# Integer constants with the low 12 bits clear are materialized by lui.
|
||||
RV32.enc(base.iconst.i32, U, LUI())
|
||||
RV64.enc(base.iconst.i32, U, LUI())
|
||||
RV64.enc(base.iconst.i64, U, LUI())
|
||||
|
||||
# "M" Standard Extension for Integer Multiplication and Division.
|
||||
# Gated by the `use_m` flag.
|
||||
RV32.enc(base.imul.i32, R, OP(0b000, 0b0000001), isap=use_m)
|
||||
RV64.enc(base.imul.i64, R, OP(0b000, 0b0000001), isap=use_m)
|
||||
RV64.enc(base.imul.i32, R, OP32(0b000, 0b0000001), isap=use_m)
|
||||
|
||||
# Control flow.
|
||||
|
||||
# Unconditional branches.
|
||||
RV32.enc(base.jump, UJ, JAL())
|
||||
RV64.enc(base.jump, UJ, JAL())
|
||||
RV32.enc(base.call, UJcall, JAL())
|
||||
RV64.enc(base.call, UJcall, JAL())
|
||||
|
||||
# Conditional branches.
|
||||
for cond, f3 in [
|
||||
(intcc.eq, 0b000),
|
||||
(intcc.ne, 0b001),
|
||||
(intcc.slt, 0b100),
|
||||
(intcc.sge, 0b101),
|
||||
(intcc.ult, 0b110),
|
||||
(intcc.uge, 0b111)
|
||||
]:
|
||||
RV32.enc(base.br_icmp.i32(cond, x, y, dest, args), SB, BRANCH(f3))
|
||||
RV64.enc(base.br_icmp.i64(cond, x, y, dest, args), SB, BRANCH(f3))
|
||||
|
||||
for inst, f3 in [
|
||||
(base.brz, 0b000),
|
||||
(base.brnz, 0b001)
|
||||
]:
|
||||
RV32.enc(inst.i32, SBzero, BRANCH(f3))
|
||||
RV64.enc(inst.i64, SBzero, BRANCH(f3))
|
||||
RV32.enc(inst.b1, SBzero, BRANCH(f3))
|
||||
RV64.enc(inst.b1, SBzero, BRANCH(f3))
|
||||
|
||||
# Returns are a special case of JALR using %x1 to hold the return address.
|
||||
# The return address is provided by a special-purpose `link` return value that
|
||||
# is added by legalize_signature().
|
||||
RV32.enc(base.x_return, Iret, JALR())
|
||||
RV64.enc(base.x_return, Iret, JALR())
|
||||
RV32.enc(base.call_indirect.i32, Icall, JALR())
|
||||
RV64.enc(base.call_indirect.i64, Icall, JALR())
|
||||
|
||||
# Spill and fill.
|
||||
RV32.enc(base.spill.i32, GPsp, STORE(0b010))
|
||||
RV64.enc(base.spill.i32, GPsp, STORE(0b010))
|
||||
RV64.enc(base.spill.i64, GPsp, STORE(0b011))
|
||||
RV32.enc(base.fill.i32, GPfi, LOAD(0b010))
|
||||
RV64.enc(base.fill.i32, GPfi, LOAD(0b010))
|
||||
RV64.enc(base.fill.i64, GPfi, LOAD(0b011))
|
||||
|
||||
# Register copies.
|
||||
RV32.enc(base.copy.i32, Icopy, OPIMM(0b000))
|
||||
RV64.enc(base.copy.i64, Icopy, OPIMM(0b000))
|
||||
RV64.enc(base.copy.i32, Icopy, OPIMM32(0b000))
|
||||
|
||||
RV32.enc(base.regmove.i32, Irmov, OPIMM(0b000))
|
||||
RV64.enc(base.regmove.i64, Irmov, OPIMM(0b000))
|
||||
RV64.enc(base.regmove.i32, Irmov, OPIMM32(0b000))
|
||||
|
||||
RV32.enc(base.copy.b1, Icopy, OPIMM(0b000))
|
||||
RV64.enc(base.copy.b1, Icopy, OPIMM(0b000))
|
||||
RV32.enc(base.regmove.b1, Irmov, OPIMM(0b000))
|
||||
RV64.enc(base.regmove.b1, Irmov, OPIMM(0b000))
|
||||
225
cranelift/codegen/meta-python/isa/riscv/recipes.py
Normal file
225
cranelift/codegen/meta-python/isa/riscv/recipes.py
Normal file
@@ -0,0 +1,225 @@
|
||||
"""
|
||||
RISC-V Encoding recipes.
|
||||
|
||||
The encoding recipes defined here more or less correspond to the RISC-V native
|
||||
instruction formats described in the reference:
|
||||
|
||||
The RISC-V Instruction Set Manual
|
||||
Volume I: User-Level ISA
|
||||
Version 2.1
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.isa import EncRecipe
|
||||
from cdsl.predicates import IsSignedInt
|
||||
from cdsl.registers import Stack
|
||||
from base.formats import Binary, BinaryImm, MultiAry, IntCompare, IntCompareImm
|
||||
from base.formats import Unary, UnaryImm, BranchIcmp, Branch, Jump
|
||||
from base.formats import Call, CallIndirect, RegMove
|
||||
from .registers import GPR
|
||||
|
||||
# The low 7 bits of a RISC-V instruction is the base opcode. All 32-bit
|
||||
# instructions have 11 as the two low bits, with bits 6:2 determining the base
|
||||
# opcode.
|
||||
#
|
||||
# Encbits for the 32-bit recipes are opcode[6:2] | (funct3 << 5) | ...
|
||||
# The functions below encode the encbits.
|
||||
|
||||
|
||||
def LOAD(funct3):
|
||||
# type: (int) -> int
|
||||
assert funct3 <= 0b111
|
||||
return 0b00000 | (funct3 << 5)
|
||||
|
||||
|
||||
def STORE(funct3):
|
||||
# type: (int) -> int
|
||||
assert funct3 <= 0b111
|
||||
return 0b01000 | (funct3 << 5)
|
||||
|
||||
|
||||
def BRANCH(funct3):
|
||||
# type: (int) -> int
|
||||
assert funct3 <= 0b111
|
||||
return 0b11000 | (funct3 << 5)
|
||||
|
||||
|
||||
def JALR(funct3=0):
|
||||
# type: (int) -> int
|
||||
assert funct3 <= 0b111
|
||||
return 0b11001 | (funct3 << 5)
|
||||
|
||||
|
||||
def JAL():
|
||||
# type: () -> int
|
||||
return 0b11011
|
||||
|
||||
|
||||
def OPIMM(funct3, funct7=0):
|
||||
# type: (int, int) -> int
|
||||
assert funct3 <= 0b111
|
||||
return 0b00100 | (funct3 << 5) | (funct7 << 8)
|
||||
|
||||
|
||||
def OPIMM32(funct3, funct7=0):
|
||||
# type: (int, int) -> int
|
||||
assert funct3 <= 0b111
|
||||
return 0b00110 | (funct3 << 5) | (funct7 << 8)
|
||||
|
||||
|
||||
def OP(funct3, funct7):
|
||||
# type: (int, int) -> int
|
||||
assert funct3 <= 0b111
|
||||
assert funct7 <= 0b1111111
|
||||
return 0b01100 | (funct3 << 5) | (funct7 << 8)
|
||||
|
||||
|
||||
def OP32(funct3, funct7):
|
||||
# type: (int, int) -> int
|
||||
assert funct3 <= 0b111
|
||||
assert funct7 <= 0b1111111
|
||||
return 0b01110 | (funct3 << 5) | (funct7 << 8)
|
||||
|
||||
|
||||
def AIUPC():
|
||||
# type: () -> int
|
||||
return 0b00101
|
||||
|
||||
|
||||
def LUI():
|
||||
# type: () -> int
|
||||
return 0b01101
|
||||
|
||||
|
||||
# R-type 32-bit instructions: These are mostly binary arithmetic instructions.
|
||||
# The encbits are `opcode[6:2] | (funct3 << 5) | (funct7 << 8)
|
||||
R = EncRecipe(
|
||||
'R', Binary, base_size=4, ins=(GPR, GPR), outs=GPR,
|
||||
emit='put_r(bits, in_reg0, in_reg1, out_reg0, sink);')
|
||||
|
||||
# R-type with an immediate shift amount instead of rs2.
|
||||
Rshamt = EncRecipe(
|
||||
'Rshamt', BinaryImm, base_size=4, ins=GPR, outs=GPR,
|
||||
emit='put_rshamt(bits, in_reg0, imm.into(), out_reg0, sink);')
|
||||
|
||||
# R-type encoding of an integer comparison.
|
||||
Ricmp = EncRecipe(
|
||||
'Ricmp', IntCompare, base_size=4, ins=(GPR, GPR), outs=GPR,
|
||||
emit='put_r(bits, in_reg0, in_reg1, out_reg0, sink);')
|
||||
|
||||
Ii = EncRecipe(
|
||||
'Ii', BinaryImm, base_size=4, ins=GPR, outs=GPR,
|
||||
instp=IsSignedInt(BinaryImm.imm, 12),
|
||||
emit='put_i(bits, in_reg0, imm.into(), out_reg0, sink);')
|
||||
|
||||
# I-type instruction with a hardcoded %x0 rs1.
|
||||
Iz = EncRecipe(
|
||||
'Iz', UnaryImm, base_size=4, ins=(), outs=GPR,
|
||||
instp=IsSignedInt(UnaryImm.imm, 12),
|
||||
emit='put_i(bits, 0, imm.into(), out_reg0, sink);')
|
||||
|
||||
# I-type encoding of an integer comparison.
|
||||
Iicmp = EncRecipe(
|
||||
'Iicmp', IntCompareImm, base_size=4, ins=GPR, outs=GPR,
|
||||
instp=IsSignedInt(IntCompareImm.imm, 12),
|
||||
emit='put_i(bits, in_reg0, imm.into(), out_reg0, sink);')
|
||||
|
||||
# I-type encoding for `jalr` as a return instruction. We won't use the
|
||||
# immediate offset.
|
||||
# The variable return values are not encoded.
|
||||
Iret = EncRecipe(
|
||||
'Iret', MultiAry, base_size=4, ins=(), outs=(),
|
||||
emit='''
|
||||
// Return instructions are always a jalr to %x1.
|
||||
// The return address is provided as a special-purpose link argument.
|
||||
put_i(
|
||||
bits,
|
||||
1, // rs1 = %x1
|
||||
0, // no offset.
|
||||
0, // rd = %x0: no address written.
|
||||
sink,
|
||||
);
|
||||
''')
|
||||
|
||||
# I-type encoding for `jalr` as a call_indirect.
|
||||
Icall = EncRecipe(
|
||||
'Icall', CallIndirect, base_size=4, ins=GPR, outs=(),
|
||||
emit='''
|
||||
// call_indirect instructions are jalr with rd=%x1.
|
||||
put_i(
|
||||
bits,
|
||||
in_reg0,
|
||||
0, // no offset.
|
||||
1, // rd = %x1: link register.
|
||||
sink,
|
||||
);
|
||||
''')
|
||||
|
||||
|
||||
# Copy of a GPR is implemented as addi x, 0.
|
||||
Icopy = EncRecipe(
|
||||
'Icopy', Unary, base_size=4, ins=GPR, outs=GPR,
|
||||
emit='put_i(bits, in_reg0, 0, out_reg0, sink);')
|
||||
|
||||
# Same for a GPR regmove.
|
||||
Irmov = EncRecipe(
|
||||
'Irmov', RegMove, base_size=4, ins=GPR, outs=(),
|
||||
emit='put_i(bits, src, 0, dst, sink);')
|
||||
|
||||
# U-type instructions have a 20-bit immediate that targets bits 12-31.
|
||||
U = EncRecipe(
|
||||
'U', UnaryImm, base_size=4, ins=(), outs=GPR,
|
||||
instp=IsSignedInt(UnaryImm.imm, 32, 12),
|
||||
emit='put_u(bits, imm.into(), out_reg0, sink);')
|
||||
|
||||
# UJ-type unconditional branch instructions.
|
||||
UJ = EncRecipe(
|
||||
'UJ', Jump, base_size=4, ins=(), outs=(), branch_range=(0, 21),
|
||||
emit='''
|
||||
let dest = i64::from(func.offsets[destination]);
|
||||
let disp = dest - i64::from(sink.offset());
|
||||
put_uj(bits, disp, 0, sink);
|
||||
''')
|
||||
|
||||
UJcall = EncRecipe(
|
||||
'UJcall', Call, base_size=4, ins=(), outs=(),
|
||||
emit='''
|
||||
sink.reloc_external(Reloc::RiscvCall,
|
||||
&func.dfg.ext_funcs[func_ref].name,
|
||||
0);
|
||||
// rd=%x1 is the standard link register.
|
||||
put_uj(bits, 0, 1, sink);
|
||||
''')
|
||||
|
||||
# SB-type branch instructions.
|
||||
SB = EncRecipe(
|
||||
'SB', BranchIcmp, base_size=4,
|
||||
ins=(GPR, GPR), outs=(),
|
||||
branch_range=(0, 13),
|
||||
emit='''
|
||||
let dest = i64::from(func.offsets[destination]);
|
||||
let disp = dest - i64::from(sink.offset());
|
||||
put_sb(bits, disp, in_reg0, in_reg1, sink);
|
||||
''')
|
||||
|
||||
# SB-type branch instruction with rs2 fixed to zero.
|
||||
SBzero = EncRecipe(
|
||||
'SBzero', Branch, base_size=4,
|
||||
ins=(GPR), outs=(),
|
||||
branch_range=(0, 13),
|
||||
emit='''
|
||||
let dest = i64::from(func.offsets[destination]);
|
||||
let disp = dest - i64::from(sink.offset());
|
||||
put_sb(bits, disp, in_reg0, 0, sink);
|
||||
''')
|
||||
|
||||
# Spill of a GPR.
|
||||
GPsp = EncRecipe(
|
||||
'GPsp', Unary, base_size=4,
|
||||
ins=GPR, outs=Stack(GPR),
|
||||
emit='unimplemented!();')
|
||||
|
||||
# Fill of a GPR.
|
||||
GPfi = EncRecipe(
|
||||
'GPfi', Unary, base_size=4,
|
||||
ins=Stack(GPR), outs=GPR,
|
||||
emit='unimplemented!();')
|
||||
23
cranelift/codegen/meta-python/isa/riscv/registers.py
Normal file
23
cranelift/codegen/meta-python/isa/riscv/registers.py
Normal file
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
RISC-V register banks.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.registers import RegBank, RegClass
|
||||
from .defs import ISA
|
||||
|
||||
|
||||
# We include `x0`, a.k.a `zero` in the register bank. It will be reserved.
|
||||
IntRegs = RegBank(
|
||||
'IntRegs', ISA,
|
||||
'General purpose registers',
|
||||
units=32, prefix='x')
|
||||
|
||||
FloatRegs = RegBank(
|
||||
'FloatRegs', ISA,
|
||||
'Floating point registers',
|
||||
units=32, prefix='f')
|
||||
|
||||
GPR = RegClass(IntRegs)
|
||||
FPR = RegClass(FloatRegs)
|
||||
|
||||
RegClass.extract_names(globals())
|
||||
31
cranelift/codegen/meta-python/isa/riscv/settings.py
Normal file
31
cranelift/codegen/meta-python/isa/riscv/settings.py
Normal file
@@ -0,0 +1,31 @@
|
||||
"""
|
||||
RISC-V settings.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.settings import SettingGroup, BoolSetting
|
||||
from cdsl.predicates import And
|
||||
import base.settings as shared
|
||||
from .defs import ISA
|
||||
|
||||
ISA.settings = SettingGroup('riscv', parent=shared.group)
|
||||
|
||||
supports_m = BoolSetting("CPU supports the 'M' extension (mul/div)")
|
||||
supports_a = BoolSetting("CPU supports the 'A' extension (atomics)")
|
||||
supports_f = BoolSetting("CPU supports the 'F' extension (float)")
|
||||
supports_d = BoolSetting("CPU supports the 'D' extension (double)")
|
||||
|
||||
enable_m = BoolSetting(
|
||||
"Enable the use of 'M' instructions if available",
|
||||
default=True)
|
||||
|
||||
enable_e = BoolSetting(
|
||||
"Enable the 'RV32E' instruction set with only 16 registers")
|
||||
|
||||
use_m = And(supports_m, enable_m)
|
||||
use_a = And(supports_a, shared.enable_atomics)
|
||||
use_f = And(supports_f, shared.enable_float)
|
||||
use_d = And(supports_d, shared.enable_float)
|
||||
|
||||
full_float = And(shared.enable_simd, supports_f, supports_d)
|
||||
|
||||
ISA.settings.close(globals())
|
||||
22
cranelift/codegen/meta-python/isa/x86/__init__.py
Normal file
22
cranelift/codegen/meta-python/isa/x86/__init__.py
Normal file
@@ -0,0 +1,22 @@
|
||||
"""
|
||||
x86 Target Architecture
|
||||
-----------------------
|
||||
|
||||
This target ISA generates code for x86 CPUs with two separate CPU modes:
|
||||
|
||||
`I32`
|
||||
32-bit x86 architecture, also known as 'IA-32', also sometimes referred
|
||||
to as 'i386', however note that Cranelift depends on instructions not
|
||||
in the original `i386`, such as SSE2, CMOVcc, and UD2.
|
||||
|
||||
`I64`
|
||||
x86-64 architecture, also known as 'AMD64`, `Intel 64`, and 'x64'.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from . import defs
|
||||
from . import encodings, settings, registers # noqa
|
||||
from cdsl.isa import TargetISA # noqa
|
||||
|
||||
# Re-export the primary target ISA definition.
|
||||
ISA = defs.ISA.finish() # type: TargetISA
|
||||
28
cranelift/codegen/meta-python/isa/x86/defs.py
Normal file
28
cranelift/codegen/meta-python/isa/x86/defs.py
Normal file
@@ -0,0 +1,28 @@
|
||||
"""
|
||||
x86 definitions.
|
||||
|
||||
Commonly used definitions.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.isa import TargetISA, CPUMode
|
||||
import base.instructions
|
||||
from . import instructions as x86
|
||||
from base.immediates import floatcc
|
||||
|
||||
ISA = TargetISA('x86', [base.instructions.GROUP, x86.GROUP]) # type: TargetISA
|
||||
|
||||
# CPU modes for 32-bit and 64-bit operation.
|
||||
X86_64 = CPUMode('I64', ISA)
|
||||
X86_32 = CPUMode('I32', ISA)
|
||||
|
||||
# The set of floating point condition codes that are directly supported.
|
||||
# Other condition codes need to be reversed or expressed as two tests.
|
||||
supported_floatccs = [
|
||||
floatcc.ord,
|
||||
floatcc.uno,
|
||||
floatcc.one,
|
||||
floatcc.ueq,
|
||||
floatcc.gt,
|
||||
floatcc.ge,
|
||||
floatcc.ult,
|
||||
floatcc.ule]
|
||||
748
cranelift/codegen/meta-python/isa/x86/encodings.py
Normal file
748
cranelift/codegen/meta-python/isa/x86/encodings.py
Normal file
@@ -0,0 +1,748 @@
|
||||
"""
|
||||
x86 Encodings.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.predicates import IsZero32BitFloat, IsZero64BitFloat
|
||||
from cdsl.predicates import IsUnsignedInt, Not, And
|
||||
from base.predicates import IsColocatedFunc, IsColocatedData, LengthEquals
|
||||
from base import instructions as base
|
||||
from base import types
|
||||
from base.formats import UnaryIeee32, UnaryIeee64, UnaryImm
|
||||
from base.formats import FuncAddr, Call, LoadComplex, StoreComplex
|
||||
from .defs import X86_64, X86_32
|
||||
from . import recipes as r
|
||||
from . import settings as cfg
|
||||
from . import instructions as x86
|
||||
from .legalize import x86_expand
|
||||
from base.legalize import narrow, widen, expand_flags
|
||||
from base.settings import allones_funcaddrs, is_pic
|
||||
from .settings import use_sse41
|
||||
|
||||
try:
|
||||
from typing import TYPE_CHECKING, Any # noqa
|
||||
if TYPE_CHECKING:
|
||||
from cdsl.instructions import MaybeBoundInst # noqa
|
||||
from cdsl.predicates import FieldPredicate # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
X86_32.legalize_monomorphic(expand_flags)
|
||||
X86_32.legalize_type(
|
||||
default=narrow,
|
||||
b1=expand_flags,
|
||||
i8=widen,
|
||||
i16=widen,
|
||||
i32=x86_expand,
|
||||
f32=x86_expand,
|
||||
f64=x86_expand)
|
||||
|
||||
X86_64.legalize_monomorphic(expand_flags)
|
||||
X86_64.legalize_type(
|
||||
default=narrow,
|
||||
b1=expand_flags,
|
||||
i8=widen,
|
||||
i16=widen,
|
||||
i32=x86_expand,
|
||||
i64=x86_expand,
|
||||
f32=x86_expand,
|
||||
f64=x86_expand)
|
||||
|
||||
|
||||
#
|
||||
# Helper functions for generating encodings.
|
||||
#
|
||||
|
||||
def enc_x86_64(inst, recipe, *args, **kwargs):
|
||||
# type: (MaybeBoundInst, r.TailRecipe, *int, **int) -> None
|
||||
"""
|
||||
Add encodings for `inst` to X86_64 with and without a REX prefix.
|
||||
"""
|
||||
X86_64.enc(inst, *recipe.rex(*args, **kwargs))
|
||||
X86_64.enc(inst, *recipe(*args, **kwargs))
|
||||
|
||||
|
||||
def enc_x86_64_instp(inst, recipe, instp, *args, **kwargs):
|
||||
# type: (MaybeBoundInst, r.TailRecipe, FieldPredicate, *int, **int) -> None
|
||||
"""
|
||||
Add encodings for `inst` to X86_64 with and without a REX prefix.
|
||||
"""
|
||||
X86_64.enc(inst, *recipe.rex(*args, **kwargs), instp=instp)
|
||||
X86_64.enc(inst, *recipe(*args, **kwargs), instp=instp)
|
||||
|
||||
|
||||
def enc_both(inst, recipe, *args, **kwargs):
|
||||
# type: (MaybeBoundInst, r.TailRecipe, *int, **Any) -> None
|
||||
"""
|
||||
Add encodings for `inst` to both X86_32 and X86_64.
|
||||
"""
|
||||
X86_32.enc(inst, *recipe(*args, **kwargs))
|
||||
enc_x86_64(inst, recipe, *args, **kwargs)
|
||||
|
||||
|
||||
def enc_both_instp(inst, recipe, instp, *args, **kwargs):
|
||||
# type: (MaybeBoundInst, r.TailRecipe, FieldPredicate, *int, **Any) -> None
|
||||
"""
|
||||
Add encodings for `inst` to both X86_32 and X86_64.
|
||||
"""
|
||||
X86_32.enc(inst, *recipe(*args, **kwargs), instp=instp)
|
||||
enc_x86_64_instp(inst, recipe, instp, *args, **kwargs)
|
||||
|
||||
|
||||
def enc_i32_i64(inst, recipe, *args, **kwargs):
|
||||
# type: (MaybeBoundInst, r.TailRecipe, *int, **int) -> None
|
||||
"""
|
||||
Add encodings for `inst.i32` to X86_32.
|
||||
Add encodings for `inst.i32` to X86_64 with and without REX.
|
||||
Add encodings for `inst.i64` to X86_64 with a REX.W prefix.
|
||||
"""
|
||||
X86_32.enc(inst.i32, *recipe(*args, **kwargs))
|
||||
|
||||
# REX-less encoding must come after REX encoding so we don't use it by
|
||||
# default. Otherwise reg-alloc would never use r8 and up.
|
||||
X86_64.enc(inst.i32, *recipe.rex(*args, **kwargs))
|
||||
X86_64.enc(inst.i32, *recipe(*args, **kwargs))
|
||||
|
||||
X86_64.enc(inst.i64, *recipe.rex(*args, w=1, **kwargs))
|
||||
|
||||
|
||||
def enc_i32_i64_instp(inst, recipe, instp, *args, **kwargs):
|
||||
# type: (MaybeBoundInst, r.TailRecipe, FieldPredicate, *int, **int) -> None
|
||||
"""
|
||||
Add encodings for `inst.i32` to X86_32.
|
||||
Add encodings for `inst.i32` to X86_64 with and without REX.
|
||||
Add encodings for `inst.i64` to X86_64 with a REX.W prefix.
|
||||
|
||||
Similar to `enc_i32_i64` but applies `instp` to each encoding.
|
||||
"""
|
||||
X86_32.enc(inst.i32, *recipe(*args, **kwargs), instp=instp)
|
||||
|
||||
# REX-less encoding must come after REX encoding so we don't use it by
|
||||
# default. Otherwise reg-alloc would never use r8 and up.
|
||||
X86_64.enc(inst.i32, *recipe.rex(*args, **kwargs), instp=instp)
|
||||
X86_64.enc(inst.i32, *recipe(*args, **kwargs), instp=instp)
|
||||
|
||||
X86_64.enc(inst.i64, *recipe.rex(*args, w=1, **kwargs), instp=instp)
|
||||
|
||||
|
||||
def enc_i32_i64_ld_st(inst, w_bit, recipe, *args, **kwargs):
|
||||
# type: (MaybeBoundInst, bool, r.TailRecipe, *int, **int) -> None
|
||||
"""
|
||||
Add encodings for `inst.i32` to X86_32.
|
||||
Add encodings for `inst.i32` to X86_64 with and without REX.
|
||||
Add encodings for `inst.i64` to X86_64 with a REX prefix, using the `w_bit`
|
||||
argument to determine whether or not to set the REX.W bit.
|
||||
"""
|
||||
X86_32.enc(inst.i32.any, *recipe(*args, **kwargs))
|
||||
|
||||
# REX-less encoding must come after REX encoding so we don't use it by
|
||||
# default. Otherwise reg-alloc would never use r8 and up.
|
||||
X86_64.enc(inst.i32.any, *recipe.rex(*args, **kwargs))
|
||||
X86_64.enc(inst.i32.any, *recipe(*args, **kwargs))
|
||||
|
||||
if w_bit:
|
||||
X86_64.enc(inst.i64.any, *recipe.rex(*args, w=1, **kwargs))
|
||||
else:
|
||||
X86_64.enc(inst.i64.any, *recipe.rex(*args, **kwargs))
|
||||
X86_64.enc(inst.i64.any, *recipe(*args, **kwargs))
|
||||
|
||||
|
||||
for inst, opc in [
|
||||
(base.iadd, 0x01),
|
||||
(base.isub, 0x29),
|
||||
(base.band, 0x21),
|
||||
(base.bor, 0x09),
|
||||
(base.bxor, 0x31)]:
|
||||
enc_i32_i64(inst, r.rr, opc)
|
||||
|
||||
# x86 has a bitwise not instruction NOT.
|
||||
enc_i32_i64(base.bnot, r.ur, 0xf7, rrr=2)
|
||||
|
||||
# Also add a `b1` encodings for the logic instructions.
|
||||
# TODO: Should this be done with 8-bit instructions? It would improve
|
||||
# partial register dependencies.
|
||||
enc_both(base.band.b1, r.rr, 0x21)
|
||||
enc_both(base.bor.b1, r.rr, 0x09)
|
||||
enc_both(base.bxor.b1, r.rr, 0x31)
|
||||
|
||||
enc_i32_i64(base.imul, r.rrx, 0x0f, 0xaf)
|
||||
enc_i32_i64(x86.sdivmodx, r.div, 0xf7, rrr=7)
|
||||
enc_i32_i64(x86.udivmodx, r.div, 0xf7, rrr=6)
|
||||
|
||||
enc_i32_i64(x86.smulx, r.mulx, 0xf7, rrr=5)
|
||||
enc_i32_i64(x86.umulx, r.mulx, 0xf7, rrr=4)
|
||||
|
||||
enc_i32_i64(base.copy, r.umr, 0x89)
|
||||
for ty in [types.b1, types.i8, types.i16]:
|
||||
enc_both(base.copy.bind(ty), r.umr, 0x89)
|
||||
|
||||
# For x86-64, only define REX forms for now, since we can't describe the
|
||||
# special regunit immediate operands with the current constraint language.
|
||||
for ty in [types.i8, types.i16, types.i32]:
|
||||
X86_32.enc(base.regmove.bind(ty), *r.rmov(0x89))
|
||||
X86_64.enc(base.regmove.bind(ty), *r.rmov.rex(0x89))
|
||||
X86_64.enc(base.regmove.i64, *r.rmov.rex(0x89, w=1))
|
||||
|
||||
enc_both(base.regmove.b1, r.rmov, 0x89)
|
||||
enc_both(base.regmove.i8, r.rmov, 0x89)
|
||||
|
||||
# Immediate instructions with sign-extended 8-bit and 32-bit immediate.
|
||||
for inst, rrr in [
|
||||
(base.iadd_imm, 0),
|
||||
(base.band_imm, 4),
|
||||
(base.bor_imm, 1),
|
||||
(base.bxor_imm, 6)]:
|
||||
enc_i32_i64(inst, r.r_ib, 0x83, rrr=rrr)
|
||||
enc_i32_i64(inst, r.r_id, 0x81, rrr=rrr)
|
||||
|
||||
# TODO: band_imm.i64 with an unsigned 32-bit immediate can be encoded as
|
||||
# band_imm.i32. Can even use the single-byte immediate for 0xffff_ffXX masks.
|
||||
|
||||
# Immediate constants.
|
||||
X86_32.enc(base.iconst.i32, *r.pu_id(0xb8))
|
||||
|
||||
X86_64.enc(base.iconst.i32, *r.pu_id.rex(0xb8))
|
||||
X86_64.enc(base.iconst.i32, *r.pu_id(0xb8))
|
||||
# The 32-bit immediate movl also zero-extends to 64 bits.
|
||||
X86_64.enc(base.iconst.i64, *r.pu_id.rex(0xb8),
|
||||
instp=IsUnsignedInt(UnaryImm.imm, 32))
|
||||
X86_64.enc(base.iconst.i64, *r.pu_id(0xb8),
|
||||
instp=IsUnsignedInt(UnaryImm.imm, 32))
|
||||
# Sign-extended 32-bit immediate.
|
||||
X86_64.enc(base.iconst.i64, *r.u_id.rex(0xc7, rrr=0, w=1))
|
||||
# Finally, the 0xb8 opcode takes an 8-byte immediate with a REX.W prefix.
|
||||
X86_64.enc(base.iconst.i64, *r.pu_iq.rex(0xb8, w=1))
|
||||
|
||||
# bool constants.
|
||||
enc_both(base.bconst.b1, r.pu_id_bool, 0xb8)
|
||||
|
||||
# Shifts and rotates.
|
||||
# Note that the dynamic shift amount is only masked by 5 or 6 bits; the 8-bit
|
||||
# and 16-bit shifts would need explicit masking.
|
||||
for inst, rrr in [
|
||||
(base.rotl, 0),
|
||||
(base.rotr, 1),
|
||||
(base.ishl, 4),
|
||||
(base.ushr, 5),
|
||||
(base.sshr, 7)]:
|
||||
# Cannot use enc_i32_i64 for this pattern because instructions require
|
||||
# .any suffix.
|
||||
X86_32.enc(inst.i32.any, *r.rc(0xd3, rrr=rrr))
|
||||
X86_64.enc(inst.i64.any, *r.rc.rex(0xd3, rrr=rrr, w=1))
|
||||
X86_64.enc(inst.i32.any, *r.rc.rex(0xd3, rrr=rrr))
|
||||
X86_64.enc(inst.i32.any, *r.rc(0xd3, rrr=rrr))
|
||||
|
||||
for inst, rrr in [
|
||||
(base.ishl_imm, 4),
|
||||
(base.ushr_imm, 5),
|
||||
(base.sshr_imm, 7)]:
|
||||
enc_i32_i64(inst, r.r_ib, 0xc1, rrr=rrr)
|
||||
|
||||
# Population count.
|
||||
X86_32.enc(base.popcnt.i32, *r.urm(0xf3, 0x0f, 0xb8), isap=cfg.use_popcnt)
|
||||
X86_64.enc(base.popcnt.i64, *r.urm.rex(0xf3, 0x0f, 0xb8, w=1),
|
||||
isap=cfg.use_popcnt)
|
||||
X86_64.enc(base.popcnt.i32, *r.urm.rex(0xf3, 0x0f, 0xb8), isap=cfg.use_popcnt)
|
||||
X86_64.enc(base.popcnt.i32, *r.urm(0xf3, 0x0f, 0xb8), isap=cfg.use_popcnt)
|
||||
|
||||
# Count leading zero bits.
|
||||
X86_32.enc(base.clz.i32, *r.urm(0xf3, 0x0f, 0xbd), isap=cfg.use_lzcnt)
|
||||
X86_64.enc(base.clz.i64, *r.urm.rex(0xf3, 0x0f, 0xbd, w=1),
|
||||
isap=cfg.use_lzcnt)
|
||||
X86_64.enc(base.clz.i32, *r.urm.rex(0xf3, 0x0f, 0xbd), isap=cfg.use_lzcnt)
|
||||
X86_64.enc(base.clz.i32, *r.urm(0xf3, 0x0f, 0xbd), isap=cfg.use_lzcnt)
|
||||
|
||||
# Count trailing zero bits.
|
||||
X86_32.enc(base.ctz.i32, *r.urm(0xf3, 0x0f, 0xbc), isap=cfg.use_bmi1)
|
||||
X86_64.enc(base.ctz.i64, *r.urm.rex(0xf3, 0x0f, 0xbc, w=1),
|
||||
isap=cfg.use_bmi1)
|
||||
X86_64.enc(base.ctz.i32, *r.urm.rex(0xf3, 0x0f, 0xbc), isap=cfg.use_bmi1)
|
||||
X86_64.enc(base.ctz.i32, *r.urm(0xf3, 0x0f, 0xbc), isap=cfg.use_bmi1)
|
||||
|
||||
#
|
||||
# Loads and stores.
|
||||
#
|
||||
|
||||
ldcomplexp = LengthEquals(LoadComplex, 2)
|
||||
for recipe in [r.ldWithIndex, r.ldWithIndexDisp8, r.ldWithIndexDisp32]:
|
||||
enc_i32_i64_instp(base.load_complex, recipe, ldcomplexp, 0x8b)
|
||||
enc_x86_64_instp(base.uload32_complex, recipe, ldcomplexp, 0x8b)
|
||||
X86_64.enc(base.sload32_complex, *recipe.rex(0x63, w=1),
|
||||
instp=ldcomplexp)
|
||||
enc_i32_i64_instp(base.uload16_complex, recipe, ldcomplexp, 0x0f, 0xb7)
|
||||
enc_i32_i64_instp(base.sload16_complex, recipe, ldcomplexp, 0x0f, 0xbf)
|
||||
enc_i32_i64_instp(base.uload8_complex, recipe, ldcomplexp, 0x0f, 0xb6)
|
||||
enc_i32_i64_instp(base.sload8_complex, recipe, ldcomplexp, 0x0f, 0xbe)
|
||||
|
||||
stcomplexp = LengthEquals(StoreComplex, 3)
|
||||
for recipe in [r.stWithIndex, r.stWithIndexDisp8, r.stWithIndexDisp32]:
|
||||
enc_i32_i64_instp(base.store_complex, recipe, stcomplexp, 0x89)
|
||||
enc_x86_64_instp(base.istore32_complex, recipe, stcomplexp, 0x89)
|
||||
enc_both_instp(base.istore16_complex.i32, recipe, stcomplexp, 0x66, 0x89)
|
||||
enc_x86_64_instp(base.istore16_complex.i64, recipe, stcomplexp, 0x66, 0x89)
|
||||
|
||||
for recipe in [r.stWithIndex_abcd,
|
||||
r.stWithIndexDisp8_abcd,
|
||||
r.stWithIndexDisp32_abcd]:
|
||||
enc_both_instp(base.istore8_complex.i32, recipe, stcomplexp, 0x88)
|
||||
enc_x86_64_instp(base.istore8_complex.i64, recipe, stcomplexp, 0x88)
|
||||
|
||||
for recipe in [r.st, r.stDisp8, r.stDisp32]:
|
||||
enc_i32_i64_ld_st(base.store, True, recipe, 0x89)
|
||||
enc_x86_64(base.istore32.i64.any, recipe, 0x89)
|
||||
enc_i32_i64_ld_st(base.istore16, False, recipe, 0x66, 0x89)
|
||||
|
||||
# Byte stores are more complicated because the registers they can address
|
||||
# depends of the presence of a REX prefix. The st*_abcd recipes fall back to
|
||||
# the corresponding st* recipes when a REX prefix is applied.
|
||||
for recipe in [r.st_abcd, r.stDisp8_abcd, r.stDisp32_abcd]:
|
||||
enc_both(base.istore8.i32.any, recipe, 0x88)
|
||||
enc_x86_64(base.istore8.i64.any, recipe, 0x88)
|
||||
|
||||
enc_i32_i64(base.spill, r.spillSib32, 0x89)
|
||||
enc_i32_i64(base.regspill, r.regspill32, 0x89)
|
||||
|
||||
# Use a 32-bit write for spilling `b1`, `i8` and `i16` to avoid
|
||||
# constraining the permitted registers.
|
||||
# See MIN_SPILL_SLOT_SIZE which makes this safe.
|
||||
for ty in [types.b1, types.i8, types.i16]:
|
||||
enc_both(base.spill.bind(ty), r.spillSib32, 0x89)
|
||||
enc_both(base.regspill.bind(ty), r.regspill32, 0x89)
|
||||
|
||||
for recipe in [r.ld, r.ldDisp8, r.ldDisp32]:
|
||||
enc_i32_i64_ld_st(base.load, True, recipe, 0x8b)
|
||||
enc_x86_64(base.uload32.i64, recipe, 0x8b)
|
||||
X86_64.enc(base.sload32.i64, *recipe.rex(0x63, w=1))
|
||||
enc_i32_i64_ld_st(base.uload16, True, recipe, 0x0f, 0xb7)
|
||||
enc_i32_i64_ld_st(base.sload16, True, recipe, 0x0f, 0xbf)
|
||||
enc_i32_i64_ld_st(base.uload8, True, recipe, 0x0f, 0xb6)
|
||||
enc_i32_i64_ld_st(base.sload8, True, recipe, 0x0f, 0xbe)
|
||||
|
||||
enc_i32_i64(base.fill, r.fillSib32, 0x8b)
|
||||
enc_i32_i64(base.regfill, r.regfill32, 0x8b)
|
||||
|
||||
# Load 32 bits from `b1`, `i8` and `i16` spill slots. See `spill.b1` above.
|
||||
for ty in [types.b1, types.i8, types.i16]:
|
||||
enc_both(base.fill.bind(ty), r.fillSib32, 0x8b)
|
||||
enc_both(base.regfill.bind(ty), r.regfill32, 0x8b)
|
||||
|
||||
# Push and Pop
|
||||
X86_32.enc(x86.push.i32, *r.pushq(0x50))
|
||||
enc_x86_64(x86.push.i64, r.pushq, 0x50)
|
||||
|
||||
X86_32.enc(x86.pop.i32, *r.popq(0x58))
|
||||
enc_x86_64(x86.pop.i64, r.popq, 0x58)
|
||||
|
||||
# Copy Special
|
||||
# For x86-64, only define REX forms for now, since we can't describe the
|
||||
# special regunit immediate operands with the current constraint language.
|
||||
X86_64.enc(base.copy_special, *r.copysp.rex(0x89, w=1))
|
||||
X86_32.enc(base.copy_special, *r.copysp(0x89))
|
||||
|
||||
# Adjust SP down by a dynamic value (or up, with a negative operand).
|
||||
X86_32.enc(base.adjust_sp_down.i32, *r.adjustsp(0x29))
|
||||
X86_64.enc(base.adjust_sp_down.i64, *r.adjustsp.rex(0x29, w=1))
|
||||
|
||||
# Adjust SP up by an immediate (or down, with a negative immediate)
|
||||
X86_32.enc(base.adjust_sp_up_imm, *r.adjustsp_ib(0x83))
|
||||
X86_32.enc(base.adjust_sp_up_imm, *r.adjustsp_id(0x81))
|
||||
X86_64.enc(base.adjust_sp_up_imm, *r.adjustsp_ib.rex(0x83, w=1))
|
||||
X86_64.enc(base.adjust_sp_up_imm, *r.adjustsp_id.rex(0x81, w=1))
|
||||
|
||||
# Adjust SP down by an immediate (or up, with a negative immediate)
|
||||
X86_32.enc(base.adjust_sp_down_imm, *r.adjustsp_ib(0x83, rrr=5))
|
||||
X86_32.enc(base.adjust_sp_down_imm, *r.adjustsp_id(0x81, rrr=5))
|
||||
X86_64.enc(base.adjust_sp_down_imm, *r.adjustsp_ib.rex(0x83, rrr=5, w=1))
|
||||
X86_64.enc(base.adjust_sp_down_imm, *r.adjustsp_id.rex(0x81, rrr=5, w=1))
|
||||
|
||||
#
|
||||
# Float loads and stores.
|
||||
#
|
||||
|
||||
enc_both(base.load.f32.any, r.fld, 0xf3, 0x0f, 0x10)
|
||||
enc_both(base.load.f32.any, r.fldDisp8, 0xf3, 0x0f, 0x10)
|
||||
enc_both(base.load.f32.any, r.fldDisp32, 0xf3, 0x0f, 0x10)
|
||||
|
||||
enc_both(base.load_complex.f32, r.fldWithIndex, 0xf3, 0x0f, 0x10)
|
||||
enc_both(base.load_complex.f32, r.fldWithIndexDisp8, 0xf3, 0x0f, 0x10)
|
||||
enc_both(base.load_complex.f32, r.fldWithIndexDisp32, 0xf3, 0x0f, 0x10)
|
||||
|
||||
enc_both(base.load.f64.any, r.fld, 0xf2, 0x0f, 0x10)
|
||||
enc_both(base.load.f64.any, r.fldDisp8, 0xf2, 0x0f, 0x10)
|
||||
enc_both(base.load.f64.any, r.fldDisp32, 0xf2, 0x0f, 0x10)
|
||||
|
||||
enc_both(base.load_complex.f64, r.fldWithIndex, 0xf2, 0x0f, 0x10)
|
||||
enc_both(base.load_complex.f64, r.fldWithIndexDisp8, 0xf2, 0x0f, 0x10)
|
||||
enc_both(base.load_complex.f64, r.fldWithIndexDisp32, 0xf2, 0x0f, 0x10)
|
||||
|
||||
enc_both(base.store.f32.any, r.fst, 0xf3, 0x0f, 0x11)
|
||||
enc_both(base.store.f32.any, r.fstDisp8, 0xf3, 0x0f, 0x11)
|
||||
enc_both(base.store.f32.any, r.fstDisp32, 0xf3, 0x0f, 0x11)
|
||||
|
||||
enc_both(base.store_complex.f32, r.fstWithIndex, 0xf3, 0x0f, 0x11)
|
||||
enc_both(base.store_complex.f32, r.fstWithIndexDisp8, 0xf3, 0x0f, 0x11)
|
||||
enc_both(base.store_complex.f32, r.fstWithIndexDisp32, 0xf3, 0x0f, 0x11)
|
||||
|
||||
enc_both(base.store.f64.any, r.fst, 0xf2, 0x0f, 0x11)
|
||||
enc_both(base.store.f64.any, r.fstDisp8, 0xf2, 0x0f, 0x11)
|
||||
enc_both(base.store.f64.any, r.fstDisp32, 0xf2, 0x0f, 0x11)
|
||||
|
||||
enc_both(base.store_complex.f64, r.fstWithIndex, 0xf2, 0x0f, 0x11)
|
||||
enc_both(base.store_complex.f64, r.fstWithIndexDisp8, 0xf2, 0x0f, 0x11)
|
||||
enc_both(base.store_complex.f64, r.fstWithIndexDisp32, 0xf2, 0x0f, 0x11)
|
||||
|
||||
enc_both(base.fill.f32, r.ffillSib32, 0xf3, 0x0f, 0x10)
|
||||
enc_both(base.regfill.f32, r.fregfill32, 0xf3, 0x0f, 0x10)
|
||||
enc_both(base.fill.f64, r.ffillSib32, 0xf2, 0x0f, 0x10)
|
||||
enc_both(base.regfill.f64, r.fregfill32, 0xf2, 0x0f, 0x10)
|
||||
|
||||
enc_both(base.spill.f32, r.fspillSib32, 0xf3, 0x0f, 0x11)
|
||||
enc_both(base.regspill.f32, r.fregspill32, 0xf3, 0x0f, 0x11)
|
||||
enc_both(base.spill.f64, r.fspillSib32, 0xf2, 0x0f, 0x11)
|
||||
enc_both(base.regspill.f64, r.fregspill32, 0xf2, 0x0f, 0x11)
|
||||
|
||||
#
|
||||
# Function addresses.
|
||||
#
|
||||
|
||||
# Non-PIC, all-ones funcaddresses.
|
||||
X86_32.enc(base.func_addr.i32, *r.fnaddr4(0xb8),
|
||||
isap=And(Not(allones_funcaddrs), Not(is_pic)))
|
||||
X86_64.enc(base.func_addr.i64, *r.fnaddr8.rex(0xb8, w=1),
|
||||
isap=And(Not(allones_funcaddrs), Not(is_pic)))
|
||||
|
||||
# Non-PIC, all-zeros funcaddresses.
|
||||
X86_32.enc(base.func_addr.i32, *r.allones_fnaddr4(0xb8),
|
||||
isap=And(allones_funcaddrs, Not(is_pic)))
|
||||
X86_64.enc(base.func_addr.i64, *r.allones_fnaddr8.rex(0xb8, w=1),
|
||||
isap=And(allones_funcaddrs, Not(is_pic)))
|
||||
|
||||
# 64-bit, colocated, both PIC and non-PIC. Use the lea instruction's
|
||||
# pc-relative field.
|
||||
X86_64.enc(base.func_addr.i64, *r.pcrel_fnaddr8.rex(0x8d, w=1),
|
||||
instp=IsColocatedFunc(FuncAddr.func_ref))
|
||||
|
||||
# 64-bit, non-colocated, PIC.
|
||||
X86_64.enc(base.func_addr.i64, *r.got_fnaddr8.rex(0x8b, w=1),
|
||||
isap=is_pic)
|
||||
|
||||
#
|
||||
# Global addresses.
|
||||
#
|
||||
|
||||
# Non-PIC
|
||||
X86_32.enc(base.symbol_value.i32, *r.gvaddr4(0xb8),
|
||||
isap=Not(is_pic))
|
||||
X86_64.enc(base.symbol_value.i64, *r.gvaddr8.rex(0xb8, w=1),
|
||||
isap=Not(is_pic))
|
||||
|
||||
# PIC, colocated
|
||||
X86_64.enc(base.symbol_value.i64, *r.pcrel_gvaddr8.rex(0x8d, w=1),
|
||||
isap=is_pic,
|
||||
instp=IsColocatedData())
|
||||
|
||||
# PIC, non-colocated
|
||||
X86_64.enc(base.symbol_value.i64, *r.got_gvaddr8.rex(0x8b, w=1),
|
||||
isap=is_pic)
|
||||
|
||||
#
|
||||
# Stack addresses.
|
||||
#
|
||||
# TODO: Add encoding rules for stack_load and stack_store, so that they
|
||||
# don't get legalized to stack_addr + load/store.
|
||||
#
|
||||
X86_32.enc(base.stack_addr.i32, *r.spaddr4_id(0x8d))
|
||||
X86_64.enc(base.stack_addr.i64, *r.spaddr8_id.rex(0x8d, w=1))
|
||||
|
||||
#
|
||||
# Call/return
|
||||
#
|
||||
|
||||
# 32-bit, both PIC and non-PIC.
|
||||
X86_32.enc(base.call, *r.call_id(0xe8))
|
||||
|
||||
# 64-bit, colocated, both PIC and non-PIC. Use the call instruction's
|
||||
# pc-relative field.
|
||||
X86_64.enc(base.call, *r.call_id(0xe8),
|
||||
instp=IsColocatedFunc(Call.func_ref))
|
||||
|
||||
# 64-bit, non-colocated, PIC. There is no 64-bit non-colocated non-PIC version,
|
||||
# since non-PIC is currently using the large model, which requires calls be
|
||||
# lowered to func_addr+call_indirect.
|
||||
X86_64.enc(base.call, *r.call_plt_id(0xe8), isap=is_pic)
|
||||
|
||||
X86_32.enc(base.call_indirect.i32, *r.call_r(0xff, rrr=2))
|
||||
X86_64.enc(base.call_indirect.i64, *r.call_r.rex(0xff, rrr=2))
|
||||
X86_64.enc(base.call_indirect.i64, *r.call_r(0xff, rrr=2))
|
||||
|
||||
X86_32.enc(base.x_return, *r.ret(0xc3))
|
||||
X86_64.enc(base.x_return, *r.ret(0xc3))
|
||||
|
||||
#
|
||||
# Branches
|
||||
#
|
||||
enc_both(base.jump, r.jmpb, 0xeb)
|
||||
enc_both(base.jump, r.jmpd, 0xe9)
|
||||
|
||||
enc_both(base.brif, r.brib, 0x70)
|
||||
enc_both(base.brif, r.brid, 0x0f, 0x80)
|
||||
|
||||
# Not all float condition codes are legal, see `supported_floatccs`.
|
||||
enc_both(base.brff, r.brfb, 0x70)
|
||||
enc_both(base.brff, r.brfd, 0x0f, 0x80)
|
||||
|
||||
# Note that the tjccd opcode will be prefixed with 0x0f.
|
||||
enc_i32_i64(base.brz, r.tjccb, 0x74)
|
||||
enc_i32_i64(base.brz, r.tjccd, 0x84)
|
||||
enc_i32_i64(base.brnz, r.tjccb, 0x75)
|
||||
enc_i32_i64(base.brnz, r.tjccd, 0x85)
|
||||
|
||||
# Branch on a b1 value in a register only looks at the low 8 bits. See also
|
||||
# bint encodings below.
|
||||
#
|
||||
# Start with the worst-case encoding for X86_32 only. The register allocator
|
||||
# can't handle a branch with an ABCD-constrained operand.
|
||||
X86_32.enc(base.brz.b1, *r.t8jccd_long(0x84))
|
||||
X86_32.enc(base.brnz.b1, *r.t8jccd_long(0x85))
|
||||
|
||||
enc_both(base.brz.b1, r.t8jccb_abcd, 0x74)
|
||||
enc_both(base.brz.b1, r.t8jccd_abcd, 0x84)
|
||||
enc_both(base.brnz.b1, r.t8jccb_abcd, 0x75)
|
||||
enc_both(base.brnz.b1, r.t8jccd_abcd, 0x85)
|
||||
|
||||
#
|
||||
# Jump tables
|
||||
#
|
||||
X86_64.enc(base.jump_table_entry.i64.any.any, *r.jt_entry.rex(0x63, w=1))
|
||||
X86_32.enc(base.jump_table_entry.i32.any.any, *r.jt_entry(0x8b))
|
||||
|
||||
X86_64.enc(base.jump_table_base.i64, *r.jt_base.rex(0x8d, w=1))
|
||||
X86_32.enc(base.jump_table_base.i32, *r.jt_base(0x8d))
|
||||
|
||||
enc_x86_64(base.indirect_jump_table_br.i64, r.indirect_jmp, 0xff, rrr=4)
|
||||
X86_32.enc(base.indirect_jump_table_br.i32, *r.indirect_jmp(0xff, rrr=4))
|
||||
|
||||
#
|
||||
# Trap as ud2
|
||||
#
|
||||
X86_32.enc(base.trap, *r.trap(0x0f, 0x0b))
|
||||
X86_64.enc(base.trap, *r.trap(0x0f, 0x0b))
|
||||
|
||||
# Debug trap as int3
|
||||
X86_32.enc(base.debugtrap, r.debugtrap, 0)
|
||||
X86_64.enc(base.debugtrap, r.debugtrap, 0)
|
||||
|
||||
# Using a standard EncRecipe, not the TailRecipe.
|
||||
X86_32.enc(base.trapif, r.trapif, 0)
|
||||
X86_64.enc(base.trapif, r.trapif, 0)
|
||||
X86_32.enc(base.trapff, r.trapff, 0)
|
||||
X86_64.enc(base.trapff, r.trapff, 0)
|
||||
|
||||
#
|
||||
# Comparisons
|
||||
#
|
||||
enc_i32_i64(base.icmp, r.icscc, 0x39)
|
||||
enc_i32_i64(base.icmp_imm, r.icscc_ib, 0x83, rrr=7)
|
||||
enc_i32_i64(base.icmp_imm, r.icscc_id, 0x81, rrr=7)
|
||||
enc_i32_i64(base.ifcmp, r.rcmp, 0x39)
|
||||
enc_i32_i64(base.ifcmp_imm, r.rcmp_ib, 0x83, rrr=7)
|
||||
enc_i32_i64(base.ifcmp_imm, r.rcmp_id, 0x81, rrr=7)
|
||||
# TODO: We could special-case ifcmp_imm(x, 0) to TEST(x, x).
|
||||
|
||||
X86_32.enc(base.ifcmp_sp.i32, *r.rcmp_sp(0x39))
|
||||
X86_64.enc(base.ifcmp_sp.i64, *r.rcmp_sp.rex(0x39, w=1))
|
||||
|
||||
#
|
||||
# Convert flags to bool.
|
||||
#
|
||||
# This encodes `b1` as an 8-bit low register with the value 0 or 1.
|
||||
enc_both(base.trueif, r.seti_abcd, 0x0f, 0x90)
|
||||
enc_both(base.trueff, r.setf_abcd, 0x0f, 0x90)
|
||||
|
||||
#
|
||||
# Conditional move (a.k.a integer select)
|
||||
#
|
||||
enc_i32_i64(base.selectif, r.cmov, 0x0F, 0x40)
|
||||
|
||||
#
|
||||
# Bit scan forwards and reverse
|
||||
#
|
||||
enc_i32_i64(x86.bsf, r.bsf_and_bsr, 0x0F, 0xBC)
|
||||
enc_i32_i64(x86.bsr, r.bsf_and_bsr, 0x0F, 0xBD)
|
||||
|
||||
#
|
||||
# Convert bool to int.
|
||||
#
|
||||
# This assumes that b1 is represented as an 8-bit low register with the value 0
|
||||
# or 1.
|
||||
#
|
||||
# Encode movzbq as movzbl, because it's equivalent and shorter.
|
||||
X86_32.enc(base.bint.i32.b1, *r.urm_noflags_abcd(0x0f, 0xb6))
|
||||
X86_64.enc(base.bint.i64.b1, *r.urm_noflags.rex(0x0f, 0xb6))
|
||||
X86_64.enc(base.bint.i64.b1, *r.urm_noflags_abcd(0x0f, 0xb6))
|
||||
X86_64.enc(base.bint.i32.b1, *r.urm_noflags.rex(0x0f, 0xb6))
|
||||
X86_64.enc(base.bint.i32.b1, *r.urm_noflags_abcd(0x0f, 0xb6))
|
||||
|
||||
# Numerical conversions.
|
||||
|
||||
# Reducing an integer is a no-op.
|
||||
X86_32.enc(base.ireduce.i8.i16, r.null, 0)
|
||||
X86_32.enc(base.ireduce.i8.i32, r.null, 0)
|
||||
X86_32.enc(base.ireduce.i16.i32, r.null, 0)
|
||||
|
||||
X86_64.enc(base.ireduce.i8.i16, r.null, 0)
|
||||
X86_64.enc(base.ireduce.i8.i32, r.null, 0)
|
||||
X86_64.enc(base.ireduce.i16.i32, r.null, 0)
|
||||
X86_64.enc(base.ireduce.i8.i64, r.null, 0)
|
||||
X86_64.enc(base.ireduce.i16.i64, r.null, 0)
|
||||
X86_64.enc(base.ireduce.i32.i64, r.null, 0)
|
||||
|
||||
# TODO: Add encodings for cbw, cwde, cdqe, which are sign-extending
|
||||
# instructions for %al/%ax/%eax to %ax/%eax/%rax.
|
||||
|
||||
# movsbl
|
||||
X86_32.enc(base.sextend.i32.i8, *r.urm_noflags_abcd(0x0f, 0xbe))
|
||||
X86_64.enc(base.sextend.i32.i8, *r.urm_noflags.rex(0x0f, 0xbe))
|
||||
X86_64.enc(base.sextend.i32.i8, *r.urm_noflags_abcd(0x0f, 0xbe))
|
||||
|
||||
# movswl
|
||||
X86_32.enc(base.sextend.i32.i16, *r.urm_noflags(0x0f, 0xbf))
|
||||
X86_64.enc(base.sextend.i32.i16, *r.urm_noflags.rex(0x0f, 0xbf))
|
||||
X86_64.enc(base.sextend.i32.i16, *r.urm_noflags(0x0f, 0xbf))
|
||||
|
||||
# movsbq
|
||||
X86_64.enc(base.sextend.i64.i8, *r.urm_noflags.rex(0x0f, 0xbe, w=1))
|
||||
|
||||
# movswq
|
||||
X86_64.enc(base.sextend.i64.i16, *r.urm_noflags.rex(0x0f, 0xbf, w=1))
|
||||
|
||||
# movslq
|
||||
X86_64.enc(base.sextend.i64.i32, *r.urm_noflags.rex(0x63, w=1))
|
||||
|
||||
# movzbl
|
||||
X86_32.enc(base.uextend.i32.i8, *r.urm_noflags_abcd(0x0f, 0xb6))
|
||||
X86_64.enc(base.uextend.i32.i8, *r.urm_noflags.rex(0x0f, 0xb6))
|
||||
X86_64.enc(base.uextend.i32.i8, *r.urm_noflags_abcd(0x0f, 0xb6))
|
||||
|
||||
# movzwl
|
||||
X86_32.enc(base.uextend.i32.i16, *r.urm_noflags(0x0f, 0xb7))
|
||||
X86_64.enc(base.uextend.i32.i16, *r.urm_noflags.rex(0x0f, 0xb7))
|
||||
X86_64.enc(base.uextend.i32.i16, *r.urm_noflags(0x0f, 0xb7))
|
||||
|
||||
# movzbq, encoded as movzbl because it's equivalent and shorter
|
||||
X86_64.enc(base.uextend.i64.i8, *r.urm_noflags.rex(0x0f, 0xb6))
|
||||
X86_64.enc(base.uextend.i64.i8, *r.urm_noflags(0x0f, 0xb6))
|
||||
|
||||
# movzwq, encoded as movzwl because it's equivalent and shorter
|
||||
X86_64.enc(base.uextend.i64.i16, *r.urm_noflags.rex(0x0f, 0xb7))
|
||||
X86_64.enc(base.uextend.i64.i16, *r.urm_noflags(0x0f, 0xb7))
|
||||
|
||||
# A 32-bit register copy clears the high 32 bits.
|
||||
X86_64.enc(base.uextend.i64.i32, *r.umr.rex(0x89))
|
||||
X86_64.enc(base.uextend.i64.i32, *r.umr(0x89))
|
||||
|
||||
|
||||
#
|
||||
# Floating point
|
||||
#
|
||||
|
||||
# floating-point constants equal to 0.0 can be encoded using either
|
||||
# `xorps` or `xorpd`, for 32-bit and 64-bit floats respectively.
|
||||
X86_32.enc(base.f32const, *r.f32imm_z(0x0f, 0x57),
|
||||
instp=IsZero32BitFloat(UnaryIeee32.imm))
|
||||
X86_32.enc(base.f64const, *r.f64imm_z(0x66, 0x0f, 0x57),
|
||||
instp=IsZero64BitFloat(UnaryIeee64.imm))
|
||||
|
||||
enc_x86_64_instp(base.f32const, r.f32imm_z,
|
||||
IsZero32BitFloat(UnaryIeee32.imm), 0x0f, 0x57)
|
||||
enc_x86_64_instp(base.f64const, r.f64imm_z,
|
||||
IsZero64BitFloat(UnaryIeee64.imm), 0x66, 0x0f, 0x57)
|
||||
|
||||
# movd
|
||||
enc_both(base.bitcast.f32.i32, r.frurm, 0x66, 0x0f, 0x6e)
|
||||
enc_both(base.bitcast.i32.f32, r.rfumr, 0x66, 0x0f, 0x7e)
|
||||
|
||||
# movq
|
||||
X86_64.enc(base.bitcast.f64.i64, *r.frurm.rex(0x66, 0x0f, 0x6e, w=1))
|
||||
X86_64.enc(base.bitcast.i64.f64, *r.rfumr.rex(0x66, 0x0f, 0x7e, w=1))
|
||||
|
||||
# movaps
|
||||
enc_both(base.copy.f32, r.furm, 0x0f, 0x28)
|
||||
enc_both(base.copy.f64, r.furm, 0x0f, 0x28)
|
||||
|
||||
# For x86-64, only define REX forms for now, since we can't describe the
|
||||
# special regunit immediate operands with the current constraint language.
|
||||
X86_32.enc(base.regmove.f32, *r.frmov(0x0f, 0x28))
|
||||
X86_64.enc(base.regmove.f32, *r.frmov.rex(0x0f, 0x28))
|
||||
|
||||
# For x86-64, only define REX forms for now, since we can't describe the
|
||||
# special regunit immediate operands with the current constraint language.
|
||||
X86_32.enc(base.regmove.f64, *r.frmov(0x0f, 0x28))
|
||||
X86_64.enc(base.regmove.f64, *r.frmov.rex(0x0f, 0x28))
|
||||
|
||||
# cvtsi2ss
|
||||
enc_i32_i64(base.fcvt_from_sint.f32, r.frurm, 0xf3, 0x0f, 0x2a)
|
||||
|
||||
# cvtsi2sd
|
||||
enc_i32_i64(base.fcvt_from_sint.f64, r.frurm, 0xf2, 0x0f, 0x2a)
|
||||
|
||||
# cvtss2sd
|
||||
enc_both(base.fpromote.f64.f32, r.furm, 0xf3, 0x0f, 0x5a)
|
||||
|
||||
# cvtsd2ss
|
||||
enc_both(base.fdemote.f32.f64, r.furm, 0xf2, 0x0f, 0x5a)
|
||||
|
||||
# cvttss2si
|
||||
enc_both(x86.cvtt2si.i32.f32, r.rfurm, 0xf3, 0x0f, 0x2c)
|
||||
X86_64.enc(x86.cvtt2si.i64.f32, *r.rfurm.rex(0xf3, 0x0f, 0x2c, w=1))
|
||||
|
||||
# cvttsd2si
|
||||
enc_both(x86.cvtt2si.i32.f64, r.rfurm, 0xf2, 0x0f, 0x2c)
|
||||
X86_64.enc(x86.cvtt2si.i64.f64, *r.rfurm.rex(0xf2, 0x0f, 0x2c, w=1))
|
||||
|
||||
# Exact square roots.
|
||||
enc_both(base.sqrt.f32, r.furm, 0xf3, 0x0f, 0x51)
|
||||
enc_both(base.sqrt.f64, r.furm, 0xf2, 0x0f, 0x51)
|
||||
|
||||
# Rounding. The recipe looks at the opcode to pick an immediate.
|
||||
for inst in [
|
||||
base.nearest,
|
||||
base.floor,
|
||||
base.ceil,
|
||||
base.trunc]:
|
||||
enc_both(inst.f32, r.furmi_rnd, 0x66, 0x0f, 0x3a, 0x0a, isap=use_sse41)
|
||||
enc_both(inst.f64, r.furmi_rnd, 0x66, 0x0f, 0x3a, 0x0b, isap=use_sse41)
|
||||
|
||||
|
||||
# Binary arithmetic ops.
|
||||
for inst, opc in [
|
||||
(base.fadd, 0x58),
|
||||
(base.fsub, 0x5c),
|
||||
(base.fmul, 0x59),
|
||||
(base.fdiv, 0x5e),
|
||||
(x86.fmin, 0x5d),
|
||||
(x86.fmax, 0x5f)]:
|
||||
enc_both(inst.f32, r.fa, 0xf3, 0x0f, opc)
|
||||
enc_both(inst.f64, r.fa, 0xf2, 0x0f, opc)
|
||||
|
||||
# Binary bitwise ops.
|
||||
for inst, opc in [
|
||||
(base.band, 0x54),
|
||||
(base.bor, 0x56),
|
||||
(base.bxor, 0x57)]:
|
||||
enc_both(inst.f32, r.fa, 0x0f, opc)
|
||||
enc_both(inst.f64, r.fa, 0x0f, opc)
|
||||
|
||||
# The `andnps(x,y)` instruction computes `~x&y`, while band_not(x,y)` is `x&~y.
|
||||
enc_both(base.band_not.f32, r.fax, 0x0f, 0x55)
|
||||
enc_both(base.band_not.f64, r.fax, 0x0f, 0x55)
|
||||
|
||||
# Comparisons.
|
||||
#
|
||||
# This only covers the condition codes in `supported_floatccs`, the rest are
|
||||
# handled by legalization patterns.
|
||||
enc_both(base.fcmp.f32, r.fcscc, 0x0f, 0x2e)
|
||||
enc_both(base.fcmp.f64, r.fcscc, 0x66, 0x0f, 0x2e)
|
||||
|
||||
enc_both(base.ffcmp.f32, r.fcmp, 0x0f, 0x2e)
|
||||
enc_both(base.ffcmp.f64, r.fcmp, 0x66, 0x0f, 0x2e)
|
||||
173
cranelift/codegen/meta-python/isa/x86/instructions.py
Normal file
173
cranelift/codegen/meta-python/isa/x86/instructions.py
Normal file
@@ -0,0 +1,173 @@
|
||||
"""
|
||||
Supplementary instruction definitions for x86.
|
||||
|
||||
This module defines additional instructions that are useful only to the x86
|
||||
target ISA.
|
||||
"""
|
||||
|
||||
from base.types import iflags
|
||||
from cdsl.operands import Operand
|
||||
from cdsl.typevar import TypeVar
|
||||
from cdsl.instructions import Instruction, InstructionGroup
|
||||
|
||||
|
||||
GROUP = InstructionGroup("x86", "x86-specific instruction set")
|
||||
|
||||
iWord = TypeVar('iWord', 'A scalar integer machine word', ints=(32, 64))
|
||||
|
||||
nlo = Operand('nlo', iWord, doc='Low part of numerator')
|
||||
nhi = Operand('nhi', iWord, doc='High part of numerator')
|
||||
d = Operand('d', iWord, doc='Denominator')
|
||||
q = Operand('q', iWord, doc='Quotient')
|
||||
r = Operand('r', iWord, doc='Remainder')
|
||||
|
||||
udivmodx = Instruction(
|
||||
'x86_udivmodx', r"""
|
||||
Extended unsigned division.
|
||||
|
||||
Concatenate the bits in `nhi` and `nlo` to form the numerator.
|
||||
Interpret the bits as an unsigned number and divide by the unsigned
|
||||
denominator `d`. Trap when `d` is zero or if the quotient is larger
|
||||
than the range of the output.
|
||||
|
||||
Return both quotient and remainder.
|
||||
""",
|
||||
ins=(nlo, nhi, d), outs=(q, r), can_trap=True)
|
||||
|
||||
sdivmodx = Instruction(
|
||||
'x86_sdivmodx', r"""
|
||||
Extended signed division.
|
||||
|
||||
Concatenate the bits in `nhi` and `nlo` to form the numerator.
|
||||
Interpret the bits as a signed number and divide by the signed
|
||||
denominator `d`. Trap when `d` is zero or if the quotient is outside
|
||||
the range of the output.
|
||||
|
||||
Return both quotient and remainder.
|
||||
""",
|
||||
ins=(nlo, nhi, d), outs=(q, r), can_trap=True)
|
||||
|
||||
argL = Operand('argL', iWord)
|
||||
argR = Operand('argR', iWord)
|
||||
resLo = Operand('resLo', iWord)
|
||||
resHi = Operand('resHi', iWord)
|
||||
|
||||
umulx = Instruction(
|
||||
'x86_umulx', r"""
|
||||
Unsigned integer multiplication, producing a double-length result.
|
||||
|
||||
Polymorphic over all scalar integer types, but does not support vector
|
||||
types.
|
||||
""",
|
||||
ins=(argL, argR), outs=(resLo, resHi))
|
||||
|
||||
smulx = Instruction(
|
||||
'x86_smulx', r"""
|
||||
Signed integer multiplication, producing a double-length result.
|
||||
|
||||
Polymorphic over all scalar integer types, but does not support vector
|
||||
types.
|
||||
""",
|
||||
ins=(argL, argR), outs=(resLo, resHi))
|
||||
|
||||
Float = TypeVar(
|
||||
'Float', 'A scalar or vector floating point number',
|
||||
floats=True, simd=True)
|
||||
IntTo = TypeVar(
|
||||
'IntTo', 'An integer type with the same number of lanes',
|
||||
ints=(32, 64), simd=True)
|
||||
|
||||
x = Operand('x', Float)
|
||||
a = Operand('a', IntTo)
|
||||
|
||||
cvtt2si = Instruction(
|
||||
'x86_cvtt2si', r"""
|
||||
Convert with truncation floating point to signed integer.
|
||||
|
||||
The source floating point operand is converted to a signed integer by
|
||||
rounding towards zero. If the result can't be represented in the output
|
||||
type, returns the smallest signed value the output type can represent.
|
||||
|
||||
This instruction does not trap.
|
||||
""",
|
||||
ins=x, outs=a)
|
||||
|
||||
x = Operand('x', Float)
|
||||
a = Operand('a', Float)
|
||||
y = Operand('y', Float)
|
||||
|
||||
fmin = Instruction(
|
||||
'x86_fmin', r"""
|
||||
Floating point minimum with x86 semantics.
|
||||
|
||||
This is equivalent to the C ternary operator `x < y ? x : y` which
|
||||
differs from :inst:`fmin` when either operand is NaN or when comparing
|
||||
+0.0 to -0.0.
|
||||
|
||||
When the two operands don't compare as LT, `y` is returned unchanged,
|
||||
even if it is a signalling NaN.
|
||||
""",
|
||||
ins=(x, y), outs=a)
|
||||
|
||||
fmax = Instruction(
|
||||
'x86_fmax', r"""
|
||||
Floating point maximum with x86 semantics.
|
||||
|
||||
This is equivalent to the C ternary operator `x > y ? x : y` which
|
||||
differs from :inst:`fmax` when either operand is NaN or when comparing
|
||||
+0.0 to -0.0.
|
||||
|
||||
When the two operands don't compare as GT, `y` is returned unchanged,
|
||||
even if it is a signalling NaN.
|
||||
""",
|
||||
ins=(x, y), outs=a)
|
||||
|
||||
|
||||
x = Operand('x', iWord)
|
||||
|
||||
push = Instruction(
|
||||
'x86_push', r"""
|
||||
Pushes a value onto the stack.
|
||||
|
||||
Decrements the stack pointer and stores the specified value on to the top.
|
||||
|
||||
This is polymorphic in i32 and i64. However, it is only implemented for i64
|
||||
in 64-bit mode, and only for i32 in 32-bit mode.
|
||||
""",
|
||||
ins=x, can_store=True, other_side_effects=True)
|
||||
|
||||
pop = Instruction(
|
||||
'x86_pop', r"""
|
||||
Pops a value from the stack.
|
||||
|
||||
Loads a value from the top of the stack and then increments the stack
|
||||
pointer.
|
||||
|
||||
This is polymorphic in i32 and i64. However, it is only implemented for i64
|
||||
in 64-bit mode, and only for i32 in 32-bit mode.
|
||||
""",
|
||||
outs=x, can_load=True, other_side_effects=True)
|
||||
|
||||
y = Operand('y', iWord)
|
||||
rflags = Operand('rflags', iflags)
|
||||
|
||||
bsr = Instruction(
|
||||
'x86_bsr', r"""
|
||||
Bit Scan Reverse -- returns the bit-index of the most significant 1
|
||||
in the word. Result is undefined if the argument is zero. However, it
|
||||
sets the Z flag depending on the argument, so it is at least easy to
|
||||
detect and handle that case.
|
||||
|
||||
This is polymorphic in i32 and i64. It is implemented for both i64 and
|
||||
i32 in 64-bit mode, and only for i32 in 32-bit mode.
|
||||
""",
|
||||
ins=x, outs=(y, rflags))
|
||||
|
||||
bsf = Instruction(
|
||||
'x86_bsf', r"""
|
||||
Bit Scan Forwards -- returns the bit-index of the least significant 1
|
||||
in the word. Is otherwise identical to 'bsr', just above.
|
||||
""",
|
||||
ins=x, outs=(y, rflags))
|
||||
|
||||
GROUP.close()
|
||||
229
cranelift/codegen/meta-python/isa/x86/legalize.py
Normal file
229
cranelift/codegen/meta-python/isa/x86/legalize.py
Normal file
@@ -0,0 +1,229 @@
|
||||
"""
|
||||
Custom legalization patterns for x86.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.ast import Var
|
||||
from cdsl.xform import Rtl, XFormGroup
|
||||
from base.immediates import imm64, intcc, floatcc
|
||||
from base import legalize as shared
|
||||
from base import instructions as insts
|
||||
from . import instructions as x86
|
||||
from .defs import ISA
|
||||
|
||||
x86_expand = XFormGroup(
|
||||
'x86_expand',
|
||||
"""
|
||||
Legalize instructions by expansion.
|
||||
|
||||
Use x86-specific instructions if needed.
|
||||
""",
|
||||
isa=ISA, chain=shared.expand_flags)
|
||||
|
||||
a = Var('a')
|
||||
dead = Var('dead')
|
||||
x = Var('x')
|
||||
xhi = Var('xhi')
|
||||
y = Var('y')
|
||||
a1 = Var('a1')
|
||||
a2 = Var('a2')
|
||||
|
||||
#
|
||||
# Division and remainder.
|
||||
#
|
||||
# The srem expansion requires custom code because srem INT_MIN, -1 is not
|
||||
# allowed to trap. The other ops need to check avoid_div_traps.
|
||||
x86_expand.custom_legalize(insts.sdiv, 'expand_sdivrem')
|
||||
x86_expand.custom_legalize(insts.srem, 'expand_sdivrem')
|
||||
x86_expand.custom_legalize(insts.udiv, 'expand_udivrem')
|
||||
x86_expand.custom_legalize(insts.urem, 'expand_udivrem')
|
||||
|
||||
#
|
||||
# Double length (widening) multiplication
|
||||
#
|
||||
resLo = Var('resLo')
|
||||
resHi = Var('resHi')
|
||||
x86_expand.legalize(
|
||||
resHi << insts.umulhi(x, y),
|
||||
Rtl(
|
||||
(resLo, resHi) << x86.umulx(x, y)
|
||||
))
|
||||
|
||||
x86_expand.legalize(
|
||||
resHi << insts.smulhi(x, y),
|
||||
Rtl(
|
||||
(resLo, resHi) << x86.smulx(x, y)
|
||||
))
|
||||
|
||||
# Floating point condition codes.
|
||||
#
|
||||
# The 8 condition codes in `supported_floatccs` are directly supported by a
|
||||
# `ucomiss` or `ucomisd` instruction. The remaining codes need legalization
|
||||
# patterns.
|
||||
|
||||
# Equality needs an explicit `ord` test which checks the parity bit.
|
||||
x86_expand.legalize(
|
||||
a << insts.fcmp(floatcc.eq, x, y),
|
||||
Rtl(
|
||||
a1 << insts.fcmp(floatcc.ord, x, y),
|
||||
a2 << insts.fcmp(floatcc.ueq, x, y),
|
||||
a << insts.band(a1, a2)
|
||||
))
|
||||
x86_expand.legalize(
|
||||
a << insts.fcmp(floatcc.ne, x, y),
|
||||
Rtl(
|
||||
a1 << insts.fcmp(floatcc.uno, x, y),
|
||||
a2 << insts.fcmp(floatcc.one, x, y),
|
||||
a << insts.bor(a1, a2)
|
||||
))
|
||||
|
||||
# Inequalities that need to be reversed.
|
||||
for cc, rev_cc in [
|
||||
(floatcc.lt, floatcc.gt),
|
||||
(floatcc.le, floatcc.ge),
|
||||
(floatcc.ugt, floatcc.ult),
|
||||
(floatcc.uge, floatcc.ule)]:
|
||||
x86_expand.legalize(
|
||||
a << insts.fcmp(cc, x, y),
|
||||
Rtl(
|
||||
a << insts.fcmp(rev_cc, y, x)
|
||||
))
|
||||
|
||||
# We need to modify the CFG for min/max legalization.
|
||||
x86_expand.custom_legalize(insts.fmin, 'expand_minmax')
|
||||
x86_expand.custom_legalize(insts.fmax, 'expand_minmax')
|
||||
|
||||
# Conversions from unsigned need special handling.
|
||||
x86_expand.custom_legalize(insts.fcvt_from_uint, 'expand_fcvt_from_uint')
|
||||
# Conversions from float to int can trap and modify the control flow graph.
|
||||
x86_expand.custom_legalize(insts.fcvt_to_sint, 'expand_fcvt_to_sint')
|
||||
x86_expand.custom_legalize(insts.fcvt_to_uint, 'expand_fcvt_to_uint')
|
||||
x86_expand.custom_legalize(insts.fcvt_to_sint_sat, 'expand_fcvt_to_sint_sat')
|
||||
x86_expand.custom_legalize(insts.fcvt_to_uint_sat, 'expand_fcvt_to_uint_sat')
|
||||
|
||||
# Count leading and trailing zeroes, for baseline x86_64
|
||||
c_minus_one = Var('c_minus_one')
|
||||
c_thirty_one = Var('c_thirty_one')
|
||||
c_thirty_two = Var('c_thirty_two')
|
||||
c_sixty_three = Var('c_sixty_three')
|
||||
c_sixty_four = Var('c_sixty_four')
|
||||
index1 = Var('index1')
|
||||
r2flags = Var('r2flags')
|
||||
index2 = Var('index2')
|
||||
|
||||
x86_expand.legalize(
|
||||
a << insts.clz.i64(x),
|
||||
Rtl(
|
||||
c_minus_one << insts.iconst(imm64(-1)),
|
||||
c_sixty_three << insts.iconst(imm64(63)),
|
||||
(index1, r2flags) << x86.bsr(x),
|
||||
index2 << insts.selectif(intcc.eq, r2flags, c_minus_one, index1),
|
||||
a << insts.isub(c_sixty_three, index2),
|
||||
))
|
||||
|
||||
x86_expand.legalize(
|
||||
a << insts.clz.i32(x),
|
||||
Rtl(
|
||||
c_minus_one << insts.iconst(imm64(-1)),
|
||||
c_thirty_one << insts.iconst(imm64(31)),
|
||||
(index1, r2flags) << x86.bsr(x),
|
||||
index2 << insts.selectif(intcc.eq, r2flags, c_minus_one, index1),
|
||||
a << insts.isub(c_thirty_one, index2),
|
||||
))
|
||||
|
||||
x86_expand.legalize(
|
||||
a << insts.ctz.i64(x),
|
||||
Rtl(
|
||||
c_sixty_four << insts.iconst(imm64(64)),
|
||||
(index1, r2flags) << x86.bsf(x),
|
||||
a << insts.selectif(intcc.eq, r2flags, c_sixty_four, index1),
|
||||
))
|
||||
|
||||
x86_expand.legalize(
|
||||
a << insts.ctz.i32(x),
|
||||
Rtl(
|
||||
c_thirty_two << insts.iconst(imm64(32)),
|
||||
(index1, r2flags) << x86.bsf(x),
|
||||
a << insts.selectif(intcc.eq, r2flags, c_thirty_two, index1),
|
||||
))
|
||||
|
||||
|
||||
# Population count for baseline x86_64
|
||||
qv1 = Var('qv1')
|
||||
qv3 = Var('qv3')
|
||||
qv4 = Var('qv4')
|
||||
qv5 = Var('qv5')
|
||||
qv6 = Var('qv6')
|
||||
qv7 = Var('qv7')
|
||||
qv8 = Var('qv8')
|
||||
qv9 = Var('qv9')
|
||||
qv10 = Var('qv10')
|
||||
qv11 = Var('qv11')
|
||||
qv12 = Var('qv12')
|
||||
qv13 = Var('qv13')
|
||||
qv14 = Var('qv14')
|
||||
qv15 = Var('qv15')
|
||||
qv16 = Var('qv16')
|
||||
qc77 = Var('qc77')
|
||||
qc0F = Var('qc0F')
|
||||
qc01 = Var('qc01')
|
||||
x86_expand.legalize(
|
||||
qv16 << insts.popcnt.i64(qv1),
|
||||
Rtl(
|
||||
qv3 << insts.ushr_imm(qv1, imm64(1)),
|
||||
qc77 << insts.iconst(imm64(0x7777777777777777)),
|
||||
qv4 << insts.band(qv3, qc77),
|
||||
qv5 << insts.isub(qv1, qv4),
|
||||
qv6 << insts.ushr_imm(qv4, imm64(1)),
|
||||
qv7 << insts.band(qv6, qc77),
|
||||
qv8 << insts.isub(qv5, qv7),
|
||||
qv9 << insts.ushr_imm(qv7, imm64(1)),
|
||||
qv10 << insts.band(qv9, qc77),
|
||||
qv11 << insts.isub(qv8, qv10),
|
||||
qv12 << insts.ushr_imm(qv11, imm64(4)),
|
||||
qv13 << insts.iadd(qv11, qv12),
|
||||
qc0F << insts.iconst(imm64(0x0F0F0F0F0F0F0F0F)),
|
||||
qv14 << insts.band(qv13, qc0F),
|
||||
qc01 << insts.iconst(imm64(0x0101010101010101)),
|
||||
qv15 << insts.imul(qv14, qc01),
|
||||
qv16 << insts.ushr_imm(qv15, imm64(56))
|
||||
))
|
||||
|
||||
lv1 = Var('lv1')
|
||||
lv3 = Var('lv3')
|
||||
lv4 = Var('lv4')
|
||||
lv5 = Var('lv5')
|
||||
lv6 = Var('lv6')
|
||||
lv7 = Var('lv7')
|
||||
lv8 = Var('lv8')
|
||||
lv9 = Var('lv9')
|
||||
lv10 = Var('lv10')
|
||||
lv11 = Var('lv11')
|
||||
lv12 = Var('lv12')
|
||||
lv13 = Var('lv13')
|
||||
lv14 = Var('lv14')
|
||||
lv15 = Var('lv15')
|
||||
lv16 = Var('lv16')
|
||||
lc77 = Var('lc77')
|
||||
lc0F = Var('lc0F')
|
||||
lc01 = Var('lc01')
|
||||
x86_expand.legalize(
|
||||
lv16 << insts.popcnt.i32(lv1),
|
||||
Rtl(
|
||||
lv3 << insts.ushr_imm(lv1, imm64(1)),
|
||||
lc77 << insts.iconst(imm64(0x77777777)),
|
||||
lv4 << insts.band(lv3, lc77),
|
||||
lv5 << insts.isub(lv1, lv4),
|
||||
lv6 << insts.ushr_imm(lv4, imm64(1)),
|
||||
lv7 << insts.band(lv6, lc77),
|
||||
lv8 << insts.isub(lv5, lv7),
|
||||
lv9 << insts.ushr_imm(lv7, imm64(1)),
|
||||
lv10 << insts.band(lv9, lc77),
|
||||
lv11 << insts.isub(lv8, lv10),
|
||||
lv12 << insts.ushr_imm(lv11, imm64(4)),
|
||||
lv13 << insts.iadd(lv11, lv12),
|
||||
lc0F << insts.iconst(imm64(0x0F0F0F0F)),
|
||||
lv14 << insts.band(lv13, lc0F),
|
||||
lc01 << insts.iconst(imm64(0x01010101)),
|
||||
lv15 << insts.imul(lv14, lc01),
|
||||
lv16 << insts.ushr_imm(lv15, imm64(24))
|
||||
))
|
||||
2056
cranelift/codegen/meta-python/isa/x86/recipes.py
Normal file
2056
cranelift/codegen/meta-python/isa/x86/recipes.py
Normal file
File diff suppressed because it is too large
Load Diff
61
cranelift/codegen/meta-python/isa/x86/registers.py
Normal file
61
cranelift/codegen/meta-python/isa/x86/registers.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""
|
||||
x86 register banks.
|
||||
|
||||
While the floating-point registers are straight-forward, the general purpose
|
||||
register bank has a few quirks on x86. We have these encodings of the 8-bit
|
||||
registers:
|
||||
|
||||
I32 I64 | 16b 32b 64b
|
||||
000 AL AL | AX EAX RAX
|
||||
001 CL CL | CX ECX RCX
|
||||
010 DL DL | DX EDX RDX
|
||||
011 BL BL | BX EBX RBX
|
||||
100 AH SPL | SP ESP RSP
|
||||
101 CH BPL | BP EBP RBP
|
||||
110 DH SIL | SI ESI RSI
|
||||
111 BH DIL | DI EDI RDI
|
||||
|
||||
Here, the I64 column refers to the registers you get with a REX prefix. Without
|
||||
the REX prefix, you get the I32 registers.
|
||||
|
||||
The 8-bit registers are not that useful since WebAssembly only has i32 and i64
|
||||
data types, and the H-registers even less so. Rather than trying to model the
|
||||
H-registers accurately, we'll avoid using them in both I32 and I64 modes.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.registers import RegBank, RegClass, Stack
|
||||
from .defs import ISA
|
||||
|
||||
|
||||
IntRegs = RegBank(
|
||||
'IntRegs', ISA,
|
||||
'General purpose registers',
|
||||
units=16, prefix='r',
|
||||
names='rax rcx rdx rbx rsp rbp rsi rdi'.split())
|
||||
|
||||
FloatRegs = RegBank(
|
||||
'FloatRegs', ISA,
|
||||
'SSE floating point registers',
|
||||
units=16, prefix='xmm')
|
||||
|
||||
FlagRegs = RegBank(
|
||||
'FlagRegs', ISA,
|
||||
'Flag registers',
|
||||
units=1,
|
||||
pressure_tracking=False,
|
||||
names=['rflags'])
|
||||
|
||||
GPR = RegClass(IntRegs)
|
||||
GPR8 = GPR[0:8]
|
||||
ABCD = GPR[0:4]
|
||||
FPR = RegClass(FloatRegs)
|
||||
FPR8 = FPR[0:8]
|
||||
FLAG = RegClass(FlagRegs)
|
||||
|
||||
# Constraints for stack operands.
|
||||
|
||||
# Stack operand with a 32-bit signed displacement from either RBP or RSP.
|
||||
StackGPR32 = Stack(GPR)
|
||||
StackFPR32 = Stack(FPR)
|
||||
|
||||
RegClass.extract_names(globals())
|
||||
54
cranelift/codegen/meta-python/isa/x86/settings.py
Normal file
54
cranelift/codegen/meta-python/isa/x86/settings.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
x86 settings.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.settings import SettingGroup, BoolSetting, Preset
|
||||
from cdsl.predicates import And
|
||||
import base.settings as shared
|
||||
from .defs import ISA
|
||||
|
||||
ISA.settings = SettingGroup('x86', parent=shared.group)
|
||||
|
||||
# The has_* settings here correspond to CPUID bits.
|
||||
|
||||
# CPUID.01H:ECX
|
||||
has_sse3 = BoolSetting("SSE3: CPUID.01H:ECX.SSE3[bit 0]")
|
||||
has_ssse3 = BoolSetting("SSSE3: CPUID.01H:ECX.SSSE3[bit 9]")
|
||||
has_sse41 = BoolSetting("SSE4.1: CPUID.01H:ECX.SSE4_1[bit 19]")
|
||||
has_sse42 = BoolSetting("SSE4.2: CPUID.01H:ECX.SSE4_2[bit 20]")
|
||||
has_popcnt = BoolSetting("POPCNT: CPUID.01H:ECX.POPCNT[bit 23]")
|
||||
has_avx = BoolSetting("AVX: CPUID.01H:ECX.AVX[bit 28]")
|
||||
|
||||
# CPUID.(EAX=07H, ECX=0H):EBX
|
||||
has_bmi1 = BoolSetting("BMI1: CPUID.(EAX=07H, ECX=0H):EBX.BMI1[bit 3]")
|
||||
has_bmi2 = BoolSetting("BMI2: CPUID.(EAX=07H, ECX=0H):EBX.BMI2[bit 8]")
|
||||
|
||||
# CPUID.EAX=80000001H:ECX
|
||||
has_lzcnt = BoolSetting("LZCNT: CPUID.EAX=80000001H:ECX.LZCNT[bit 5]")
|
||||
|
||||
|
||||
# The use_* settings here are used to determine if a feature can be used.
|
||||
|
||||
use_sse41 = And(has_sse41)
|
||||
use_sse42 = And(has_sse42, use_sse41)
|
||||
use_popcnt = And(has_popcnt, has_sse42)
|
||||
use_bmi1 = And(has_bmi1)
|
||||
use_lzcnt = And(has_lzcnt)
|
||||
|
||||
# Presets corresponding to x86 CPUs.
|
||||
|
||||
baseline = Preset()
|
||||
|
||||
nehalem = Preset(
|
||||
has_sse3, has_ssse3, has_sse41, has_sse42, has_popcnt)
|
||||
haswell = Preset(nehalem, has_bmi1, has_bmi2, has_lzcnt)
|
||||
broadwell = Preset(haswell)
|
||||
skylake = Preset(broadwell)
|
||||
cannonlake = Preset(skylake)
|
||||
icelake = Preset(cannonlake)
|
||||
|
||||
znver1 = Preset(
|
||||
has_sse3, has_ssse3, has_sse41, has_sse42, has_popcnt,
|
||||
has_bmi1, has_bmi2, has_lzcnt)
|
||||
|
||||
ISA.settings.close(globals())
|
||||
5
cranelift/codegen/meta-python/mypy.ini
Normal file
5
cranelift/codegen/meta-python/mypy.ini
Normal file
@@ -0,0 +1,5 @@
|
||||
[mypy]
|
||||
disallow_untyped_defs = True
|
||||
warn_unused_ignores = True
|
||||
warn_return_any = True
|
||||
strict_optional = False
|
||||
77
cranelift/codegen/meta-python/semantics/__init__.py
Normal file
77
cranelift/codegen/meta-python/semantics/__init__.py
Normal file
@@ -0,0 +1,77 @@
|
||||
"""Definitions for the semantics segment of the Cranelift language."""
|
||||
from cdsl.ti import TypeEnv, ti_rtl, get_type_env
|
||||
from cdsl.operands import ImmediateKind
|
||||
from cdsl.ast import Var
|
||||
|
||||
try:
|
||||
from typing import List, Dict, Tuple # noqa
|
||||
from cdsl.ast import VarAtomMap # noqa
|
||||
from cdsl.xform import XForm, Rtl # noqa
|
||||
from cdsl.ti import VarTyping # noqa
|
||||
from cdsl.instructions import Instruction, InstructionSemantics # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def verify_semantics(inst, src, xforms):
|
||||
# type: (Instruction, Rtl, InstructionSemantics) -> None
|
||||
"""
|
||||
Verify that the semantics transforms in xforms correctly describe the
|
||||
instruction described by the src Rtl. This involves checking that:
|
||||
0) src is a single instance of inst
|
||||
1) For all x \\in xforms x.src is a single instance of inst
|
||||
2) For any concrete values V of Literals in inst:
|
||||
For all concrete typing T of inst:
|
||||
Exists single x \\in xforms that applies to src conretazied to
|
||||
V and T
|
||||
"""
|
||||
# 0) The source rtl is always a single instance of inst
|
||||
assert len(src.rtl) == 1 and src.rtl[0].expr.inst == inst
|
||||
|
||||
# 1) For all XForms x, x.src is a single instance of inst
|
||||
for x in xforms:
|
||||
assert len(x.src.rtl) == 1 and x.src.rtl[0].expr.inst == inst
|
||||
|
||||
variants = [src] # type: List[Rtl]
|
||||
|
||||
# 2) For all enumerated immediates, compute all the possible
|
||||
# versions of src with the concrete value filled in.
|
||||
for i in inst.imm_opnums:
|
||||
op = inst.ins[i]
|
||||
if not (isinstance(op.kind, ImmediateKind) and
|
||||
op.kind.is_enumerable()):
|
||||
continue
|
||||
|
||||
new_variants = [] # type: List[Rtl]
|
||||
for rtl_var in variants:
|
||||
s = {v: v for v in rtl_var.vars()} # type: VarAtomMap
|
||||
arg = rtl_var.rtl[0].expr.args[i]
|
||||
assert isinstance(arg, Var)
|
||||
for val in op.kind.possible_values():
|
||||
s[arg] = val
|
||||
new_variants.append(rtl_var.copy(s))
|
||||
variants = new_variants
|
||||
|
||||
# For any possible version of the src with concrete enumerated immediates
|
||||
for src in variants:
|
||||
# 2) Any possible typing should be covered by exactly ONE semantic
|
||||
# XForm
|
||||
src = src.copy({})
|
||||
typenv = get_type_env(ti_rtl(src, TypeEnv()))
|
||||
typenv.normalize()
|
||||
typenv = typenv.extract()
|
||||
|
||||
for t in typenv.concrete_typings():
|
||||
matching_xforms = [] # type: List[XForm]
|
||||
for x in xforms:
|
||||
if src.substitution(x.src, {}) is None:
|
||||
continue
|
||||
|
||||
# Translate t using x.symtab
|
||||
t = {x.symtab[str(v)]: tv for (v, tv) in t.items()}
|
||||
if (x.ti.permits(t)):
|
||||
matching_xforms.append(x)
|
||||
|
||||
assert len(matching_xforms) == 1,\
|
||||
("Possible typing {} of {} not matched by exactly one case " +
|
||||
": {}").format(t, src.rtl[0], matching_xforms)
|
||||
146
cranelift/codegen/meta-python/semantics/elaborate.py
Normal file
146
cranelift/codegen/meta-python/semantics/elaborate.py
Normal file
@@ -0,0 +1,146 @@
|
||||
"""
|
||||
Tools to elaborate a given Rtl with concrete types into its semantically
|
||||
equivalent primitive version. Its elaborated primitive version contains only
|
||||
primitive cranelift instructions, which map well to SMTLIB functions.
|
||||
"""
|
||||
from .primitives import GROUP as PRIMITIVES, prim_to_bv, prim_from_bv
|
||||
from cdsl.xform import Rtl
|
||||
from cdsl.ast import Var
|
||||
|
||||
try:
|
||||
from typing import TYPE_CHECKING, Dict, Union, List, Set, Tuple # noqa
|
||||
from cdsl.xform import XForm # noqa
|
||||
from cdsl.ast import Def, VarAtomMap # noqa
|
||||
from cdsl.ti import VarTyping # noqa
|
||||
except ImportError:
|
||||
TYPE_CHECKING = False
|
||||
|
||||
|
||||
def find_matching_xform(d):
|
||||
# type: (Def) -> XForm
|
||||
"""
|
||||
Given a concrete Def d, find the unique semantic XForm x in
|
||||
d.expr.inst.semantics that applies to it.
|
||||
"""
|
||||
res = [] # type: List[XForm]
|
||||
typing = {v: v.get_typevar() for v in d.vars()} # type: VarTyping
|
||||
|
||||
for x in d.expr.inst.semantics:
|
||||
subst = d.substitution(x.src.rtl[0], {})
|
||||
|
||||
# There may not be a substitution if there are concrete Enumerator
|
||||
# values in the src pattern. (e.g. specifying the semantics of icmp.eq,
|
||||
# icmp.ge... as separate transforms)
|
||||
if (subst is None):
|
||||
continue
|
||||
|
||||
inner_typing = {} # type: VarTyping
|
||||
for (v, tv) in typing.items():
|
||||
inner_v = subst[v]
|
||||
assert isinstance(inner_v, Var)
|
||||
inner_typing[inner_v] = tv
|
||||
|
||||
if x.ti.permits(inner_typing):
|
||||
res.append(x)
|
||||
|
||||
assert len(res) == 1, "Couldn't find semantic transform for {}".format(d)
|
||||
return res[0]
|
||||
|
||||
|
||||
def cleanup_semantics(r, outputs):
|
||||
# type: (Rtl, Set[Var]) -> Rtl
|
||||
"""
|
||||
The elaboration process creates a lot of redundant prim_to_bv conversions.
|
||||
Cleanup the following cases:
|
||||
|
||||
1) prim_to_bv/prim_from_bv pair:
|
||||
a.0 << prim_from_bv(bva.0)
|
||||
...
|
||||
bva.1 << prim_to_bv(a.0) <-- redundant, replace by bva.0
|
||||
...
|
||||
|
||||
2) prim_to_bv/prim_to-bv pair:
|
||||
bva.0 << prim_to_bv(a)
|
||||
...
|
||||
bva.1 << prim_to_bv(a) <-- redundant, replace by bva.0
|
||||
...
|
||||
"""
|
||||
new_defs = [] # type: List[Def]
|
||||
subst_m = {v: v for v in r.vars()} # type: VarAtomMap
|
||||
definition = {} # type: Dict[Var, Def]
|
||||
prim_to_bv_map = {} # type: Dict[Var, Def]
|
||||
|
||||
# Pass 1: Remove redundant prim_to_bv
|
||||
for d in r.rtl:
|
||||
inst = d.expr.inst
|
||||
|
||||
if (inst == prim_to_bv):
|
||||
arg = d.expr.args[0]
|
||||
df = d.defs[0]
|
||||
assert isinstance(arg, Var)
|
||||
|
||||
if arg in definition:
|
||||
def_loc = definition[arg]
|
||||
if def_loc.expr.inst == prim_from_bv:
|
||||
assert isinstance(def_loc.expr.args[0], Var)
|
||||
subst_m[df] = def_loc.expr.args[0]
|
||||
continue
|
||||
|
||||
if arg in prim_to_bv_map:
|
||||
subst_m[df] = prim_to_bv_map[arg].defs[0]
|
||||
continue
|
||||
|
||||
prim_to_bv_map[arg] = d
|
||||
|
||||
new_def = d.copy(subst_m)
|
||||
|
||||
for v in new_def.defs:
|
||||
assert v not in definition # Guaranteed by SSA
|
||||
definition[v] = new_def
|
||||
|
||||
new_defs.append(new_def)
|
||||
|
||||
# Pass 2: Remove dead prim_from_bv
|
||||
live = set(outputs) # type: Set[Var]
|
||||
for d in new_defs:
|
||||
live = live.union(d.uses())
|
||||
|
||||
new_defs = [d for d in new_defs if not (d.expr.inst == prim_from_bv and
|
||||
d.defs[0] not in live)]
|
||||
|
||||
return Rtl(*new_defs)
|
||||
|
||||
|
||||
def elaborate(r):
|
||||
# type: (Rtl) -> Rtl
|
||||
"""
|
||||
Given a concrete Rtl r, return a semantically equivalent Rtl r1 containing
|
||||
only primitive instructions.
|
||||
"""
|
||||
fp = False
|
||||
primitives = set(PRIMITIVES.instructions)
|
||||
idx = 0
|
||||
|
||||
res = Rtl(*r.rtl)
|
||||
outputs = res.definitions()
|
||||
|
||||
while not fp:
|
||||
assert res.is_concrete()
|
||||
new_defs = [] # type: List[Def]
|
||||
fp = True
|
||||
|
||||
for d in res.rtl:
|
||||
inst = d.expr.inst
|
||||
|
||||
if (inst not in primitives):
|
||||
t = find_matching_xform(d)
|
||||
transformed = t.apply(Rtl(d), str(idx))
|
||||
idx += 1
|
||||
new_defs.extend(transformed.rtl)
|
||||
fp = False
|
||||
else:
|
||||
new_defs.append(d)
|
||||
|
||||
res.rtl = tuple(new_defs)
|
||||
|
||||
return cleanup_semantics(res, outputs)
|
||||
45
cranelift/codegen/meta-python/semantics/macros.py
Normal file
45
cranelift/codegen/meta-python/semantics/macros.py
Normal file
@@ -0,0 +1,45 @@
|
||||
"""
|
||||
Useful semantics "macro" instructions built on top of
|
||||
the primitives.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.operands import Operand
|
||||
from cdsl.typevar import TypeVar
|
||||
from cdsl.instructions import Instruction, InstructionGroup
|
||||
from base.types import b1
|
||||
from base.immediates import imm64
|
||||
from cdsl.ast import Var
|
||||
from cdsl.xform import Rtl
|
||||
from semantics.primitives import bv_from_imm64, bvite
|
||||
import base.formats # noqa
|
||||
|
||||
GROUP = InstructionGroup("primitive_macros", "Semantic macros instruction set")
|
||||
AnyBV = TypeVar('AnyBV', bitvecs=True, doc="")
|
||||
x = Var('x')
|
||||
y = Var('y')
|
||||
imm = Var('imm')
|
||||
a = Var('a')
|
||||
|
||||
#
|
||||
# Bool-to-bv1
|
||||
#
|
||||
BV1 = TypeVar("BV1", bitvecs=(1, 1), doc="")
|
||||
bv1_op = Operand('bv1_op', BV1, doc="")
|
||||
cond_op = Operand("cond", b1, doc="")
|
||||
bool2bv = Instruction(
|
||||
'bool2bv', r"""Convert a b1 value to a 1-bit BV""",
|
||||
ins=cond_op, outs=bv1_op)
|
||||
|
||||
v1 = Var('v1')
|
||||
v2 = Var('v2')
|
||||
bvone = Var('bvone')
|
||||
bvzero = Var('bvzero')
|
||||
bool2bv.set_semantics(
|
||||
v1 << bool2bv(v2),
|
||||
Rtl(
|
||||
bvone << bv_from_imm64(imm64(1)),
|
||||
bvzero << bv_from_imm64(imm64(0)),
|
||||
v1 << bvite(v2, bvone, bvzero)
|
||||
))
|
||||
|
||||
GROUP.close()
|
||||
133
cranelift/codegen/meta-python/semantics/primitives.py
Normal file
133
cranelift/codegen/meta-python/semantics/primitives.py
Normal file
@@ -0,0 +1,133 @@
|
||||
"""
|
||||
Cranelift primitive instruction set.
|
||||
|
||||
This module defines a primitive instruction set, in terms of which the base set
|
||||
is described. Most instructions in this set correspond 1-1 with an SMTLIB
|
||||
bitvector function.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.operands import Operand
|
||||
from cdsl.typevar import TypeVar
|
||||
from cdsl.instructions import Instruction, InstructionGroup
|
||||
from cdsl.ti import WiderOrEq
|
||||
from base.types import b1
|
||||
from base.immediates import imm64
|
||||
import base.formats # noqa
|
||||
|
||||
GROUP = InstructionGroup("primitive", "Primitive instruction set")
|
||||
|
||||
BV = TypeVar('BV', 'A bitvector type.', bitvecs=True)
|
||||
BV1 = TypeVar('BV1', 'A single bit bitvector.', bitvecs=(1, 1))
|
||||
Real = TypeVar('Real', 'Any real type.', ints=True, floats=True,
|
||||
bools=True, simd=True)
|
||||
|
||||
x = Operand('x', BV, doc="A semantic value X")
|
||||
y = Operand('x', BV, doc="A semantic value Y (same width as X)")
|
||||
a = Operand('a', BV, doc="A semantic value A (same width as X)")
|
||||
cond = Operand('b', TypeVar.singleton(b1), doc='A b1 value')
|
||||
|
||||
real = Operand('real', Real, doc="A real cranelift value")
|
||||
fromReal = Operand('fromReal', Real.to_bitvec(),
|
||||
doc="A real cranelift value converted to a BV")
|
||||
|
||||
#
|
||||
# BV Conversion/Materialization
|
||||
#
|
||||
prim_to_bv = Instruction(
|
||||
'prim_to_bv', r"""
|
||||
Convert an SSA Value to a flat bitvector
|
||||
""",
|
||||
ins=(real), outs=(fromReal))
|
||||
|
||||
prim_from_bv = Instruction(
|
||||
'prim_from_bv', r"""
|
||||
Convert a flat bitvector to a real SSA Value.
|
||||
""",
|
||||
ins=(fromReal), outs=(real))
|
||||
|
||||
N = Operand('N', imm64)
|
||||
bv_from_imm64 = Instruction(
|
||||
'bv_from_imm64', r"""Materialize an imm64 as a bitvector.""",
|
||||
ins=(N), outs=a)
|
||||
|
||||
#
|
||||
# Generics
|
||||
#
|
||||
bvite = Instruction(
|
||||
'bvite', r"""Bitvector ternary operator""",
|
||||
ins=(cond, x, y), outs=a)
|
||||
|
||||
|
||||
xh = Operand('xh', BV.half_width(),
|
||||
doc="A semantic value representing the upper half of X")
|
||||
xl = Operand('xl', BV.half_width(),
|
||||
doc="A semantic value representing the lower half of X")
|
||||
bvsplit = Instruction(
|
||||
'bvsplit', r"""
|
||||
""",
|
||||
ins=(x), outs=(xh, xl))
|
||||
|
||||
xy = Operand('xy', BV.double_width(),
|
||||
doc="A semantic value representing the concatenation of X and Y")
|
||||
bvconcat = Instruction(
|
||||
'bvconcat', r"""
|
||||
""",
|
||||
ins=(x, y), outs=xy)
|
||||
|
||||
bvadd = Instruction(
|
||||
'bvadd', r"""
|
||||
Standard 2's complement addition. Equivalent to wrapping integer
|
||||
addition: :math:`a := x + y \pmod{2^B}`.
|
||||
|
||||
This instruction does not depend on the signed/unsigned interpretation
|
||||
of the operands.
|
||||
""",
|
||||
ins=(x, y), outs=a)
|
||||
#
|
||||
# Bitvector comparisons
|
||||
#
|
||||
|
||||
bveq = Instruction(
|
||||
'bveq', r"""Unsigned bitvector equality""",
|
||||
ins=(x, y), outs=cond)
|
||||
bvne = Instruction(
|
||||
'bveq', r"""Unsigned bitvector inequality""",
|
||||
ins=(x, y), outs=cond)
|
||||
bvsge = Instruction(
|
||||
'bvsge', r"""Signed bitvector greater or equal""",
|
||||
ins=(x, y), outs=cond)
|
||||
bvsgt = Instruction(
|
||||
'bvsgt', r"""Signed bitvector greater than""",
|
||||
ins=(x, y), outs=cond)
|
||||
bvsle = Instruction(
|
||||
'bvsle', r"""Signed bitvector less than or equal""",
|
||||
ins=(x, y), outs=cond)
|
||||
bvslt = Instruction(
|
||||
'bvslt', r"""Signed bitvector less than""",
|
||||
ins=(x, y), outs=cond)
|
||||
bvuge = Instruction(
|
||||
'bvuge', r"""Unsigned bitvector greater or equal""",
|
||||
ins=(x, y), outs=cond)
|
||||
bvugt = Instruction(
|
||||
'bvugt', r"""Unsigned bitvector greater than""",
|
||||
ins=(x, y), outs=cond)
|
||||
bvule = Instruction(
|
||||
'bvule', r"""Unsigned bitvector less than or equal""",
|
||||
ins=(x, y), outs=cond)
|
||||
bvult = Instruction(
|
||||
'bvult', r"""Unsigned bitvector less than""",
|
||||
ins=(x, y), outs=cond)
|
||||
|
||||
# Extensions
|
||||
ToBV = TypeVar('ToBV', 'A bitvector type.', bitvecs=True)
|
||||
x1 = Operand('x1', ToBV, doc="")
|
||||
|
||||
bvzeroext = Instruction(
|
||||
'bvzeroext', r"""Unsigned bitvector extension""",
|
||||
ins=x, outs=x1, constraints=WiderOrEq(ToBV, BV))
|
||||
|
||||
bvsignext = Instruction(
|
||||
'bvsignext', r"""Signed bitvector extension""",
|
||||
ins=x, outs=x1, constraints=WiderOrEq(ToBV, BV))
|
||||
|
||||
GROUP.close()
|
||||
241
cranelift/codegen/meta-python/semantics/smtlib.py
Normal file
241
cranelift/codegen/meta-python/semantics/smtlib.py
Normal file
@@ -0,0 +1,241 @@
|
||||
"""
|
||||
Tools to emit SMTLIB bitvector queries encoding concrete RTLs containing only
|
||||
primitive instructions.
|
||||
"""
|
||||
from .primitives import GROUP as PRIMITIVES, prim_from_bv, prim_to_bv, bvadd,\
|
||||
bvult, bvzeroext, bvsplit, bvconcat, bvsignext
|
||||
from cdsl.ast import Var
|
||||
from cdsl.types import BVType
|
||||
from .elaborate import elaborate
|
||||
from z3 import BitVec, ZeroExt, SignExt, And, Extract, Concat, Not, Solver,\
|
||||
unsat, BoolRef, BitVecVal, If
|
||||
from z3.z3core import Z3_mk_eq
|
||||
|
||||
try:
|
||||
from typing import TYPE_CHECKING, Tuple, Dict, List # noqa
|
||||
from cdsl.xform import Rtl, XForm # noqa
|
||||
from cdsl.ast import VarAtomMap, Atom # noqa
|
||||
from cdsl.ti import VarTyping # noqa
|
||||
if TYPE_CHECKING:
|
||||
from z3 import ExprRef, BitVecRef # noqa
|
||||
Z3VarMap = Dict[Var, BitVecRef]
|
||||
except ImportError:
|
||||
TYPE_CHECKING = False
|
||||
|
||||
|
||||
# Use this for constructing a == b instead of == since MyPy doesn't
|
||||
# accept overloading of __eq__ that doesn't return bool
|
||||
def mk_eq(e1, e2):
|
||||
# type: (ExprRef, ExprRef) -> ExprRef
|
||||
"""Return a z3 expression equivalent to e1 == e2"""
|
||||
return BoolRef(Z3_mk_eq(e1.ctx_ref(), e1.as_ast(), e2.as_ast()), e1.ctx)
|
||||
|
||||
|
||||
def to_smt(r):
|
||||
# type: (Rtl) -> Tuple[List[ExprRef], Z3VarMap]
|
||||
"""
|
||||
Encode a concrete primitive Rtl r sa z3 query.
|
||||
Returns a tuple (query, var_m) where:
|
||||
- query is a list of z3 expressions
|
||||
- var_m is a map from Vars v with non-BVType to their correspodning z3
|
||||
bitvector variable.
|
||||
"""
|
||||
assert r.is_concrete()
|
||||
# Should contain only primitives
|
||||
primitives = set(PRIMITIVES.instructions)
|
||||
assert set(d.expr.inst for d in r.rtl).issubset(primitives)
|
||||
|
||||
q = [] # type: List[ExprRef]
|
||||
m = {} # type: Z3VarMap
|
||||
|
||||
# Build declarations for any bitvector Vars
|
||||
var_to_bv = {} # type: Z3VarMap
|
||||
for v in r.vars():
|
||||
typ = v.get_typevar().singleton_type()
|
||||
if not isinstance(typ, BVType):
|
||||
continue
|
||||
|
||||
var_to_bv[v] = BitVec(v.name, typ.bits)
|
||||
|
||||
# Encode each instruction as a equality assertion
|
||||
for d in r.rtl:
|
||||
inst = d.expr.inst
|
||||
|
||||
exp = None # type: ExprRef
|
||||
# For prim_to_bv/prim_from_bv just update var_m. No assertion needed
|
||||
if inst == prim_to_bv:
|
||||
assert isinstance(d.expr.args[0], Var)
|
||||
m[d.expr.args[0]] = var_to_bv[d.defs[0]]
|
||||
continue
|
||||
|
||||
if inst == prim_from_bv:
|
||||
assert isinstance(d.expr.args[0], Var)
|
||||
m[d.defs[0]] = var_to_bv[d.expr.args[0]]
|
||||
continue
|
||||
|
||||
if inst in [bvadd, bvult]: # Binary instructions
|
||||
assert len(d.expr.args) == 2 and len(d.defs) == 1
|
||||
lhs = d.expr.args[0]
|
||||
rhs = d.expr.args[1]
|
||||
df = d.defs[0]
|
||||
assert isinstance(lhs, Var) and isinstance(rhs, Var)
|
||||
|
||||
if inst == bvadd: # Normal binary - output type same as args
|
||||
exp = (var_to_bv[lhs] + var_to_bv[rhs])
|
||||
else:
|
||||
assert inst == bvult
|
||||
exp = (var_to_bv[lhs] < var_to_bv[rhs])
|
||||
# Comparison binary - need to convert bool to BitVec 1
|
||||
exp = If(exp, BitVecVal(1, 1), BitVecVal(0, 1))
|
||||
|
||||
exp = mk_eq(var_to_bv[df], exp)
|
||||
elif inst == bvzeroext:
|
||||
arg = d.expr.args[0]
|
||||
df = d.defs[0]
|
||||
assert isinstance(arg, Var)
|
||||
fromW = arg.get_typevar().singleton_type().width()
|
||||
toW = df.get_typevar().singleton_type().width()
|
||||
|
||||
exp = mk_eq(var_to_bv[df], ZeroExt(toW-fromW, var_to_bv[arg]))
|
||||
elif inst == bvsignext:
|
||||
arg = d.expr.args[0]
|
||||
df = d.defs[0]
|
||||
assert isinstance(arg, Var)
|
||||
fromW = arg.get_typevar().singleton_type().width()
|
||||
toW = df.get_typevar().singleton_type().width()
|
||||
|
||||
exp = mk_eq(var_to_bv[df], SignExt(toW-fromW, var_to_bv[arg]))
|
||||
elif inst == bvsplit:
|
||||
arg = d.expr.args[0]
|
||||
assert isinstance(arg, Var)
|
||||
arg_typ = arg.get_typevar().singleton_type()
|
||||
width = arg_typ.width()
|
||||
assert (width % 2 == 0)
|
||||
|
||||
lo = d.defs[0]
|
||||
hi = d.defs[1]
|
||||
|
||||
exp = And(mk_eq(var_to_bv[lo],
|
||||
Extract(width//2-1, 0, var_to_bv[arg])),
|
||||
mk_eq(var_to_bv[hi],
|
||||
Extract(width-1, width//2, var_to_bv[arg])))
|
||||
elif inst == bvconcat:
|
||||
assert isinstance(d.expr.args[0], Var) and \
|
||||
isinstance(d.expr.args[1], Var)
|
||||
lo = d.expr.args[0]
|
||||
hi = d.expr.args[1]
|
||||
df = d.defs[0]
|
||||
|
||||
# Z3 Concat expects hi bits first, then lo bits
|
||||
exp = mk_eq(var_to_bv[df], Concat(var_to_bv[hi], var_to_bv[lo]))
|
||||
else:
|
||||
assert False, "Unknown primitive instruction {}".format(inst)
|
||||
|
||||
q.append(exp)
|
||||
|
||||
return (q, m)
|
||||
|
||||
|
||||
def equivalent(r1, r2, inp_m, out_m):
|
||||
# type: (Rtl, Rtl, VarAtomMap, VarAtomMap) -> List[ExprRef]
|
||||
"""
|
||||
Given:
|
||||
- concrete source Rtl r1
|
||||
- concrete dest Rtl r2
|
||||
- VarAtomMap inp_m mapping r1's non-bitvector inputs to r2
|
||||
- VarAtomMap out_m mapping r1's non-bitvector outputs to r2
|
||||
|
||||
Build a query checking whether r1 and r2 are semantically equivalent.
|
||||
If the returned query is unsatisfiable, then r1 and r2 are equivalent.
|
||||
Otherwise, the satisfying example for the query gives us values
|
||||
for which the two Rtls disagree.
|
||||
"""
|
||||
# Sanity - inp_m is a bijection from the set of inputs of r1 to the set of
|
||||
# inputs of r2
|
||||
assert set(r1.free_vars()) == set(inp_m.keys())
|
||||
assert set(r2.free_vars()) == set(inp_m.values())
|
||||
|
||||
# Note that the same rule is not expected to hold for out_m due to
|
||||
# temporaries/intermediates. out_m specified which values are enough for
|
||||
# equivalence.
|
||||
|
||||
# Rename the vars in r1 and r2 with unique suffixes to avoid conflicts
|
||||
src_m = {v: Var(v.name + ".a", v.get_typevar()) for v in r1.vars()} # type: VarAtomMap # noqa
|
||||
dst_m = {v: Var(v.name + ".b", v.get_typevar()) for v in r2.vars()} # type: VarAtomMap # noqa
|
||||
r1 = r1.copy(src_m)
|
||||
r2 = r2.copy(dst_m)
|
||||
|
||||
def _translate(m, k_m, v_m):
|
||||
# type: (VarAtomMap, VarAtomMap, VarAtomMap) -> VarAtomMap
|
||||
"""Obtain a new map from m, by mapping m's keys with k_m and m's values
|
||||
with v_m"""
|
||||
res = {} # type: VarAtomMap
|
||||
for (k, v) in m1.items():
|
||||
new_k = k_m[k]
|
||||
new_v = v_m[v]
|
||||
assert isinstance(new_k, Var)
|
||||
res[new_k] = new_v
|
||||
|
||||
return res
|
||||
|
||||
# Convert inp_m, out_m in terms of variables with the .a/.b suffixes
|
||||
inp_m = _translate(inp_m, src_m, dst_m)
|
||||
out_m = _translate(out_m, src_m, dst_m)
|
||||
|
||||
# Encode r1 and r2 as SMT queries
|
||||
(q1, m1) = to_smt(r1)
|
||||
(q2, m2) = to_smt(r2)
|
||||
|
||||
# Build an expression for the equality of real Cranelift inputs of
|
||||
# r1 and r2
|
||||
args_eq_exp = [] # type: List[ExprRef]
|
||||
|
||||
for (v1, v2) in inp_m.items():
|
||||
assert isinstance(v2, Var)
|
||||
args_eq_exp.append(mk_eq(m1[v1], m2[v2]))
|
||||
|
||||
# Build an expression for the equality of real Cranelift outputs of
|
||||
# r1 and r2
|
||||
results_eq_exp = [] # type: List[ExprRef]
|
||||
for (v1, v2) in out_m.items():
|
||||
assert isinstance(v2, Var)
|
||||
results_eq_exp.append(mk_eq(m1[v1], m2[v2]))
|
||||
|
||||
# Put the whole query together
|
||||
return q1 + q2 + args_eq_exp + [Not(And(*results_eq_exp))]
|
||||
|
||||
|
||||
def xform_correct(x, typing):
|
||||
# type: (XForm, VarTyping) -> bool
|
||||
"""
|
||||
Given an XForm x and a concrete variable typing for x check whether x is
|
||||
semantically preserving for the concrete typing.
|
||||
"""
|
||||
assert x.ti.permits(typing)
|
||||
|
||||
# Create copies of the x.src and x.dst with their concrete types
|
||||
src_m = {v: Var(v.name, typing[v]) for v in x.src.vars()} # type: VarAtomMap # noqa
|
||||
src = x.src.copy(src_m)
|
||||
dst = x.apply(src)
|
||||
dst_m = x.dst.substitution(dst, {})
|
||||
|
||||
# Build maps for the inputs/outputs for src->dst
|
||||
inp_m = {} # type: VarAtomMap
|
||||
out_m = {} # type: VarAtomMap
|
||||
|
||||
for v in x.src.vars():
|
||||
src_v = src_m[v]
|
||||
assert isinstance(src_v, Var)
|
||||
if v.is_input():
|
||||
inp_m[src_v] = dst_m[v]
|
||||
elif v.is_output():
|
||||
out_m[src_v] = dst_m[v]
|
||||
|
||||
# Get the primitive semantic Rtls for src and dst
|
||||
prim_src = elaborate(src)
|
||||
prim_dst = elaborate(dst)
|
||||
asserts = equivalent(prim_src, prim_dst, inp_m, out_m)
|
||||
|
||||
s = Solver()
|
||||
s.add(*asserts)
|
||||
return s.check() == unsat
|
||||
392
cranelift/codegen/meta-python/semantics/test_elaborate.py
Normal file
392
cranelift/codegen/meta-python/semantics/test_elaborate.py
Normal file
@@ -0,0 +1,392 @@
|
||||
from __future__ import absolute_import
|
||||
from base.instructions import vselect, vsplit, vconcat, iconst, iadd, bint
|
||||
from base.instructions import b1, icmp, ireduce, iadd_cout
|
||||
from base.immediates import intcc, imm64
|
||||
from base.types import i64, i8, b32, i32, i16, f32
|
||||
from cdsl.typevar import TypeVar
|
||||
from cdsl.ast import Var
|
||||
from cdsl.xform import Rtl
|
||||
from unittest import TestCase
|
||||
from .elaborate import elaborate
|
||||
from .primitives import prim_to_bv, bvsplit, prim_from_bv, bvconcat, bvadd, \
|
||||
bvult, bv_from_imm64, bvite
|
||||
import base.semantics # noqa
|
||||
|
||||
|
||||
def concrete_rtls_eq(r1, r2):
|
||||
# type: (Rtl, Rtl) -> bool
|
||||
"""
|
||||
Check whether 2 concrete Rtls are equivalent. That is:
|
||||
1) They are structurally the same (i.e. there is a substitution between
|
||||
them)
|
||||
2) Corresponding Vars between them have the same singleton type.
|
||||
"""
|
||||
assert r1.is_concrete()
|
||||
assert r2.is_concrete()
|
||||
|
||||
s = r1.substitution(r2, {})
|
||||
|
||||
if s is None:
|
||||
return False
|
||||
|
||||
for (v, v1) in s.items():
|
||||
if v.get_typevar().singleton_type() !=\
|
||||
v1.get_typevar().singleton_type():
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class TestCleanupConcreteRtl(TestCase):
|
||||
"""
|
||||
Test cleanup_concrete_rtl(). cleanup_concrete_rtl() should take Rtls for
|
||||
which we can infer a single concrete typing, and update the TypeVars
|
||||
in-place to singleton TVs.
|
||||
"""
|
||||
def test_cleanup_concrete_rtl(self):
|
||||
# type: () -> None
|
||||
typ = i64.by(4)
|
||||
x = Var('x')
|
||||
lo = Var('lo')
|
||||
hi = Var('hi')
|
||||
|
||||
r = Rtl(
|
||||
(lo, hi) << vsplit(x),
|
||||
)
|
||||
r1 = r.copy({})
|
||||
s = r.substitution(r1, {})
|
||||
|
||||
s[x].set_typevar(TypeVar.singleton(typ))
|
||||
r1.cleanup_concrete_rtl()
|
||||
assert s is not None
|
||||
assert s[x].get_typevar().singleton_type() == typ
|
||||
assert s[lo].get_typevar().singleton_type() == i64.by(2)
|
||||
assert s[hi].get_typevar().singleton_type() == i64.by(2)
|
||||
|
||||
def test_cleanup_concrete_rtl_fail(self):
|
||||
# type: () -> None
|
||||
x = Var('x')
|
||||
lo = Var('lo')
|
||||
hi = Var('hi')
|
||||
r = Rtl(
|
||||
(lo, hi) << vsplit(x),
|
||||
)
|
||||
|
||||
with self.assertRaises(AssertionError):
|
||||
r.cleanup_concrete_rtl()
|
||||
|
||||
def test_cleanup_concrete_rtl_ireduce(self):
|
||||
# type: () -> None
|
||||
x = Var('x')
|
||||
y = Var('y')
|
||||
r = Rtl(
|
||||
y << ireduce(x),
|
||||
)
|
||||
r1 = r.copy({})
|
||||
s = r.substitution(r1, {})
|
||||
s[x].set_typevar(TypeVar.singleton(i8.by(2)))
|
||||
r1.cleanup_concrete_rtl()
|
||||
|
||||
assert s is not None
|
||||
assert s[x].get_typevar().singleton_type() == i8.by(2)
|
||||
assert s[y].get_typevar().singleton_type() == i8.by(2)
|
||||
|
||||
def test_cleanup_concrete_rtl_ireduce_bad(self):
|
||||
# type: () -> None
|
||||
x = Var('x')
|
||||
y = Var('y')
|
||||
x.set_typevar(TypeVar.singleton(i16.by(1)))
|
||||
r = Rtl(
|
||||
y << ireduce(x),
|
||||
)
|
||||
|
||||
with self.assertRaises(AssertionError):
|
||||
r.cleanup_concrete_rtl()
|
||||
|
||||
def test_vselect_icmpimm(self):
|
||||
# type: () -> None
|
||||
x = Var('x')
|
||||
y = Var('y')
|
||||
z = Var('z')
|
||||
w = Var('w')
|
||||
v = Var('v')
|
||||
zeroes = Var('zeroes')
|
||||
imm0 = Var("imm0")
|
||||
|
||||
r = Rtl(
|
||||
zeroes << iconst(imm0),
|
||||
y << icmp(intcc.eq, x, zeroes),
|
||||
v << vselect(y, z, w),
|
||||
)
|
||||
r1 = r.copy({})
|
||||
|
||||
s = r.substitution(r1, {})
|
||||
s[zeroes].set_typevar(TypeVar.singleton(i32.by(4)))
|
||||
s[z].set_typevar(TypeVar.singleton(f32.by(4)))
|
||||
|
||||
r1.cleanup_concrete_rtl()
|
||||
|
||||
assert s is not None
|
||||
assert s[zeroes].get_typevar().singleton_type() == i32.by(4)
|
||||
assert s[x].get_typevar().singleton_type() == i32.by(4)
|
||||
assert s[y].get_typevar().singleton_type() == b32.by(4)
|
||||
assert s[z].get_typevar().singleton_type() == f32.by(4)
|
||||
assert s[w].get_typevar().singleton_type() == f32.by(4)
|
||||
assert s[v].get_typevar().singleton_type() == f32.by(4)
|
||||
|
||||
def test_bint(self):
|
||||
# type: () -> None
|
||||
x = Var('x')
|
||||
y = Var('y')
|
||||
z = Var('z')
|
||||
w = Var('w')
|
||||
v = Var('v')
|
||||
u = Var('u')
|
||||
|
||||
r = Rtl(
|
||||
z << iadd(x, y),
|
||||
w << bint(v),
|
||||
u << iadd(z, w)
|
||||
)
|
||||
r1 = r.copy({})
|
||||
s = r.substitution(r1, {})
|
||||
|
||||
s[x].set_typevar(TypeVar.singleton(i32.by(8)))
|
||||
s[z].set_typevar(TypeVar.singleton(i32.by(8)))
|
||||
# TODO: Relax this to simd=True
|
||||
s[v].set_typevar(TypeVar('v', '', bools=(1, 1), simd=(8, 8)))
|
||||
r1.cleanup_concrete_rtl()
|
||||
|
||||
assert s is not None
|
||||
assert s[x].get_typevar().singleton_type() == i32.by(8)
|
||||
assert s[y].get_typevar().singleton_type() == i32.by(8)
|
||||
assert s[z].get_typevar().singleton_type() == i32.by(8)
|
||||
assert s[w].get_typevar().singleton_type() == i32.by(8)
|
||||
assert s[u].get_typevar().singleton_type() == i32.by(8)
|
||||
assert s[v].get_typevar().singleton_type() == b1.by(8)
|
||||
|
||||
|
||||
class TestElaborate(TestCase):
|
||||
"""
|
||||
Test semantics elaboration.
|
||||
"""
|
||||
def setUp(self):
|
||||
# type: () -> None
|
||||
self.v0 = Var("v0")
|
||||
self.v1 = Var("v1")
|
||||
self.v2 = Var("v2")
|
||||
self.v3 = Var("v3")
|
||||
self.v4 = Var("v4")
|
||||
self.v5 = Var("v5")
|
||||
self.v6 = Var("v6")
|
||||
self.v7 = Var("v7")
|
||||
self.v8 = Var("v8")
|
||||
self.v9 = Var("v9")
|
||||
self.imm0 = Var("imm0")
|
||||
self.IxN_nonscalar = TypeVar("IxN_nonscalar", "", ints=True,
|
||||
scalars=False, simd=True)
|
||||
self.TxN = TypeVar("TxN", "", ints=True, bools=True, floats=True,
|
||||
scalars=False, simd=True)
|
||||
self.b1 = TypeVar.singleton(b1)
|
||||
|
||||
def test_elaborate_vsplit(self):
|
||||
# type: () -> None
|
||||
i32.by(4) # Make sure i32x4 exists.
|
||||
i32.by(2) # Make sure i32x2 exists.
|
||||
r = Rtl(
|
||||
(self.v0, self.v1) << vsplit.i32x4(self.v2),
|
||||
)
|
||||
r.cleanup_concrete_rtl()
|
||||
sem = elaborate(r)
|
||||
bvx = Var('bvx')
|
||||
bvlo = Var('bvlo')
|
||||
bvhi = Var('bvhi')
|
||||
x = Var('x')
|
||||
lo = Var('lo')
|
||||
hi = Var('hi')
|
||||
|
||||
exp = Rtl(
|
||||
bvx << prim_to_bv.i32x4(x),
|
||||
(bvlo, bvhi) << bvsplit.bv128(bvx),
|
||||
lo << prim_from_bv.i32x2(bvlo),
|
||||
hi << prim_from_bv.i32x2(bvhi)
|
||||
)
|
||||
exp.cleanup_concrete_rtl()
|
||||
|
||||
assert concrete_rtls_eq(sem, exp)
|
||||
|
||||
def test_elaborate_vconcat(self):
|
||||
# type: () -> None
|
||||
i32.by(4) # Make sure i32x4 exists.
|
||||
i32.by(2) # Make sure i32x2 exists.
|
||||
r = Rtl(
|
||||
self.v0 << vconcat.i32x2(self.v1, self.v2),
|
||||
)
|
||||
r.cleanup_concrete_rtl()
|
||||
sem = elaborate(r)
|
||||
bvx = Var('bvx')
|
||||
bvlo = Var('bvlo')
|
||||
bvhi = Var('bvhi')
|
||||
x = Var('x')
|
||||
lo = Var('lo')
|
||||
hi = Var('hi')
|
||||
|
||||
exp = Rtl(
|
||||
bvlo << prim_to_bv.i32x2(lo),
|
||||
bvhi << prim_to_bv.i32x2(hi),
|
||||
bvx << bvconcat.bv64(bvlo, bvhi),
|
||||
x << prim_from_bv.i32x4(bvx)
|
||||
)
|
||||
exp.cleanup_concrete_rtl()
|
||||
|
||||
assert concrete_rtls_eq(sem, exp)
|
||||
|
||||
def test_elaborate_iadd_simple(self):
|
||||
# type: () -> None
|
||||
i32.by(2) # Make sure i32x2 exists.
|
||||
x = Var('x')
|
||||
y = Var('y')
|
||||
a = Var('a')
|
||||
bvx = Var('bvx')
|
||||
bvy = Var('bvy')
|
||||
bva = Var('bva')
|
||||
r = Rtl(
|
||||
a << iadd.i32(x, y),
|
||||
)
|
||||
r.cleanup_concrete_rtl()
|
||||
sem = elaborate(r)
|
||||
exp = Rtl(
|
||||
bvx << prim_to_bv.i32(x),
|
||||
bvy << prim_to_bv.i32(y),
|
||||
bva << bvadd.bv32(bvx, bvy),
|
||||
a << prim_from_bv.i32(bva)
|
||||
)
|
||||
exp.cleanup_concrete_rtl()
|
||||
|
||||
assert concrete_rtls_eq(sem, exp)
|
||||
|
||||
def test_elaborate_iadd_elaborate_1(self):
|
||||
# type: () -> None
|
||||
i32.by(2) # Make sure i32x2 exists.
|
||||
r = Rtl(
|
||||
self.v0 << iadd.i32x2(self.v1, self.v2),
|
||||
)
|
||||
r.cleanup_concrete_rtl()
|
||||
sem = elaborate(r)
|
||||
x = Var('x')
|
||||
y = Var('y')
|
||||
a = Var('a')
|
||||
bvx_1 = Var('bvx_1')
|
||||
bvx_2 = Var('bvx_2')
|
||||
bvx_5 = Var('bvx_5')
|
||||
bvlo_1 = Var('bvlo_1')
|
||||
bvlo_2 = Var('bvlo_2')
|
||||
bvhi_1 = Var('bvhi_1')
|
||||
bvhi_2 = Var('bvhi_2')
|
||||
|
||||
bva_3 = Var('bva_3')
|
||||
bva_4 = Var('bva_4')
|
||||
|
||||
exp = Rtl(
|
||||
bvx_1 << prim_to_bv.i32x2(x),
|
||||
(bvlo_1, bvhi_1) << bvsplit.bv64(bvx_1),
|
||||
bvx_2 << prim_to_bv.i32x2(y),
|
||||
(bvlo_2, bvhi_2) << bvsplit.bv64(bvx_2),
|
||||
bva_3 << bvadd.bv32(bvlo_1, bvlo_2),
|
||||
bva_4 << bvadd.bv32(bvhi_1, bvhi_2),
|
||||
bvx_5 << bvconcat.bv32(bva_3, bva_4),
|
||||
a << prim_from_bv.i32x2(bvx_5)
|
||||
)
|
||||
exp.cleanup_concrete_rtl()
|
||||
|
||||
assert concrete_rtls_eq(sem, exp)
|
||||
|
||||
def test_elaborate_iadd_elaborate_2(self):
|
||||
# type: () -> None
|
||||
i8.by(4) # Make sure i32x2 exists.
|
||||
r = Rtl(
|
||||
self.v0 << iadd.i8x4(self.v1, self.v2),
|
||||
)
|
||||
r.cleanup_concrete_rtl()
|
||||
|
||||
sem = elaborate(r)
|
||||
x = Var('x')
|
||||
y = Var('y')
|
||||
a = Var('a')
|
||||
bvx_1 = Var('bvx_1')
|
||||
bvx_2 = Var('bvx_2')
|
||||
bvx_5 = Var('bvx_5')
|
||||
bvx_10 = Var('bvx_10')
|
||||
bvx_15 = Var('bvx_15')
|
||||
|
||||
bvlo_1 = Var('bvlo_1')
|
||||
bvlo_2 = Var('bvlo_2')
|
||||
bvlo_6 = Var('bvlo_6')
|
||||
bvlo_7 = Var('bvlo_7')
|
||||
bvlo_11 = Var('bvlo_11')
|
||||
bvlo_12 = Var('bvlo_12')
|
||||
|
||||
bvhi_1 = Var('bvhi_1')
|
||||
bvhi_2 = Var('bvhi_2')
|
||||
bvhi_6 = Var('bvhi_6')
|
||||
bvhi_7 = Var('bvhi_7')
|
||||
bvhi_11 = Var('bvhi_11')
|
||||
bvhi_12 = Var('bvhi_12')
|
||||
|
||||
bva_8 = Var('bva_8')
|
||||
bva_9 = Var('bva_9')
|
||||
bva_13 = Var('bva_13')
|
||||
bva_14 = Var('bva_14')
|
||||
|
||||
exp = Rtl(
|
||||
bvx_1 << prim_to_bv.i8x4(x),
|
||||
(bvlo_1, bvhi_1) << bvsplit.bv32(bvx_1),
|
||||
bvx_2 << prim_to_bv.i8x4(y),
|
||||
(bvlo_2, bvhi_2) << bvsplit.bv32(bvx_2),
|
||||
(bvlo_6, bvhi_6) << bvsplit.bv16(bvlo_1),
|
||||
(bvlo_7, bvhi_7) << bvsplit.bv16(bvlo_2),
|
||||
bva_8 << bvadd.bv8(bvlo_6, bvlo_7),
|
||||
bva_9 << bvadd.bv8(bvhi_6, bvhi_7),
|
||||
bvx_10 << bvconcat.bv8(bva_8, bva_9),
|
||||
(bvlo_11, bvhi_11) << bvsplit.bv16(bvhi_1),
|
||||
(bvlo_12, bvhi_12) << bvsplit.bv16(bvhi_2),
|
||||
bva_13 << bvadd.bv8(bvlo_11, bvlo_12),
|
||||
bva_14 << bvadd.bv8(bvhi_11, bvhi_12),
|
||||
bvx_15 << bvconcat.bv8(bva_13, bva_14),
|
||||
bvx_5 << bvconcat.bv16(bvx_10, bvx_15),
|
||||
a << prim_from_bv.i8x4(bvx_5)
|
||||
)
|
||||
exp.cleanup_concrete_rtl()
|
||||
assert concrete_rtls_eq(sem, exp)
|
||||
|
||||
def test_elaborate_iadd_cout_simple(self):
|
||||
# type: () -> None
|
||||
x = Var('x')
|
||||
y = Var('y')
|
||||
a = Var('a')
|
||||
c_out = Var('c_out')
|
||||
bvc_out = Var('bvc_out')
|
||||
bc_out = Var('bc_out')
|
||||
bvx = Var('bvx')
|
||||
bvy = Var('bvy')
|
||||
bva = Var('bva')
|
||||
bvone = Var('bvone')
|
||||
bvzero = Var('bvzero')
|
||||
r = Rtl(
|
||||
(a, c_out) << iadd_cout.i32(x, y),
|
||||
)
|
||||
r.cleanup_concrete_rtl()
|
||||
sem = elaborate(r)
|
||||
exp = Rtl(
|
||||
bvx << prim_to_bv.i32(x),
|
||||
bvy << prim_to_bv.i32(y),
|
||||
bva << bvadd.bv32(bvx, bvy),
|
||||
bc_out << bvult.bv32(bva, bvx),
|
||||
bvone << bv_from_imm64(imm64(1)),
|
||||
bvzero << bv_from_imm64(imm64(0)),
|
||||
bvc_out << bvite(bc_out, bvone, bvzero),
|
||||
a << prim_from_bv.i32(bva),
|
||||
c_out << prim_from_bv.b1(bvc_out)
|
||||
)
|
||||
exp.cleanup_concrete_rtl()
|
||||
assert concrete_rtls_eq(sem, exp)
|
||||
277
cranelift/codegen/meta-python/srcgen.py
Normal file
277
cranelift/codegen/meta-python/srcgen.py
Normal file
@@ -0,0 +1,277 @@
|
||||
"""
|
||||
Source code generator.
|
||||
|
||||
The `srcgen` module contains generic helper routines and classes for generating
|
||||
source code.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
import sys
|
||||
import os
|
||||
from collections import OrderedDict
|
||||
|
||||
try:
|
||||
from typing import Any, List, Set, Tuple # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class Formatter(object):
|
||||
"""
|
||||
Source code formatter class.
|
||||
|
||||
- Collect source code to be written to a file.
|
||||
- Keep track of indentation.
|
||||
|
||||
Indentation example:
|
||||
|
||||
>>> f = Formatter()
|
||||
>>> f.line('Hello line 1')
|
||||
>>> f.writelines()
|
||||
Hello line 1
|
||||
>>> f.indent_push()
|
||||
>>> f.comment('Nested comment')
|
||||
>>> f.indent_pop()
|
||||
>>> f.format('Back {} again', 'home')
|
||||
>>> f.writelines()
|
||||
Hello line 1
|
||||
// Nested comment
|
||||
Back home again
|
||||
|
||||
"""
|
||||
|
||||
shiftwidth = 4
|
||||
|
||||
def __init__(self):
|
||||
# type: () -> None
|
||||
self.indent = ''
|
||||
self.lines = [] # type: List[str]
|
||||
|
||||
def indent_push(self):
|
||||
# type: () -> None
|
||||
"""Increase current indentation level by one."""
|
||||
self.indent += ' ' * self.shiftwidth
|
||||
|
||||
def indent_pop(self):
|
||||
# type: () -> None
|
||||
"""Decrease indentation by one level."""
|
||||
assert self.indent != '', 'Already at top level indentation'
|
||||
self.indent = self.indent[0:-self.shiftwidth]
|
||||
|
||||
def line(self, s=None):
|
||||
# type: (str) -> None
|
||||
"""Add an indented line."""
|
||||
if s:
|
||||
self.lines.append('{}{}\n'.format(self.indent, s))
|
||||
else:
|
||||
self.lines.append('\n')
|
||||
|
||||
def outdented_line(self, s):
|
||||
# type: (str) -> None
|
||||
"""
|
||||
Emit a line outdented one level.
|
||||
|
||||
This is used for '} else {' and similar things inside a single indented
|
||||
block.
|
||||
"""
|
||||
self.lines.append('{}{}\n'.format(self.indent[0:-self.shiftwidth], s))
|
||||
|
||||
def writelines(self, f=None):
|
||||
# type: (Any) -> None
|
||||
"""Write all lines to `f`."""
|
||||
if not f:
|
||||
f = sys.stdout
|
||||
f.writelines(self.lines)
|
||||
|
||||
def update_file(self, filename, directory):
|
||||
# type: (str, str) -> None
|
||||
if directory is not None:
|
||||
filename = os.path.join(directory, filename)
|
||||
with open(filename, 'w') as f:
|
||||
self.writelines(f)
|
||||
|
||||
class _IndentedScope(object):
|
||||
def __init__(self, fmt, after):
|
||||
# type: (Formatter, str) -> None
|
||||
self.fmt = fmt
|
||||
self.after = after
|
||||
|
||||
def __enter__(self):
|
||||
# type: () -> None
|
||||
self.fmt.indent_push()
|
||||
|
||||
def __exit__(self, t, v, tb):
|
||||
# type: (object, object, object) -> None
|
||||
self.fmt.indent_pop()
|
||||
if self.after:
|
||||
self.fmt.line(self.after)
|
||||
|
||||
def indented(self, before=None, after=None):
|
||||
# type: (str, str) -> Formatter._IndentedScope
|
||||
"""
|
||||
Return a scope object for use with a `with` statement:
|
||||
|
||||
>>> f = Formatter()
|
||||
>>> with f.indented('prefix {', '} suffix'):
|
||||
... f.line('hello')
|
||||
>>> f.writelines()
|
||||
prefix {
|
||||
hello
|
||||
} suffix
|
||||
|
||||
The optional `before` and `after` parameters are surrounding lines
|
||||
which are *not* indented.
|
||||
"""
|
||||
if before:
|
||||
self.line(before)
|
||||
return Formatter._IndentedScope(self, after)
|
||||
|
||||
def format(self, fmt, *args):
|
||||
# type: (str, *Any) -> None
|
||||
self.line(fmt.format(*args))
|
||||
|
||||
def multi_line(self, s):
|
||||
# type: (str) -> None
|
||||
"""Add one or more lines after stripping common indentation."""
|
||||
for l in parse_multiline(s):
|
||||
self.line(l)
|
||||
|
||||
def comment(self, s):
|
||||
# type: (str) -> None
|
||||
"""Add a comment line."""
|
||||
self.line('// ' + s)
|
||||
|
||||
def doc_comment(self, s):
|
||||
# type: (str) -> None
|
||||
"""Add a (multi-line) documentation comment."""
|
||||
for l in parse_multiline(s):
|
||||
self.line('/// ' + l if l else '///')
|
||||
|
||||
def match(self, m):
|
||||
# type: (Match) -> None
|
||||
"""
|
||||
Add a match expression.
|
||||
|
||||
Example:
|
||||
|
||||
>>> f = Formatter()
|
||||
>>> m = Match('x')
|
||||
>>> m.arm('Orange', ['a', 'b'], 'some body')
|
||||
>>> m.arm('Yellow', ['a', 'b'], 'some body')
|
||||
>>> m.arm('Green', ['a', 'b'], 'different body')
|
||||
>>> m.arm('Blue', ['x', 'y'], 'some body')
|
||||
>>> f.match(m)
|
||||
>>> f.writelines()
|
||||
match x {
|
||||
Orange { a, b } |
|
||||
Yellow { a, b } => {
|
||||
some body
|
||||
}
|
||||
Green { a, b } => {
|
||||
different body
|
||||
}
|
||||
Blue { x, y } => {
|
||||
some body
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
with self.indented('match {} {{'.format(m.expr), '}'):
|
||||
for (fields, body), names in m.arms.items():
|
||||
with self.indented('', '}'):
|
||||
names_left = len(names)
|
||||
for name in names.keys():
|
||||
fields_str = ', '.join(fields)
|
||||
if len(fields) != 0:
|
||||
fields_str = '{{ {} }} '.format(fields_str)
|
||||
names_left -= 1
|
||||
if names_left > 0:
|
||||
suffix = '|'
|
||||
else:
|
||||
suffix = '=> {'
|
||||
self.outdented_line(name + ' ' + fields_str + suffix)
|
||||
if names_left == 0:
|
||||
self.multi_line(body)
|
||||
|
||||
|
||||
def _indent(s):
|
||||
# type: (str) -> int
|
||||
"""
|
||||
Compute the indentation of s, or None of an empty line.
|
||||
|
||||
Example:
|
||||
>>> _indent("foo")
|
||||
0
|
||||
>>> _indent(" bar")
|
||||
4
|
||||
>>> _indent(" ")
|
||||
>>> _indent("")
|
||||
"""
|
||||
t = s.lstrip()
|
||||
return len(s) - len(t) if t else None
|
||||
|
||||
|
||||
def parse_multiline(s):
|
||||
# type: (str) -> List[str]
|
||||
"""
|
||||
Given a multi-line string, split it into a sequence of lines after
|
||||
stripping a common indentation, as described in the "trim" function
|
||||
from PEP 257. This is useful for strings defined with doc strings:
|
||||
>>> parse_multiline('\\n hello\\n world\\n')
|
||||
['hello', 'world']
|
||||
"""
|
||||
if not s:
|
||||
return []
|
||||
# Convert tabs to spaces (following the normal Python rules)
|
||||
# and split into a list of lines:
|
||||
lines = s.expandtabs().splitlines()
|
||||
# Determine minimum indentation (first line doesn't count):
|
||||
indent = sys.maxsize
|
||||
for line in lines[1:]:
|
||||
stripped = line.lstrip()
|
||||
if stripped:
|
||||
indent = min(indent, len(line) - len(stripped))
|
||||
# Remove indentation (first line is special):
|
||||
trimmed = [lines[0].strip()]
|
||||
if indent < sys.maxsize:
|
||||
for line in lines[1:]:
|
||||
trimmed.append(line[indent:].rstrip())
|
||||
# Strip off trailing and leading blank lines:
|
||||
while trimmed and not trimmed[-1]:
|
||||
trimmed.pop()
|
||||
while trimmed and not trimmed[0]:
|
||||
trimmed.pop(0)
|
||||
return trimmed
|
||||
|
||||
|
||||
class Match(object):
|
||||
"""
|
||||
Match formatting class.
|
||||
|
||||
Match objects collect all the information needed to emit a Rust `match`
|
||||
expression, automatically deduplicating overlapping identical arms.
|
||||
|
||||
Example:
|
||||
|
||||
>>> m = Match('x')
|
||||
>>> m.arm('Orange', ['a', 'b'], 'some body')
|
||||
>>> m.arm('Yellow', ['a', 'b'], 'some body')
|
||||
>>> m.arm('Green', ['a', 'b'], 'different body')
|
||||
>>> m.arm('Blue', ['x', 'y'], 'some body')
|
||||
>>> assert(len(m.arms) == 3)
|
||||
|
||||
Note that this class is ignorant of Rust types, and considers two fields
|
||||
with the same name to be equivalent.
|
||||
"""
|
||||
|
||||
def __init__(self, expr):
|
||||
# type: (str) -> None
|
||||
self.expr = expr
|
||||
self.arms = OrderedDict() # type: OrderedDict[Tuple[Tuple[str, ...], str], OrderedDict[str, None]] # noqa
|
||||
|
||||
def arm(self, name, fields, body):
|
||||
# type: (str, List[str], str) -> None
|
||||
key = (tuple(fields), body)
|
||||
if key not in self.arms:
|
||||
self.arms[key] = OrderedDict()
|
||||
self.arms[key][name] = None
|
||||
151
cranelift/codegen/meta-python/stubs/z3/__init__.pyi
Normal file
151
cranelift/codegen/meta-python/stubs/z3/__init__.pyi
Normal file
@@ -0,0 +1,151 @@
|
||||
from typing import overload, Tuple, Any, List, Iterable, Union, TypeVar
|
||||
from .z3types import Ast, ContextObj
|
||||
|
||||
TExprRef = TypeVar("TExprRef", bound="ExprRef")
|
||||
|
||||
class Context:
|
||||
...
|
||||
|
||||
class Z3PPObject:
|
||||
...
|
||||
|
||||
class AstRef(Z3PPObject):
|
||||
@overload
|
||||
def __init__(self, ast: Ast, ctx: Context) -> None:
|
||||
self.ast: Ast = ...
|
||||
self.ctx: Context= ...
|
||||
|
||||
@overload
|
||||
def __init__(self, ast: Ast) -> None:
|
||||
self.ast: Ast = ...
|
||||
self.ctx: Context= ...
|
||||
def ctx_ref(self) -> ContextObj: ...
|
||||
def as_ast(self) -> Ast: ...
|
||||
def children(self) -> List[AstRef]: ...
|
||||
|
||||
class SortRef(AstRef):
|
||||
...
|
||||
|
||||
class FuncDeclRef(AstRef):
|
||||
def arity(self) -> int: ...
|
||||
def name(self) -> str: ...
|
||||
|
||||
class ExprRef(AstRef):
|
||||
def eq(self, other: ExprRef) -> ExprRef: ...
|
||||
def sort(self) -> SortRef: ...
|
||||
def decl(self) -> FuncDeclRef: ...
|
||||
|
||||
class BoolSortRef(SortRef):
|
||||
...
|
||||
|
||||
class BoolRef(ExprRef):
|
||||
...
|
||||
|
||||
|
||||
def is_true(a: BoolRef) -> bool: ...
|
||||
def is_false(a: BoolRef) -> bool: ...
|
||||
def is_int_value(a: AstRef) -> bool: ...
|
||||
def substitute(a: AstRef, *m: Tuple[AstRef, AstRef]) -> AstRef: ...
|
||||
|
||||
|
||||
class ArithSortRef(SortRef):
|
||||
...
|
||||
|
||||
class ArithRef(ExprRef):
|
||||
def __neg__(self) -> ExprRef: ...
|
||||
def __le__(self, other: ArithRef) -> ArithRef: ...
|
||||
def __lt__(self, other: ArithRef) -> ArithRef: ...
|
||||
def __ge__(self, other: ArithRef) -> ArithRef: ...
|
||||
def __gt__(self, other: ArithRef) -> ArithRef: ...
|
||||
def __add__(self, other: ArithRef) -> ArithRef: ...
|
||||
def __sub__(self, other: ArithRef) -> ArithRef: ...
|
||||
def __mul__(self, other: ArithRef) -> ArithRef: ...
|
||||
def __div__(self, other: ArithRef) -> ArithRef: ...
|
||||
def __mod__(self, other: ArithRef) -> ArithRef: ...
|
||||
|
||||
class IntNumRef(ArithRef):
|
||||
def as_long(self) -> int: ...
|
||||
|
||||
class BitVecRef(ExprRef):
|
||||
def __neg__(self) -> ExprRef: ...
|
||||
def __le__(self, other: BitVecRef) -> ExprRef: ...
|
||||
def __lt__(self, other: BitVecRef) -> ExprRef: ...
|
||||
def __ge__(self, other: BitVecRef) -> ExprRef: ...
|
||||
def __gt__(self, other: BitVecRef) -> ExprRef: ...
|
||||
def __add__(self, other: BitVecRef) -> BitVecRef: ...
|
||||
def __sub__(self, other: BitVecRef) -> BitVecRef: ...
|
||||
def __mul__(self, other: BitVecRef) -> BitVecRef: ...
|
||||
def __div__(self, other: BitVecRef) -> BitVecRef: ...
|
||||
def __mod__(self, other: BitVecRef) -> BitVecRef: ...
|
||||
|
||||
class BitVecNumRef(BitVecRef):
|
||||
def as_long(self) -> int: ...
|
||||
|
||||
class CheckSatResult: ...
|
||||
|
||||
class ModelRef(Z3PPObject):
|
||||
def __getitem__(self, k: FuncDeclRef) -> IntNumRef: ...
|
||||
def decls(self) -> Iterable[FuncDeclRef]: ...
|
||||
|
||||
class Solver(Z3PPObject):
|
||||
@overload
|
||||
def __init__(self) -> None:
|
||||
self.ctx: Context = ...
|
||||
@overload
|
||||
def __init__(self, ctx:Context) -> None:
|
||||
self.ctx: Context = ...
|
||||
|
||||
def add(self, e:ExprRef) -> None: ...
|
||||
def to_smt2(self) -> str: ...
|
||||
def check(self) -> CheckSatResult: ...
|
||||
def push(self) -> None: ...
|
||||
def pop(self) -> None: ...
|
||||
def model(self) -> ModelRef: ...
|
||||
|
||||
sat: CheckSatResult = ...
|
||||
unsat: CheckSatResult = ...
|
||||
|
||||
@overload
|
||||
def Int(name: str) -> ArithRef: ...
|
||||
@overload
|
||||
def Int(name: str, ctx: Context) -> ArithRef: ...
|
||||
|
||||
@overload
|
||||
def Bool(name: str) -> BoolRef: ...
|
||||
@overload
|
||||
def Bool(name: str, ctx: Context) -> BoolRef: ...
|
||||
|
||||
def BitVec(name: str, width: int) -> BitVecRef: ...
|
||||
|
||||
@overload
|
||||
def parse_smt2_string(s: str) -> ExprRef: ...
|
||||
@overload
|
||||
def parse_smt2_string(s: str, ctx: Context) -> ExprRef: ...
|
||||
|
||||
# Can't give more precise types here since func signature is
|
||||
# a vararg list of ExprRef optionally followed by a Context
|
||||
def Or(*args: Union[ExprRef, Context]) -> ExprRef: ...
|
||||
def And(*args: Union[ExprRef, Context]) -> ExprRef: ...
|
||||
@overload
|
||||
def Not(p: ExprRef) -> ExprRef: ...
|
||||
@overload
|
||||
def Not(p: ExprRef, ctx: Context) -> ExprRef: ...
|
||||
def Implies(a: ExprRef, b: ExprRef, ctx:Context) -> ExprRef: ...
|
||||
def If(a: ExprRef, b:TExprRef, c:TExprRef) -> TExprRef: ...
|
||||
|
||||
def ZeroExt(width: int, expr: BitVecRef) -> BitVecRef: ...
|
||||
def SignExt(width: int, expr: BitVecRef) -> BitVecRef: ...
|
||||
def Extract(hi: int, lo: int, expr: BitVecRef) -> BitVecRef: ...
|
||||
def Concat(expr1: BitVecRef, expr2: BitVecRef) -> BitVecRef: ...
|
||||
|
||||
def Function(name: str, *sig: Tuple[SortRef,...]) -> FuncDeclRef: ...
|
||||
|
||||
def IntVal(val: int, ctx: Context) -> IntNumRef: ...
|
||||
@overload
|
||||
def BoolVal(val: bool, ctx: Context) -> BoolRef: ...
|
||||
@overload
|
||||
def BoolVal(val: bool) -> BoolRef: ...
|
||||
@overload
|
||||
def BitVecVal(val: int, bits: int, ctx: Context) -> BitVecNumRef: ...
|
||||
@overload
|
||||
def BitVecVal(val: int, bits: int) -> BitVecNumRef: ...
|
||||
3
cranelift/codegen/meta-python/stubs/z3/z3core.pyi
Normal file
3
cranelift/codegen/meta-python/stubs/z3/z3core.pyi
Normal file
@@ -0,0 +1,3 @@
|
||||
from .z3types import Ast, ContextObj
|
||||
def Z3_mk_eq(ctx: ContextObj, a: Ast, b: Ast) -> Ast: ...
|
||||
def Z3_mk_div(ctx: ContextObj, a: Ast, b: Ast) -> Ast: ...
|
||||
12
cranelift/codegen/meta-python/stubs/z3/z3types.pyi
Normal file
12
cranelift/codegen/meta-python/stubs/z3/z3types.pyi
Normal file
@@ -0,0 +1,12 @@
|
||||
from typing import Any
|
||||
|
||||
class Z3Exception(Exception):
|
||||
def __init__(self, a: Any) -> None:
|
||||
self.value = a
|
||||
...
|
||||
|
||||
class ContextObj:
|
||||
...
|
||||
|
||||
class Ast:
|
||||
...
|
||||
8
cranelift/codegen/meta-python/test_constant_hash.py
Normal file
8
cranelift/codegen/meta-python/test_constant_hash.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from __future__ import absolute_import
|
||||
import doctest
|
||||
import constant_hash
|
||||
|
||||
|
||||
def load_tests(loader, tests, ignore):
|
||||
tests.addTests(doctest.DocTestSuite(constant_hash))
|
||||
return tests
|
||||
196
cranelift/codegen/meta-python/test_gen_legalizer.py
Normal file
196
cranelift/codegen/meta-python/test_gen_legalizer.py
Normal file
@@ -0,0 +1,196 @@
|
||||
import doctest
|
||||
import gen_legalizer
|
||||
from unittest import TestCase
|
||||
from srcgen import Formatter
|
||||
from gen_legalizer import get_runtime_typechecks, emit_runtime_typecheck
|
||||
from base.instructions import vselect, vsplit, isplit, iconcat, vconcat, \
|
||||
iconst, b1, icmp, copy, sextend, uextend, ireduce, fdemote, fpromote # noqa
|
||||
from base.legalize import narrow, expand # noqa
|
||||
from base.immediates import intcc # noqa
|
||||
from cdsl.typevar import TypeVar, TypeSet
|
||||
from cdsl.ast import Var, Def # noqa
|
||||
from cdsl.xform import Rtl, XForm # noqa
|
||||
from cdsl.ti import ti_rtl, subst, TypeEnv, get_type_env # noqa
|
||||
from unique_table import UniqueTable
|
||||
from functools import reduce
|
||||
|
||||
try:
|
||||
from typing import Callable, TYPE_CHECKING, Iterable, Any # noqa
|
||||
if TYPE_CHECKING:
|
||||
CheckProducer = Callable[[UniqueTable], str]
|
||||
except ImportError:
|
||||
TYPE_CHECKING = False
|
||||
|
||||
|
||||
def load_tests(loader, tests, ignore):
|
||||
# type: (Any, Any, Any) -> Any
|
||||
tests.addTests(doctest.DocTestSuite(gen_legalizer))
|
||||
return tests
|
||||
|
||||
|
||||
def format_check(typesets, s, *args):
|
||||
# type: (...) -> str
|
||||
def transform(x):
|
||||
# type: (Any) -> str
|
||||
if isinstance(x, TypeSet):
|
||||
return str(typesets.index[x])
|
||||
elif isinstance(x, TypeVar):
|
||||
assert not x.is_derived
|
||||
return x.name
|
||||
else:
|
||||
return str(x)
|
||||
|
||||
dummy_s = s # type: str
|
||||
args = tuple(map(lambda x: transform(x), args))
|
||||
return dummy_s.format(*args)
|
||||
|
||||
|
||||
def typeset_check(v, ts):
|
||||
# type: (Var, TypeSet) -> CheckProducer
|
||||
return lambda typesets: format_check(
|
||||
typesets,
|
||||
'let predicate = predicate && TYPE_SETS[{}].contains(typeof_{});\n',
|
||||
ts, v)
|
||||
|
||||
|
||||
def equiv_check(tv1, tv2):
|
||||
# type: (str, str) -> CheckProducer
|
||||
return lambda typesets: format_check(
|
||||
typesets,
|
||||
'let predicate = predicate && match ({}, {}) {{\n'
|
||||
' (Some(a), Some(b)) => a == b,\n'
|
||||
' _ => false,\n'
|
||||
'}};\n', tv1, tv2)
|
||||
|
||||
|
||||
def wider_check(tv1, tv2):
|
||||
# type: (str, str) -> CheckProducer
|
||||
return lambda typesets: format_check(
|
||||
typesets,
|
||||
'let predicate = predicate && match ({}, {}) {{\n'
|
||||
' (Some(a), Some(b)) => a.wider_or_equal(b),\n'
|
||||
' _ => false,\n'
|
||||
'}};\n', tv1, tv2)
|
||||
|
||||
|
||||
def sequence(*args):
|
||||
# type: (...) -> CheckProducer
|
||||
dummy = args # type: Iterable[CheckProducer]
|
||||
|
||||
def sequenceF(typesets):
|
||||
# type: (UniqueTable) -> str
|
||||
def strconcat(acc, el):
|
||||
# type: (str, CheckProducer) -> str
|
||||
return acc + el(typesets)
|
||||
|
||||
return reduce(strconcat, dummy, "")
|
||||
return sequenceF
|
||||
|
||||
|
||||
class TestRuntimeChecks(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
# type: () -> None
|
||||
self.v0 = Var("v0")
|
||||
self.v1 = Var("v1")
|
||||
self.v2 = Var("v2")
|
||||
self.v3 = Var("v3")
|
||||
self.v4 = Var("v4")
|
||||
self.v5 = Var("v5")
|
||||
self.v6 = Var("v6")
|
||||
self.v7 = Var("v7")
|
||||
self.v8 = Var("v8")
|
||||
self.v9 = Var("v9")
|
||||
self.imm0 = Var("imm0")
|
||||
self.IxN_nonscalar = TypeVar("IxN_nonscalar", "", ints=True,
|
||||
scalars=False, simd=True)
|
||||
self.TxN = TypeVar("TxN", "", ints=True, bools=True, floats=True,
|
||||
scalars=False, simd=True)
|
||||
self.b1 = TypeVar.singleton(b1)
|
||||
|
||||
def check_yo_check(self, xform, expected_f):
|
||||
# type: (XForm, CheckProducer) -> None
|
||||
fmt = Formatter()
|
||||
type_sets = UniqueTable()
|
||||
for check in get_runtime_typechecks(xform):
|
||||
emit_runtime_typecheck(check, fmt, type_sets)
|
||||
|
||||
# Remove comments
|
||||
got = "".join([l for l in fmt.lines if not l.strip().startswith("//")])
|
||||
expected = expected_f(type_sets)
|
||||
self.assertEqual(got, expected)
|
||||
|
||||
def test_width_check(self):
|
||||
# type: () -> None
|
||||
x = XForm(Rtl(self.v0 << copy(self.v1)),
|
||||
Rtl((self.v2, self.v3) << isplit(self.v1),
|
||||
self.v0 << iconcat(self.v2, self.v3)))
|
||||
|
||||
WideInt = TypeSet(lanes=(1, 256), ints=(16, 64))
|
||||
self.check_yo_check(x, typeset_check(self.v1, WideInt))
|
||||
|
||||
def test_lanes_check(self):
|
||||
# type: () -> None
|
||||
x = XForm(Rtl(self.v0 << copy(self.v1)),
|
||||
Rtl((self.v2, self.v3) << vsplit(self.v1),
|
||||
self.v0 << vconcat(self.v2, self.v3)))
|
||||
|
||||
WideVec = TypeSet(lanes=(2, 256), ints=(8, 64), floats=(32, 64),
|
||||
bools=(1, 64))
|
||||
self.check_yo_check(x, typeset_check(self.v1, WideVec))
|
||||
|
||||
def test_vselect_imm(self):
|
||||
# type: () -> None
|
||||
ts = TypeSet(lanes=(2, 256), ints=True, floats=True, bools=(8, 64))
|
||||
r = Rtl(
|
||||
self.v0 << iconst(self.imm0),
|
||||
self.v1 << icmp(intcc.eq, self.v2, self.v0),
|
||||
self.v5 << vselect(self.v1, self.v3, self.v4),
|
||||
)
|
||||
x = XForm(r, r)
|
||||
tv2_exp = 'Some({}).map(|t: crate::ir::Type| t.as_bool())'\
|
||||
.format(self.v2.get_typevar().name)
|
||||
tv3_exp = 'Some({}).map(|t: crate::ir::Type| t.as_bool())'\
|
||||
.format(self.v3.get_typevar().name)
|
||||
|
||||
self.check_yo_check(
|
||||
x, sequence(typeset_check(self.v3, ts),
|
||||
equiv_check(tv2_exp, tv3_exp)))
|
||||
|
||||
def test_reduce_extend(self):
|
||||
# type: () -> None
|
||||
r = Rtl(
|
||||
self.v1 << uextend(self.v0),
|
||||
self.v2 << ireduce(self.v1),
|
||||
self.v3 << sextend(self.v2),
|
||||
)
|
||||
x = XForm(r, r)
|
||||
|
||||
tv0_exp = 'Some({})'.format(self.v0.get_typevar().name)
|
||||
tv1_exp = 'Some({})'.format(self.v1.get_typevar().name)
|
||||
tv2_exp = 'Some({})'.format(self.v2.get_typevar().name)
|
||||
tv3_exp = 'Some({})'.format(self.v3.get_typevar().name)
|
||||
|
||||
self.check_yo_check(
|
||||
x, sequence(wider_check(tv1_exp, tv0_exp),
|
||||
wider_check(tv1_exp, tv2_exp),
|
||||
wider_check(tv3_exp, tv2_exp)))
|
||||
|
||||
def test_demote_promote(self):
|
||||
# type: () -> None
|
||||
r = Rtl(
|
||||
self.v1 << fpromote(self.v0),
|
||||
self.v2 << fdemote(self.v1),
|
||||
self.v3 << fpromote(self.v2),
|
||||
)
|
||||
x = XForm(r, r)
|
||||
|
||||
tv0_exp = 'Some({})'.format(self.v0.get_typevar().name)
|
||||
tv1_exp = 'Some({})'.format(self.v1.get_typevar().name)
|
||||
tv2_exp = 'Some({})'.format(self.v2.get_typevar().name)
|
||||
tv3_exp = 'Some({})'.format(self.v3.get_typevar().name)
|
||||
|
||||
self.check_yo_check(
|
||||
x, sequence(wider_check(tv1_exp, tv0_exp),
|
||||
wider_check(tv1_exp, tv2_exp),
|
||||
wider_check(tv3_exp, tv2_exp)))
|
||||
8
cranelift/codegen/meta-python/test_srcgen.py
Normal file
8
cranelift/codegen/meta-python/test_srcgen.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from __future__ import absolute_import
|
||||
import doctest
|
||||
import srcgen
|
||||
|
||||
|
||||
def load_tests(loader, tests, ignore):
|
||||
tests.addTests(doctest.DocTestSuite(srcgen))
|
||||
return tests
|
||||
80
cranelift/codegen/meta-python/unique_table.py
Normal file
80
cranelift/codegen/meta-python/unique_table.py
Normal file
@@ -0,0 +1,80 @@
|
||||
"""
|
||||
Generate a table of unique items.
|
||||
|
||||
The `UniqueTable` class collects items into an array, removing duplicates. Each
|
||||
item is mapped to its offset in the final array.
|
||||
|
||||
This is a compression technique for compile-time generated tables.
|
||||
"""
|
||||
|
||||
try:
|
||||
from typing import Any, List, Dict, Tuple, Sequence # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class UniqueTable:
|
||||
"""
|
||||
Collect items into the `table` list, removing duplicates.
|
||||
"""
|
||||
def __init__(self):
|
||||
# type: () -> None
|
||||
# List of items added in order.
|
||||
self.table = list() # type: List[Any]
|
||||
# Map item -> index.
|
||||
self.index = dict() # type: Dict[Any, int]
|
||||
|
||||
def add(self, item):
|
||||
# type: (Any) -> int
|
||||
"""
|
||||
Add a single item to the table if it isn't already there.
|
||||
|
||||
Return the offset into `self.table` of the item.
|
||||
"""
|
||||
if item in self.index:
|
||||
return self.index[item]
|
||||
|
||||
idx = len(self.table)
|
||||
self.index[item] = idx
|
||||
self.table.append(item)
|
||||
return idx
|
||||
|
||||
|
||||
class UniqueSeqTable:
|
||||
"""
|
||||
Collect sequences into the `table` list, removing duplicates.
|
||||
|
||||
Sequences don't have to be of the same length.
|
||||
"""
|
||||
def __init__(self):
|
||||
# type: () -> None
|
||||
self.table = list() # type: List[Any]
|
||||
# Map seq -> index.
|
||||
self.index = dict() # type: Dict[Tuple[Any, ...], int]
|
||||
|
||||
def add(self, seq):
|
||||
# type: (Sequence[Any]) -> int
|
||||
"""
|
||||
Add a sequence of items to the table. If the table already contains the
|
||||
items in `seq` in the same order, use those instead.
|
||||
|
||||
Return the offset into `self.table` of the beginning of `seq`.
|
||||
"""
|
||||
if len(seq) == 0:
|
||||
return 0
|
||||
tseq = tuple(seq)
|
||||
if tseq in self.index:
|
||||
return self.index[tseq]
|
||||
|
||||
idx = len(self.table)
|
||||
self.table.extend(tseq)
|
||||
|
||||
# Add seq and all sub-sequences to `index`.
|
||||
index = self.index # type: Dict[Tuple[Any, ...], int]
|
||||
assert index is not None
|
||||
for length in range(1, len(tseq) + 1):
|
||||
for offset in range(len(tseq) - length + 1):
|
||||
key = tseq[offset:offset+length]
|
||||
index[key] = idx + offset
|
||||
|
||||
return idx
|
||||
Reference in New Issue
Block a user