[meta] Remove the Python DSL

KILL THE SNAKE WITH FIRE.
This commit is contained in:
Benjamin Bouvier
2019-06-20 17:04:55 +02:00
parent 88307f693a
commit f29a26de14
75 changed files with 1 additions and 17405 deletions

View File

@@ -84,14 +84,6 @@ build.
[Rust Update Policy for Firefox]: https://wiki.mozilla.org/Rust_Update_Policy_for_Firefox#Schedule [Rust Update Policy for Firefox]: https://wiki.mozilla.org/Rust_Update_Policy_for_Firefox#Schedule
### Python
Our Python code is checked with [mypy](http://mypy-lang.org/) and
[flake8](http://flake8.pycqa.org/en/latest/); see the
[check.sh](https://github.com/CraneStation/cranelift/blob/master/cranelift-codegen/meta-python/check.sh)
file for details. The versions available in common package repositories such
as Ubuntu or Homebrew typically work fine.
## Development Process ## Development Process
We use [issues] for asking questions and tracking bugs and unimplemented We use [issues] for asking questions and tracking bugs and unimplemented

View File

@@ -53,8 +53,7 @@ needed before it would be ready for a production use case.
Cranelift's APIs are not yet stable. Cranelift's APIs are not yet stable.
Cranelift currently requires Rust 1.35 or later, and Python 2.7 or 3 Cranelift currently requires Rust 1.35 or later to build.
to build.
Planned uses Planned uses
------------ ------------

View File

@@ -57,32 +57,6 @@ fn main() {
crate_dir.join("build.rs").to_str().unwrap() crate_dir.join("build.rs").to_str().unwrap()
); );
// Scripts are in `$crate_dir/meta-python`.
let meta_dir = crate_dir.join("meta-python");
let build_script = meta_dir.join("build.py");
// Launch build script with Python. We'll just find python in the path.
// Use -B to disable .pyc files, because they cause trouble for vendoring
// scripts, and this is a build step that isn't run very often anyway.
let python = identify_python();
let status = process::Command::new(python)
.current_dir(crate_dir)
.arg("-B")
.arg(build_script)
.arg("--out-dir")
.arg(out_dir.clone())
.status()
.expect("Failed to launch second-level build script; is python installed?");
if !status.success() {
process::exit(status.code().unwrap());
}
// DEVELOPMENT:
// ------------------------------------------------------------------------
// Now that the Python build process is complete, generate files that are
// emitted by the `meta` crate.
// ------------------------------------------------------------------------
if let Err(err) = meta::generate(&isas, &out_dir) { if let Err(err) = meta::generate(&isas, &out_dir) {
eprintln!("Error: {}", err); eprintln!("Error: {}", err);
process::exit(1); process::exit(1);
@@ -99,16 +73,3 @@ fn main() {
println!("cargo:warning=Generated files are in {}", out_dir); println!("cargo:warning=Generated files are in {}", out_dir);
} }
} }
fn identify_python() -> &'static str {
for python in &["python", "python3", "python2.7"] {
if process::Command::new(python)
.arg("--version")
.status()
.is_ok()
{
return python;
}
}
panic!("The Cranelift build requires Python (version 2.7 or version 3)");
}

View File

@@ -1 +0,0 @@
"""Definitions for the base Cranelift language."""

View File

@@ -1,38 +0,0 @@
"""
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 signature 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.')

View File

@@ -1,91 +0,0 @@
"""
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))
CopyNop = InstructionFormat(
('src', entities.stack_slot), ('dst', entities.stack_slot))
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())

View File

@@ -1,123 +0,0 @@
"""
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',
})

File diff suppressed because it is too large Load Diff

View File

@@ -1,713 +0,0 @@
"""
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 [ishl, ishl_imm, ushr, ushr_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)
))
for op in [sshr, sshr_imm]:
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)
))
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 fcvt_from_{u,s}int for smaller integer types.
# These use expand and not widen because the controlling type variable for
# these instructions are f32/f64, which are legalized as part of the expand
# group.
for dest_ty in [types.f32, types.f64]:
for src_ty in [types.i8, types.i16]:
expand.legalize(
a << insts.fcvt_from_uint.bind(dest_ty).bind(src_ty)(b),
Rtl(
x << uextend.i32(b),
a << insts.fcvt_from_uint.bind(dest_ty).i32(x),
))
expand.legalize(
a << insts.fcvt_from_sint.bind(dest_ty).bind(src_ty)(b),
Rtl(
x << sextend.i32(b),
a << insts.fcvt_from_sint.bind(dest_ty).i32(x),
))
# 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 << ishl_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 << ishl_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 << ishl_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 << ishl_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)
))

View File

@@ -1,42 +0,0 @@
"""
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'))

View File

@@ -1,218 +0,0 @@
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),
))

View File

@@ -1,144 +0,0 @@
"""
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())

View File

@@ -1,49 +0,0 @@
"""
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.
""")

View File

@@ -1,51 +0,0 @@
# 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_build_deps
import gen_encoding
import gen_binemit
try:
from typing import List, Set # noqa
from cdsl.isa import TargetISA # noqa
from cdsl.instructions import InstructionGroup # noqa
except ImportError:
pass
def number_all_instructions(isas):
# type: (List[TargetISA]) -> None
seen = set() # type: Set[InstructionGroup]
num_inst = 1
for target_isa in isas:
for g in target_isa.instruction_groups:
if g not in seen:
for i in g.instructions:
i.number = num_inst
num_inst += 1
seen.add(g)
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()
number_all_instructions(isas)
gen_encoding.generate(isas, out_dir)
gen_binemit.generate(isas, out_dir)
gen_build_deps.generate()
if __name__ == "__main__":
main()

View File

@@ -1,59 +0,0 @@
"""
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

View File

@@ -1,581 +0,0 @@
"""
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):
# type: (str) -> 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 = None # 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 = None # 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)

View File

@@ -1,268 +0,0 @@
"""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)

View File

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

View File

@@ -1,455 +0,0 @@
"""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

View File

@@ -1,251 +0,0 @@
"""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']

View File

@@ -1,448 +0,0 @@
"""
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())

View File

@@ -1,413 +0,0 @@
"""
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)'

View File

@@ -1,416 +0,0 @@
"""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:
# Ensure that named predicates are ordered in a deterministic way
# that the Rust crate may simply reproduce, by pushing entries into
# a vector that we'll sort by name later.
named_predicates = []
for name, obj in globs.items():
if isinstance(obj, Setting):
assert obj.name is None, obj.name
obj.name = name
if isinstance(obj, Predicate):
named_predicates.append((name, obj))
if isinstance(obj, Preset):
assert obj.name is None, obj.name
obj.name = name
named_predicates.sort(key=lambda x: x[0])
for (name, obj) in named_predicates:
self.named_predicates[name] = obj
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

View File

@@ -1,28 +0,0 @@
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'))")

View File

@@ -1,8 +0,0 @@
from __future__ import absolute_import
import doctest
import cdsl
def load_tests(loader, tests, ignore):
tests.addTests(doctest.DocTestSuite(cdsl))
return tests

View File

@@ -1,605 +0,0 @@
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)
))

View File

@@ -1,266 +0,0 @@
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)

View File

@@ -1,131 +0,0 @@
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

View File

@@ -1,894 +0,0 @@
"""
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)

View File

@@ -1,348 +0,0 @@
"""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

View File

@@ -1,906 +0,0 @@
"""
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

View File

@@ -1,423 +0,0 @@
"""
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

View File

@@ -1,32 +0,0 @@
#!/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

View File

@@ -1,63 +0,0 @@
"""
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

View File

@@ -1,170 +0,0 @@
"""
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)

View File

@@ -1,32 +0,0 @@
"""
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))

View File

@@ -1,902 +0,0 @@
"""
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)

View File

@@ -1,24 +0,0 @@
"""
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]

View File

@@ -1,15 +0,0 @@
"""
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

View File

@@ -1,19 +0,0 @@
"""
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)

View File

@@ -1,45 +0,0 @@
"""
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())

View File

@@ -1,11 +0,0 @@
"""
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())

View File

@@ -1,14 +0,0 @@
"""
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

View File

@@ -1,15 +0,0 @@
"""
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)

View File

@@ -1,32 +0,0 @@
"""
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())

View File

@@ -1,11 +0,0 @@
"""
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())

View File

@@ -1,33 +0,0 @@
"""
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

View File

@@ -1,14 +0,0 @@
"""
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)

View File

@@ -1,169 +0,0 @@
"""
RISC-V Encodings.
"""
from __future__ import absolute_import
from base import instructions as base
from base import types
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, stacknull
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))
# Stack-slot-to-the-same-stack-slot copy, which is guaranteed to turn
# into a no-op.
for ty in [types.i64, types.i32, types.i16, types.i8, types.f64, types.f32]:
RV64.enc(base.copy_nop.bind(ty), stacknull, 0)
RV32.enc(base.copy_nop.bind(ty), stacknull, 0)

View File

@@ -1,230 +0,0 @@
"""
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!();')
# Stack-slot-to-the-same-stack-slot copy, which is guaranteed to turn
# into a no-op.
stacknull = EncRecipe('stacknull', Unary, base_size=0,
ins=Stack(GPR), outs=Stack(GPR), emit='')

View File

@@ -1,23 +0,0 @@
"""
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())

View File

@@ -1,31 +0,0 @@
"""
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())

View File

@@ -1,22 +0,0 @@
"""
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

View File

@@ -1,28 +0,0 @@
"""
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]

View File

@@ -1,771 +0,0 @@
"""
x86 Encodings.
"""
from __future__ import absolute_import
from cdsl.predicates import IsZero32BitFloat, IsZero64BitFloat
from cdsl.predicates import IsUnsignedInt
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 .settings import use_sse41, not_all_ones_funcaddrs_and_not_is_pic, \
all_ones_funcaddrs_and_not_is_pic, is_pic, not_is_pic
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.rotl_imm, 0),
(base.rotr_imm, 1),
(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))
# Stack-slot-to-the-same-stack-slot copy, which is guaranteed to turn
# into a no-op. Ideally we could to make this encoding available for
# all types, and write `base.copy_nop.any`, but it appears that the
# controlling type variable must not polymorphic. So we make do with
# the following limited set, and guard the generating transformation in
# regalloc/reload.rs accordingly.
#
# The same encoding is generated for both the 64- and 32-bit architectures.
# Note that we can't use `enc_both` here, because that attempts to create a
# variant with a REX prefix in the 64-bit-architecture case. But since
# there's no actual instruction for the REX prefix to modify the meaning of,
# it will modify the meaning of whatever instruction happens to follow this
# one, which is obviously wrong. Note also that we can and indeed *must*
# claim that there's a 64-bit encoding for the 32-bit arch case, even though
# no such single instruction actually exists for the 32-bit arch case.
for ty in [types.i64, types.i32, types.i16, types.i8, types.f64, types.f32]:
X86_64.enc(base.copy_nop.bind(ty), r.stacknull, 0)
X86_32.enc(base.copy_nop.bind(ty), r.stacknull, 0)
# 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=not_all_ones_funcaddrs_and_not_is_pic)
X86_64.enc(base.func_addr.i64, *r.fnaddr8.rex(0xb8, w=1),
isap=not_all_ones_funcaddrs_and_not_is_pic)
# Non-PIC, all-zeros funcaddresses.
X86_32.enc(base.func_addr.i32, *r.allones_fnaddr4(0xb8),
isap=all_ones_funcaddrs_and_not_is_pic)
X86_64.enc(base.func_addr.i64, *r.allones_fnaddr8.rex(0xb8, w=1),
isap=all_ones_funcaddrs_and_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
#
X86_32.enc(base.jump, *r.jmpb(0xeb))
X86_64.enc(base.jump, *r.jmpb(0xeb))
X86_32.enc(base.jump, *r.jmpd(0xe9))
X86_64.enc(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_abcd(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)

View File

@@ -1,173 +0,0 @@
"""
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()

View File

@@ -1,229 +0,0 @@
"""
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))
))

File diff suppressed because it is too large Load Diff

View File

@@ -1,61 +0,0 @@
"""
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())

View File

@@ -1,61 +0,0 @@
"""
x86 settings.
"""
from __future__ import absolute_import
from cdsl.settings import SettingGroup, BoolSetting, Preset
from cdsl.predicates import And, Not
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)
is_pic = And(shared.is_pic)
not_is_pic = Not(shared.is_pic)
all_ones_funcaddrs_and_not_is_pic = And(shared.allones_funcaddrs,
Not(shared.is_pic))
not_all_ones_funcaddrs_and_not_is_pic = And(Not(shared.allones_funcaddrs),
Not(shared.is_pic))
# 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())

View File

@@ -1,5 +0,0 @@
[mypy]
disallow_untyped_defs = True
warn_unused_ignores = True
warn_return_any = True
strict_optional = False

View File

@@ -1,77 +0,0 @@
"""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)

View File

@@ -1,146 +0,0 @@
"""
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)

View File

@@ -1,45 +0,0 @@
"""
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()

View File

@@ -1,133 +0,0 @@
"""
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()

View File

@@ -1,241 +0,0 @@
"""
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

View File

@@ -1,392 +0,0 @@
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)

View File

@@ -1,277 +0,0 @@
"""
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

View File

@@ -1,151 +0,0 @@
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: ...

View File

@@ -1,3 +0,0 @@
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: ...

View File

@@ -1,12 +0,0 @@
from typing import Any
class Z3Exception(Exception):
def __init__(self, a: Any) -> None:
self.value = a
...
class ContextObj:
...
class Ast:
...

View File

@@ -1,8 +0,0 @@
from __future__ import absolute_import
import doctest
import constant_hash
def load_tests(loader, tests, ignore):
tests.addTests(doctest.DocTestSuite(constant_hash))
return tests

View File

@@ -1,8 +0,0 @@
from __future__ import absolute_import
import doctest
import srcgen
def load_tests(loader, tests, ignore):
tests.addTests(doctest.DocTestSuite(srcgen))
return tests

View File

@@ -1,80 +0,0 @@
"""
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

View File

@@ -4,7 +4,6 @@ set -euo pipefail
# This is the top-level test script: # This is the top-level test script:
# #
# - Check code formatting. # - Check code formatting.
# - Perform checks on Python code.
# - Make a debug build. # - Make a debug build.
# - Make a release build. # - Make a release build.
# - Run unit tests for all Rust crates (including the filetests) # - Run unit tests for all Rust crates (including the filetests)
@@ -13,10 +12,6 @@ set -euo pipefail
# #
# All tests run by this script should be passing at all times. # All tests run by this script should be passing at all times.
# Disable generation of .pyc files because they cause trouble for vendoring
# scripts, and this is a build step that isn't run very often anyway.
export PYTHONDONTWRITEBYTECODE=1
# Repository top-level directory. # Repository top-level directory.
topdir=$(dirname "$0") topdir=$(dirname "$0")
cd "$topdir" cd "$topdir"
@@ -40,20 +35,6 @@ else
echo "https://github.com/rust-lang-nursery/rustfmt for more information." echo "https://github.com/rust-lang-nursery/rustfmt for more information."
fi fi
# Check if any Python files have changed since we last checked them.
tsfile="$topdir/target/meta-checked"
meta_python="$topdir/cranelift-codegen/meta-python"
if [ -f "$tsfile" ]; then
needcheck=$(find "$meta_python" -name '*.py' -newer "$tsfile")
else
needcheck=yes
fi
if [ -n "$needcheck" ]; then
banner "Checking python source files"
"$meta_python/check.sh"
touch "$tsfile" || echo no target directory
fi
# Make sure the code builds in release mode. # Make sure the code builds in release mode.
banner "Rust release build" banner "Rust release build"
cargo build --release cargo build --release