diff --git a/cranelift/CONTRIBUTING.md b/cranelift/CONTRIBUTING.md index 403e912e60..4d97970a6c 100644 --- a/cranelift/CONTRIBUTING.md +++ b/cranelift/CONTRIBUTING.md @@ -84,14 +84,6 @@ build. [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 We use [issues] for asking questions and tracking bugs and unimplemented diff --git a/cranelift/README.md b/cranelift/README.md index fe25d2a842..da1b3adb4b 100644 --- a/cranelift/README.md +++ b/cranelift/README.md @@ -53,8 +53,7 @@ needed before it would be ready for a production use case. Cranelift's APIs are not yet stable. -Cranelift currently requires Rust 1.35 or later, and Python 2.7 or 3 -to build. +Cranelift currently requires Rust 1.35 or later to build. Planned uses ------------ diff --git a/cranelift/codegen/build.rs b/cranelift/codegen/build.rs index 733a1c782f..d9d6f13fc5 100644 --- a/cranelift/codegen/build.rs +++ b/cranelift/codegen/build.rs @@ -57,32 +57,6 @@ fn main() { 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) { eprintln!("Error: {}", err); process::exit(1); @@ -99,16 +73,3 @@ fn main() { 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)"); -} diff --git a/cranelift/codegen/meta-python/base/__init__.py b/cranelift/codegen/meta-python/base/__init__.py deleted file mode 100644 index 79f6ccbf46..0000000000 --- a/cranelift/codegen/meta-python/base/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Definitions for the base Cranelift language.""" diff --git a/cranelift/codegen/meta-python/base/entities.py b/cranelift/codegen/meta-python/base/entities.py deleted file mode 100644 index 0226b18ca4..0000000000 --- a/cranelift/codegen/meta-python/base/entities.py +++ /dev/null @@ -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.') diff --git a/cranelift/codegen/meta-python/base/formats.py b/cranelift/codegen/meta-python/base/formats.py deleted file mode 100644 index b45ac26f74..0000000000 --- a/cranelift/codegen/meta-python/base/formats.py +++ /dev/null @@ -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()) diff --git a/cranelift/codegen/meta-python/base/immediates.py b/cranelift/codegen/meta-python/base/immediates.py deleted file mode 100644 index e8a6fccc78..0000000000 --- a/cranelift/codegen/meta-python/base/immediates.py +++ /dev/null @@ -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', - }) diff --git a/cranelift/codegen/meta-python/base/instructions.py b/cranelift/codegen/meta-python/base/instructions.py deleted file mode 100644 index 1c0ebc164f..0000000000 --- a/cranelift/codegen/meta-python/base/instructions.py +++ /dev/null @@ -1,2054 +0,0 @@ -""" -Cranelift base instruction set. - -This module defines the basic Cranelift instruction set that all targets -support. -""" -from __future__ import absolute_import -from cdsl.operands import Operand, VARIABLE_ARGS -from cdsl.typevar import TypeVar -from cdsl.instructions import Instruction, InstructionGroup -from base.types import f32, f64, b1, iflags, fflags -from base.immediates import imm64, uimm8, uimm32, ieee32, ieee64, offset32 -from base.immediates import boolean, intcc, floatcc, memflags, regunit -from base.immediates import trapcode -from base import entities -from cdsl.ti import WiderOrEq -import base.formats # noqa - -GROUP = InstructionGroup("base", "Shared base instruction set") - -Int = TypeVar('Int', 'A scalar or vector integer type', ints=True, simd=True) -Bool = TypeVar('Bool', 'A scalar or vector boolean type', - bools=True, simd=True) -iB = TypeVar('iB', 'A scalar integer type', ints=True) -iAddr = TypeVar('iAddr', 'An integer address type', ints=(32, 64)) -Testable = TypeVar( - 'Testable', 'A scalar boolean or integer type', - ints=True, bools=True) -TxN = TypeVar( - 'TxN', 'A SIMD vector type', - ints=True, floats=True, bools=True, scalars=False, simd=True) -Any = TypeVar( - 'Any', 'Any integer, float, or boolean scalar or vector type', - ints=True, floats=True, bools=True, scalars=True, simd=True) -Mem = TypeVar( - 'Mem', 'Any type that can be stored in memory', - ints=True, floats=True, simd=True) -MemTo = TypeVar( - 'MemTo', 'Any type that can be stored in memory', - ints=True, floats=True, simd=True) - -addr = Operand('addr', iAddr) - -# -# Control flow -# -c = Operand('c', Testable, doc='Controlling value to test') -Cond = Operand('Cond', intcc) -x = Operand('x', iB) -y = Operand('y', iB) -EBB = Operand('EBB', entities.ebb, doc='Destination extended basic block') -args = Operand('args', VARIABLE_ARGS, doc='EBB arguments') - -jump = Instruction( - 'jump', r""" - Jump. - - Unconditionally jump to an extended basic block, passing the specified - EBB arguments. The number and types of arguments must match the - destination EBB. - """, - ins=(EBB, args), is_branch=True, is_terminator=True) - -fallthrough = Instruction( - 'fallthrough', r""" - Fall through to the next EBB. - - This is the same as :inst:`jump`, except the destination EBB must be - the next one in the layout. - - Jumps are turned into fall-through instructions by the branch - relaxation pass. There is no reason to use this instruction outside - that pass. - """, - ins=(EBB, args), is_branch=True, is_terminator=True) - -brz = Instruction( - 'brz', r""" - Branch when zero. - - If ``c`` is a :type:`b1` value, take the branch when ``c`` is false. If - ``c`` is an integer value, take the branch when ``c = 0``. - """, - ins=(c, EBB, args), is_branch=True) - -brnz = Instruction( - 'brnz', r""" - Branch when non-zero. - - If ``c`` is a :type:`b1` value, take the branch when ``c`` is true. If - ``c`` is an integer value, take the branch when ``c != 0``. - """, - ins=(c, EBB, args), is_branch=True) - -br_icmp = Instruction( - 'br_icmp', r""" - Compare scalar integers and branch. - - Compare ``x`` and ``y`` in the same way as the :inst:`icmp` instruction - and take the branch if the condition is true:: - - br_icmp ugt v1, v2, ebb4(v5, v6) - - is semantically equivalent to:: - - v10 = icmp ugt, v1, v2 - brnz v10, ebb4(v5, v6) - - Some RISC architectures like MIPS and RISC-V provide instructions that - implement all or some of the condition codes. The instruction can also - be used to represent *macro-op fusion* on architectures like Intel's. - """, - ins=(Cond, x, y, EBB, args), is_branch=True) - -f = Operand('f', iflags) - -brif = Instruction( - 'brif', r""" - Branch when condition is true in integer CPU flags. - """, - ins=(Cond, f, EBB, args), is_branch=True) - -Cond = Operand('Cond', floatcc) -f = Operand('f', fflags) - -brff = Instruction( - 'brff', r""" - Branch when condition is true in floating point CPU flags. - """, - ins=(Cond, f, EBB, args), is_branch=True) - -x = Operand('x', iB, doc='index into jump table') -Entry = TypeVar('Entry', 'A scalar integer type', ints=True) -entry = Operand('entry', Entry, doc='entry of jump table') -JT = Operand('JT', entities.jump_table) -br_table = Instruction( - 'br_table', r""" - Indirect branch via jump table. - - Use ``x`` as an unsigned index into the jump table ``JT``. If a jump - table entry is found, branch to the corresponding EBB. If no entry was - found or the index is out-of-bounds, branch to the given default EBB. - - Note that this branch instruction can't pass arguments to the targeted - blocks. Split critical edges as needed to work around this. - - Do not confuse this with "tables" in WebAssembly. ``br_table`` is for - jump tables with destinations within the current function only -- think - of a ``match`` in Rust or a ``switch`` in C. If you want to call a - function in a dynamic library, that will typically use - ``call_indirect``. - """, - ins=(x, EBB, JT), is_branch=True, is_terminator=True) - -Size = Operand('Size', uimm8, 'Size in bytes') -jump_table_entry = Instruction( - 'jump_table_entry', r""" - Get an entry from a jump table. - - Load a serialized ``entry`` from a jump table ``JT`` at a given index - ``addr`` with a specific ``Size``. The retrieved entry may need to be - decoded after loading, depending upon the jump table type used. - - Currently, the only type supported is entries which are relative to the - base of the jump table. - """, - ins=(x, addr, Size, JT), outs=entry, can_load=True) - -jump_table_base = Instruction( - 'jump_table_base', r""" - Get the absolute base address of a jump table. - - This is used for jump tables wherein the entries are stored relative to - the base of jump table. In order to use these, generated code should first - load an entry using ``jump_table_entry``, then use this instruction to add - the relative base back to it. - """, - ins=JT, outs=addr) - -indirect_jump_table_br = Instruction( - 'indirect_jump_table_br', r""" - Branch indirectly via a jump table entry. - - Unconditionally jump via a jump table entry that was previously loaded - with the ``jump_table_entry`` instruction. - """, - ins=(addr, JT), - is_branch=True, is_indirect_branch=True, is_terminator=True) - -debugtrap = Instruction('debugtrap', r""" - Encodes an assembly debug trap. - """, can_load=True, can_store=True, other_side_effects=True) - -code = Operand('code', trapcode) -trap = Instruction( - 'trap', r""" - Terminate execution unconditionally. - """, - ins=code, is_terminator=True, can_trap=True) - -trapz = Instruction( - 'trapz', r""" - Trap when zero. - - if ``c`` is non-zero, execution continues at the following instruction. - """, - ins=(c, code), can_trap=True) - -trapnz = Instruction( - 'trapnz', r""" - Trap when non-zero. - - if ``c`` is zero, execution continues at the following instruction. - """, - ins=(c, code), can_trap=True) - -Cond = Operand('Cond', intcc) -f = Operand('f', iflags) - -trapif = Instruction( - 'trapif', r""" - Trap when condition is true in integer CPU flags. - """, - ins=(Cond, f, code), can_trap=True) - -Cond = Operand('Cond', floatcc) -f = Operand('f', fflags) - -trapff = Instruction( - 'trapff', r""" - Trap when condition is true in floating point CPU flags. - """, - ins=(Cond, f, code), can_trap=True) - -rvals = Operand('rvals', VARIABLE_ARGS, doc='return values') - -x_return = Instruction( - 'return', r""" - Return from the function. - - Unconditionally transfer control to the calling function, passing the - provided return values. The list of return values must match the - function signature's return types. - """, - ins=rvals, is_return=True, is_terminator=True) - -fallthrough_return = Instruction( - 'fallthrough_return', r""" - Return from the function by fallthrough. - - This is a specialized instruction for use where one wants to append - a custom epilogue, which will then perform the real return. This - instruction has no encoding. - """, - ins=rvals, is_return=True, is_terminator=True) - -FN = Operand( - 'FN', - entities.func_ref, - doc='function to call, declared by :inst:`function`') -args = Operand('args', VARIABLE_ARGS, doc='call arguments') - -call = Instruction( - 'call', r""" - Direct function call. - - Call a function which has been declared in the preamble. The argument - types must match the function's signature. - """, - ins=(FN, args), outs=rvals, is_call=True) - -SIG = Operand('SIG', entities.sig_ref, doc='function signature') -callee = Operand('callee', iAddr, doc='address of function to call') - -call_indirect = Instruction( - 'call_indirect', r""" - Indirect function call. - - Call the function pointed to by `callee` with the given arguments. The - called function must match the specified signature. - - Note that this is different from WebAssembly's ``call_indirect``; the - callee is a native address, rather than a table index. For WebAssembly, - :inst:`table_addr` and :inst:`load` are used to obtain a native address - from a table. - """, - ins=(SIG, callee, args), outs=rvals, is_call=True) - -func_addr = Instruction( - 'func_addr', r""" - Get the address of a function. - - Compute the absolute address of a function declared in the preamble. - The returned address can be used as a ``callee`` argument to - :inst:`call_indirect`. This is also a method for calling functions that - are too far away to be addressable by a direct :inst:`call` - instruction. - """, - ins=FN, outs=addr) - -# -# Memory operations -# - -SS = Operand('SS', entities.stack_slot) -Offset = Operand('Offset', offset32, 'Byte offset from base address') -x = Operand('x', Mem, doc='Value to be stored') -a = Operand('a', Mem, doc='Value loaded') -p = Operand('p', iAddr) -MemFlags = Operand('MemFlags', memflags) -args = Operand('args', VARIABLE_ARGS, doc='Address arguments') - -load = Instruction( - 'load', r""" - Load from memory at ``p + Offset``. - - This is a polymorphic instruction that can load any value type which - has a memory representation. - """, - ins=(MemFlags, p, Offset), outs=a, can_load=True) - -load_complex = Instruction( - 'load_complex', r""" - Load from memory at ``sum(args) + Offset``. - - This is a polymorphic instruction that can load any value type which - has a memory representation. - """, - ins=(MemFlags, args, Offset), outs=a, can_load=True) - -store = Instruction( - 'store', r""" - Store ``x`` to memory at ``p + Offset``. - - This is a polymorphic instruction that can store any value type with a - memory representation. - """, - ins=(MemFlags, x, p, Offset), can_store=True) - -store_complex = Instruction( - 'store_complex', r""" - Store ``x`` to memory at ``sum(args) + Offset``. - - This is a polymorphic instruction that can store any value type with a - memory representation. - """, - ins=(MemFlags, x, args, Offset), can_store=True) - - -iExt8 = TypeVar( - 'iExt8', 'An integer type with more than 8 bits', - ints=(16, 64)) -x = Operand('x', iExt8) -a = Operand('a', iExt8) - -uload8 = Instruction( - 'uload8', r""" - Load 8 bits from memory at ``p + Offset`` and zero-extend. - - This is equivalent to ``load.i8`` followed by ``uextend``. - """, - ins=(MemFlags, p, Offset), outs=a, can_load=True) - -uload8_complex = Instruction( - 'uload8_complex', r""" - Load 8 bits from memory at ``sum(args) + Offset`` and zero-extend. - - This is equivalent to ``load.i8`` followed by ``uextend``. - """, - ins=(MemFlags, args, Offset), outs=a, can_load=True) - -sload8 = Instruction( - 'sload8', r""" - Load 8 bits from memory at ``p + Offset`` and sign-extend. - - This is equivalent to ``load.i8`` followed by ``sextend``. - """, - ins=(MemFlags, p, Offset), outs=a, can_load=True) - -sload8_complex = Instruction( - 'sload8_complex', r""" - Load 8 bits from memory at ``sum(args) + Offset`` and sign-extend. - - This is equivalent to ``load.i8`` followed by ``sextend``. - """, - ins=(MemFlags, args, Offset), outs=a, can_load=True) - -istore8 = Instruction( - 'istore8', r""" - Store the low 8 bits of ``x`` to memory at ``p + Offset``. - - This is equivalent to ``ireduce.i8`` followed by ``store.i8``. - """, - ins=(MemFlags, x, p, Offset), can_store=True) - -istore8_complex = Instruction( - 'istore8_complex', r""" - Store the low 8 bits of ``x`` to memory at ``sum(args) + Offset``. - - This is equivalent to ``ireduce.i8`` followed by ``store.i8``. - """, - ins=(MemFlags, x, args, Offset), can_store=True) - -iExt16 = TypeVar( - 'iExt16', 'An integer type with more than 16 bits', - ints=(32, 64)) -x = Operand('x', iExt16) -a = Operand('a', iExt16) - -uload16 = Instruction( - 'uload16', r""" - Load 16 bits from memory at ``p + Offset`` and zero-extend. - - This is equivalent to ``load.i16`` followed by ``uextend``. - """, - ins=(MemFlags, p, Offset), outs=a, can_load=True) - -uload16_complex = Instruction( - 'uload16_complex', r""" - Load 16 bits from memory at ``sum(args) + Offset`` and zero-extend. - - This is equivalent to ``load.i16`` followed by ``uextend``. - """, - ins=(MemFlags, args, Offset), outs=a, can_load=True) - -sload16 = Instruction( - 'sload16', r""" - Load 16 bits from memory at ``p + Offset`` and sign-extend. - - This is equivalent to ``load.i16`` followed by ``sextend``. - """, - ins=(MemFlags, p, Offset), outs=a, can_load=True) - -sload16_complex = Instruction( - 'sload16_complex', r""" - Load 16 bits from memory at ``sum(args) + Offset`` and sign-extend. - - This is equivalent to ``load.i16`` followed by ``sextend``. - """, - ins=(MemFlags, args, Offset), outs=a, can_load=True) - -istore16 = Instruction( - 'istore16', r""" - Store the low 16 bits of ``x`` to memory at ``p + Offset``. - - This is equivalent to ``ireduce.i16`` followed by ``store.i16``. - """, - ins=(MemFlags, x, p, Offset), can_store=True) - -istore16_complex = Instruction( - 'istore16_complex', r""" - Store the low 16 bits of ``x`` to memory at ``sum(args) + Offset``. - - This is equivalent to ``ireduce.i16`` followed by ``store.i16``. - """, - ins=(MemFlags, x, args, Offset), can_store=True) - -iExt32 = TypeVar( - 'iExt32', 'An integer type with more than 32 bits', - ints=(64, 64)) -x = Operand('x', iExt32) -a = Operand('a', iExt32) - -uload32 = Instruction( - 'uload32', r""" - Load 32 bits from memory at ``p + Offset`` and zero-extend. - - This is equivalent to ``load.i32`` followed by ``uextend``. - """, - ins=(MemFlags, p, Offset), outs=a, can_load=True) - -uload32_complex = Instruction( - 'uload32_complex', r""" - Load 32 bits from memory at ``sum(args) + Offset`` and zero-extend. - - This is equivalent to ``load.i32`` followed by ``uextend``. - """, - ins=(MemFlags, args, Offset), outs=a, can_load=True) - -sload32 = Instruction( - 'sload32', r""" - Load 32 bits from memory at ``p + Offset`` and sign-extend. - - This is equivalent to ``load.i32`` followed by ``sextend``. - """, - ins=(MemFlags, p, Offset), outs=a, can_load=True) - -sload32_complex = Instruction( - 'sload32_complex', r""" - Load 32 bits from memory at ``sum(args) + Offset`` and sign-extend. - - This is equivalent to ``load.i32`` followed by ``sextend``. - """, - ins=(MemFlags, args, Offset), outs=a, can_load=True) - -istore32 = Instruction( - 'istore32', r""" - Store the low 32 bits of ``x`` to memory at ``p + Offset``. - - This is equivalent to ``ireduce.i32`` followed by ``store.i32``. - """, - ins=(MemFlags, x, p, Offset), can_store=True) - -istore32_complex = Instruction( - 'istore32_complex', r""" - Store the low 32 bits of ``x`` to memory at ``sum(args) + Offset``. - - This is equivalent to ``ireduce.i32`` followed by ``store.i32``. - """, - ins=(MemFlags, x, args, Offset), can_store=True) - -x = Operand('x', Mem, doc='Value to be stored') -a = Operand('a', Mem, doc='Value loaded') -Offset = Operand('Offset', offset32, 'In-bounds offset into stack slot') - -stack_load = Instruction( - 'stack_load', r""" - Load a value from a stack slot at the constant offset. - - This is a polymorphic instruction that can load any value type which - has a memory representation. - - The offset is an immediate constant, not an SSA value. The memory - access cannot go out of bounds, i.e. - :math:`sizeof(a) + Offset <= sizeof(SS)`. - """, - ins=(SS, Offset), outs=a, can_load=True) - -stack_store = Instruction( - 'stack_store', r""" - Store a value to a stack slot at a constant offset. - - This is a polymorphic instruction that can store any value type with a - memory representation. - - The offset is an immediate constant, not an SSA value. The memory - access cannot go out of bounds, i.e. - :math:`sizeof(a) + Offset <= sizeof(SS)`. - """, - ins=(x, SS, Offset), can_store=True) - -stack_addr = Instruction( - 'stack_addr', r""" - Get the address of a stack slot. - - Compute the absolute address of a byte in a stack slot. The offset must - refer to a byte inside the stack slot: - :math:`0 <= Offset < sizeof(SS)`. - """, - ins=(SS, Offset), outs=addr) - -# -# Global values. -# - -GV = Operand('GV', entities.global_value) - -global_value = Instruction( - 'global_value', r""" - Compute the value of global GV. - """, - ins=GV, outs=a) - -# A specialized form of global_value instructions that only handles -# symbolic names. -symbol_value = Instruction( - 'symbol_value', r""" - Compute the value of global GV, which is a symbolic value. - """, - ins=GV, outs=a) - -# -# WebAssembly bounds-checked heap accesses. -# - -HeapOffset = TypeVar('HeapOffset', 'An unsigned heap offset', ints=(32, 64)) - -H = Operand('H', entities.heap) -p = Operand('p', HeapOffset) -Size = Operand('Size', uimm32, 'Size in bytes') - -heap_addr = Instruction( - 'heap_addr', r""" - Bounds check and compute absolute address of heap memory. - - Verify that the offset range ``p .. p + Size - 1`` is in bounds for the - heap H, and generate an absolute address that is safe to dereference. - - 1. If ``p + Size`` is not greater than the heap bound, return an - absolute address corresponding to a byte offset of ``p`` from the - heap's base address. - 2. If ``p + Size`` is greater than the heap bound, generate a trap. - """, - ins=(H, p, Size), outs=addr) - -# -# WebAssembly bounds-checked table accesses. -# - -TableOffset = TypeVar('TableOffset', 'An unsigned table offset', ints=(32, 64)) - -T = Operand('T', entities.table) -p = Operand('p', TableOffset) -Offset = Operand('Offset', offset32, 'Byte offset from element address') - -table_addr = Instruction( - 'table_addr', r""" - Bounds check and compute absolute address of a table entry. - - Verify that the offset ``p`` is in bounds for the table T, and generate - an absolute address that is safe to dereference. - - ``Offset`` must be less than the size of a table element. - - 1. If ``p`` is not greater than the table bound, return an absolute - address corresponding to a byte offset of ``p`` from the table's - base address. - 2. If ``p`` is greater than the table bound, generate a trap. - """, - ins=(T, p, Offset), outs=addr) - - -# -# Materializing constants. -# - -N = Operand('N', imm64) -a = Operand('a', Int, doc='A constant integer scalar or vector value') -iconst = Instruction( - 'iconst', r""" - Integer constant. - - Create a scalar integer SSA value with an immediate constant value, or - an integer vector where all the lanes have the same value. - """, - ins=N, outs=a) - -N = Operand('N', ieee32) -a = Operand('a', f32, doc='A constant f32 scalar value') -f32const = Instruction( - 'f32const', r""" - Floating point constant. - - Create a :type:`f32` SSA value with an immediate constant value. - """, - ins=N, outs=a) - -N = Operand('N', ieee64) -a = Operand('a', f64, doc='A constant f64 scalar value') -f64const = Instruction( - 'f64const', r""" - Floating point constant. - - Create a :type:`f64` SSA value with an immediate constant value. - """, - ins=N, outs=a) - -N = Operand('N', boolean) -a = Operand('a', Bool, doc='A constant boolean scalar or vector value') -bconst = Instruction( - 'bconst', r""" - Boolean constant. - - Create a scalar boolean SSA value with an immediate constant value, or - a boolean vector where all the lanes have the same value. - """, - ins=N, outs=a) - -# -# Generics. -# - -nop = Instruction( - 'nop', r""" - Just a dummy instruction - - Note: this doesn't compile to a machine code nop - """) - -c = Operand('c', Testable, doc='Controlling value to test') -x = Operand('x', Any, doc='Value to use when `c` is true') -y = Operand('y', Any, doc='Value to use when `c` is false') -a = Operand('a', Any) - -select = Instruction( - 'select', r""" - Conditional select. - - This instruction selects whole values. Use :inst:`vselect` for - lane-wise selection. - """, - ins=(c, x, y), outs=a) - -cc = Operand('cc', intcc, doc='Controlling condition code') -flags = Operand('flags', iflags, doc='The machine\'s flag register') - -selectif = Instruction( - 'selectif', r""" - Conditional select, dependent on integer condition codes. - """, - ins=(cc, flags, x, y), outs=a) - -x = Operand('x', Any) - -copy = Instruction( - 'copy', r""" - Register-register copy. - - This instruction copies its input, preserving the value type. - - A pure SSA-form program does not need to copy values, but this - instruction is useful for representing intermediate stages during - instruction transformations, and the register allocator needs a way of - representing register copies. - """, - ins=x, outs=a) - -spill = Instruction( - 'spill', r""" - Spill a register value to a stack slot. - - This instruction behaves exactly like :inst:`copy`, but the result - value is assigned to a spill slot. - """, - ins=x, outs=a, can_store=True) - -fill = Instruction( - 'fill', r""" - Load a register value from a stack slot. - - This instruction behaves exactly like :inst:`copy`, but creates a new - SSA value for the spilled input value. - """, - ins=x, outs=a, can_load=True) - -src = Operand('src', regunit) -dst = Operand('dst', regunit) - -regmove = Instruction( - 'regmove', r""" - Temporarily divert ``x`` from ``src`` to ``dst``. - - This instruction moves the location of a value from one register to - another without creating a new SSA value. It is used by the register - allocator to temporarily rearrange register assignments in order to - satisfy instruction constraints. - - The register diversions created by this instruction must be undone - before the value leaves the EBB. At the entry to a new EBB, all live - values must be in their originally assigned registers. - """, - ins=(x, src, dst), - other_side_effects=True) - -copy_special = Instruction( - 'copy_special', r""" - Copies the contents of ''src'' register to ''dst'' register. - - This instructions copies the contents of one register to another - register without involving any SSA values. This is used for copying - special registers, e.g. copying the stack register to the frame - register in a function prologue. - """, - ins=(src, dst), - other_side_effects=True) - -copy_nop = Instruction( - 'copy_nop', r""" - Stack-slot-to-the-same-stack-slot copy, which is guaranteed to turn - into a no-op. This instruction is for use only within Cranelift - itself. - - This instruction copies its input, preserving the value type. - """, - ins=x, outs=a) - -delta = Operand('delta', Int) -adjust_sp_down = Instruction( - 'adjust_sp_down', r""" - Subtracts ``delta`` offset value from the stack pointer register. - - This instruction is used to adjust the stack pointer by a dynamic amount. - """, - ins=(delta,), - other_side_effects=True) - -StackOffset = Operand('Offset', imm64, 'Offset from current stack pointer') -adjust_sp_up_imm = Instruction( - 'adjust_sp_up_imm', r""" - Adds ``Offset`` immediate offset value to the stack pointer register. - - This instruction is used to adjust the stack pointer, primarily in function - prologues and epilogues. ``Offset`` is constrained to the size of a signed - 32-bit integer. - """, - ins=(StackOffset,), - other_side_effects=True) - -StackOffset = Operand('Offset', imm64, 'Offset from current stack pointer') -adjust_sp_down_imm = Instruction( - 'adjust_sp_down_imm', r""" - Subtracts ``Offset`` immediate offset value from the stack pointer - register. - - This instruction is used to adjust the stack pointer, primarily in function - prologues and epilogues. ``Offset`` is constrained to the size of a signed - 32-bit integer. - """, - ins=(StackOffset,), - other_side_effects=True) - -f = Operand('f', iflags) - -ifcmp_sp = Instruction( - 'ifcmp_sp', r""" - Compare ``addr`` with the stack pointer and set the CPU flags. - - This is like :inst:`ifcmp` where ``addr`` is the LHS operand and the stack - pointer is the RHS. - """, - ins=addr, outs=f) - -regspill = Instruction( - 'regspill', r""" - Temporarily divert ``x`` from ``src`` to ``SS``. - - This instruction moves the location of a value from a register to a - stack slot without creating a new SSA value. It is used by the register - allocator to temporarily rearrange register assignments in order to - satisfy instruction constraints. - - See also :inst:`regmove`. - """, - ins=(x, src, SS), - other_side_effects=True) - - -regfill = Instruction( - 'regfill', r""" - Temporarily divert ``x`` from ``SS`` to ``dst``. - - This instruction moves the location of a value from a stack slot to a - register without creating a new SSA value. It is used by the register - allocator to temporarily rearrange register assignments in order to - satisfy instruction constraints. - - See also :inst:`regmove`. - """, - ins=(x, SS, dst), - other_side_effects=True) -# -# Vector operations -# - -x = Operand('x', TxN, doc='Vector to split') -lo = Operand('lo', TxN.half_vector(), doc='Low-numbered lanes of `x`') -hi = Operand('hi', TxN.half_vector(), doc='High-numbered lanes of `x`') - -vsplit = Instruction( - 'vsplit', r""" - Split a vector into two halves. - - Split the vector `x` into two separate values, each containing half of - the lanes from ``x``. The result may be two scalars if ``x`` only had - two lanes. - """, - ins=x, outs=(lo, hi), is_ghost=True) - -Any128 = TypeVar( - 'Any128', 'Any scalar or vector type with as most 128 lanes', - ints=True, floats=True, bools=True, scalars=True, simd=(1, 128)) -x = Operand('x', Any128, doc='Low-numbered lanes') -y = Operand('y', Any128, doc='High-numbered lanes') -a = Operand('a', Any128.double_vector(), doc='Concatenation of `x` and `y`') - -vconcat = Instruction( - 'vconcat', r""" - Vector concatenation. - - Return a vector formed by concatenating ``x`` and ``y``. The resulting - vector type has twice as many lanes as each of the inputs. The lanes of - ``x`` appear as the low-numbered lanes, and the lanes of ``y`` become - the high-numbered lanes of ``a``. - - It is possible to form a vector by concatenating two scalars. - """, - ins=(x, y), outs=a, is_ghost=True) - -c = Operand('c', TxN.as_bool(), doc='Controlling vector') -x = Operand('x', TxN, doc='Value to use where `c` is true') -y = Operand('y', TxN, doc='Value to use where `c` is false') -a = Operand('a', TxN) - -vselect = Instruction( - 'vselect', r""" - Vector lane select. - - Select lanes from ``x`` or ``y`` controlled by the lanes of the boolean - vector ``c``. - """, - ins=(c, x, y), outs=a) - -x = Operand('x', TxN.lane_of()) - -splat = Instruction( - 'splat', r""" - Vector splat. - - Return a vector whose lanes are all ``x``. - """, - ins=x, outs=a) - -x = Operand('x', TxN, doc='SIMD vector to modify') -y = Operand('y', TxN.lane_of(), doc='New lane value') -Idx = Operand('Idx', uimm8, doc='Lane index') - -insertlane = Instruction( - 'insertlane', r""" - Insert ``y`` as lane ``Idx`` in x. - - The lane index, ``Idx``, is an immediate value, not an SSA value. It - must indicate a valid lane index for the type of ``x``. - """, - ins=(x, Idx, y), outs=a) - -x = Operand('x', TxN) -a = Operand('a', TxN.lane_of()) - -extractlane = Instruction( - 'extractlane', r""" - Extract lane ``Idx`` from ``x``. - - The lane index, ``Idx``, is an immediate value, not an SSA value. It - must indicate a valid lane index for the type of ``x``. - """, - ins=(x, Idx), outs=a) - -# -# Integer arithmetic -# - -a = Operand('a', Int.as_bool()) -Cond = Operand('Cond', intcc) -x = Operand('x', Int) -y = Operand('y', Int) - -icmp = Instruction( - 'icmp', r""" - Integer comparison. - - The condition code determines if the operands are interpreted as signed - or unsigned integers. - - ====== ======== ========= - Signed Unsigned Condition - ====== ======== ========= - eq eq Equal - ne ne Not equal - slt ult Less than - sge uge Greater than or equal - sgt ugt Greater than - sle ule Less than or equal - ====== ======== ========= - - When this instruction compares integer vectors, it returns a boolean - vector of lane-wise comparisons. - """, - ins=(Cond, x, y), outs=a) - -a = Operand('a', b1) -x = Operand('x', iB) -Y = Operand('Y', imm64) - -icmp_imm = Instruction( - 'icmp_imm', r""" - Compare scalar integer to a constant. - - This is the same as the :inst:`icmp` instruction, except one operand is - an immediate constant. - - This instruction can only compare scalars. Use :inst:`icmp` for - lane-wise vector comparisons. - """, - ins=(Cond, x, Y), outs=a) - -f = Operand('f', iflags) -x = Operand('x', iB) -y = Operand('y', iB) - -ifcmp = Instruction( - 'ifcmp', r""" - Compare scalar integers and return flags. - - Compare two scalar integer values and return integer CPU flags - representing the result. - """, - ins=(x, y), outs=f) - -ifcmp_imm = Instruction( - 'ifcmp_imm', r""" - Compare scalar integer to a constant and return flags. - - Like :inst:`icmp_imm`, but returns integer CPU flags instead of testing - a specific condition code. - """, - ins=(x, Y), outs=f) - -a = Operand('a', Int) -x = Operand('x', Int) -y = Operand('y', Int) - -iadd = Instruction( - 'iadd', r""" - 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) - -isub = Instruction( - 'isub', r""" - Wrapping integer subtraction: :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) - -imul = Instruction( - 'imul', r""" - Wrapping integer multiplication: :math:`a := x y \pmod{2^B}`. - - This instruction does not depend on the signed/unsigned interpretation - of the - operands. - - Polymorphic over all integer types (vector and scalar). - """, - ins=(x, y), outs=a) - -umulhi = Instruction( - 'umulhi', r""" - Unsigned integer multiplication, producing the high half of a - double-length result. - - Polymorphic over all scalar integer types, but does not support vector - types. - """, - ins=(x, y), outs=a) - -smulhi = Instruction( - 'smulhi', """ - Signed integer multiplication, producing the high half of a - double-length result. - - Polymorphic over all scalar integer types, but does not support vector - types. - """, - ins=(x, y), outs=a) - -udiv = Instruction( - 'udiv', r""" - Unsigned integer division: :math:`a := \lfloor {x \over y} \rfloor`. - - This operation traps if the divisor is zero. - """, - ins=(x, y), outs=a, can_trap=True) - -sdiv = Instruction( - 'sdiv', r""" - Signed integer division rounded toward zero: :math:`a := sign(xy) - \lfloor {|x| \over |y|}\rfloor`. - - This operation traps if the divisor is zero, or if the result is not - representable in :math:`B` bits two's complement. This only happens - when :math:`x = -2^{B-1}, y = -1`. - """, - ins=(x, y), outs=a, can_trap=True) - -urem = Instruction( - 'urem', """ - Unsigned integer remainder. - - This operation traps if the divisor is zero. - """, - ins=(x, y), outs=a, can_trap=True) - -srem = Instruction( - 'srem', """ - Signed integer remainder. The result has the sign of the dividend. - - This operation traps if the divisor is zero. - """, - ins=(x, y), outs=a, can_trap=True) - -a = Operand('a', iB) -x = Operand('x', iB) -Y = Operand('Y', imm64) - -iadd_imm = Instruction( - 'iadd_imm', """ - Add immediate integer. - - Same as :inst:`iadd`, but one operand is an immediate constant. - - Polymorphic over all scalar integer types, but does not support vector - types. - """, - ins=(x, Y), outs=a) - -imul_imm = Instruction( - 'imul_imm', """ - Integer multiplication by immediate constant. - - Polymorphic over all scalar integer types, but does not support vector - types. - """, - ins=(x, Y), outs=a) - -udiv_imm = Instruction( - 'udiv_imm', """ - Unsigned integer division by an immediate constant. - - This operation traps if the divisor is zero. - """, - ins=(x, Y), outs=a) - -sdiv_imm = Instruction( - 'sdiv_imm', """ - Signed integer division by an immediate constant. - - This operation traps if the divisor is zero, or if the result is not - representable in :math:`B` bits two's complement. This only happens - when :math:`x = -2^{B-1}, Y = -1`. - """, - ins=(x, Y), outs=a) - -urem_imm = Instruction( - 'urem_imm', """ - Unsigned integer remainder with immediate divisor. - - This operation traps if the divisor is zero. - """, - ins=(x, Y), outs=a) - -srem_imm = Instruction( - 'srem_imm', """ - Signed integer remainder with immediate divisor. - - This operation traps if the divisor is zero. - """, - ins=(x, Y), outs=a) - -irsub_imm = Instruction( - 'irsub_imm', """ - Immediate reverse wrapping subtraction: :math:`a := Y - x \\pmod{2^B}`. - - Also works as integer negation when :math:`Y = 0`. Use :inst:`iadd_imm` - with a negative immediate operand for the reverse immediate - subtraction. - - Polymorphic over all scalar integer types, but does not support vector - types. - """, - ins=(x, Y), outs=a) - -# -# Integer arithmetic with carry and/or borrow. -# -a = Operand('a', iB) -x = Operand('x', iB) -y = Operand('y', iB) -c_in = Operand('c_in', b1, doc="Input carry flag") -c_out = Operand('c_out', b1, doc="Output carry flag") -b_in = Operand('b_in', b1, doc="Input borrow flag") -b_out = Operand('b_out', b1, doc="Output borrow flag") - -iadd_cin = Instruction( - 'iadd_cin', r""" - Add integers with carry in. - - Same as :inst:`iadd` with an additional carry input. Computes: - - .. math:: - - a = x + y + c_{in} \pmod 2^B - - Polymorphic over all scalar integer types, but does not support vector - types. - """, - ins=(x, y, c_in), outs=a) - -iadd_cout = Instruction( - 'iadd_cout', r""" - Add integers with carry out. - - Same as :inst:`iadd` with an additional carry output. - - .. math:: - - a &= x + y \pmod 2^B \\ - c_{out} &= x+y >= 2^B - - Polymorphic over all scalar integer types, but does not support vector - types. - """, - ins=(x, y), outs=(a, c_out)) - -iadd_carry = Instruction( - 'iadd_carry', r""" - Add integers with carry in and out. - - Same as :inst:`iadd` with an additional carry input and output. - - .. math:: - - a &= x + y + c_{in} \pmod 2^B \\ - c_{out} &= x + y + c_{in} >= 2^B - - Polymorphic over all scalar integer types, but does not support vector - types. - """, - ins=(x, y, c_in), outs=(a, c_out)) - -isub_bin = Instruction( - 'isub_bin', r""" - Subtract integers with borrow in. - - Same as :inst:`isub` with an additional borrow flag input. Computes: - - .. math:: - - a = x - (y + b_{in}) \pmod 2^B - - Polymorphic over all scalar integer types, but does not support vector - types. - """, - ins=(x, y, b_in), outs=a) - -isub_bout = Instruction( - 'isub_bout', r""" - Subtract integers with borrow out. - - Same as :inst:`isub` with an additional borrow flag output. - - .. math:: - - a &= x - y \pmod 2^B \\ - b_{out} &= x < y - - Polymorphic over all scalar integer types, but does not support vector - types. - """, - ins=(x, y), outs=(a, b_out)) - -isub_borrow = Instruction( - 'isub_borrow', r""" - Subtract integers with borrow in and out. - - Same as :inst:`isub` with an additional borrow flag input and output. - - .. math:: - - a &= x - (y + b_{in}) \pmod 2^B \\ - b_{out} &= x < y + b_{in} - - Polymorphic over all scalar integer types, but does not support vector - types. - """, - ins=(x, y, b_in), outs=(a, b_out)) - -# -# Bitwise operations. -# - -# TODO: Which types should permit boolean operations? Any reason to restrict? -bits = TypeVar( - 'bits', 'Any integer, float, or boolean scalar or vector type', - ints=True, floats=True, bools=True, scalars=True, simd=True) - -x = Operand('x', bits) -y = Operand('y', bits) -a = Operand('a', bits) - -band = Instruction( - 'band', """ - Bitwise and. - """, - ins=(x, y), outs=a) - -bor = Instruction( - 'bor', """ - Bitwise or. - """, - ins=(x, y), outs=a) - -bxor = Instruction( - 'bxor', """ - Bitwise xor. - """, - ins=(x, y), outs=a) - -bnot = Instruction( - 'bnot', """ - Bitwise not. - """, - ins=x, outs=a) - -band_not = Instruction( - 'band_not', """ - Bitwise and not. - - Computes `x & ~y`. - """, - ins=(x, y), outs=a) - -bor_not = Instruction( - 'bor_not', """ - Bitwise or not. - - Computes `x | ~y`. - """, - ins=(x, y), outs=a) - -bxor_not = Instruction( - 'bxor_not', """ - Bitwise xor not. - - Computes `x ^ ~y`. - """, - ins=(x, y), outs=a) - -# Bitwise binary ops with immediate arg. -x = Operand('x', iB) -Y = Operand('Y', imm64) -a = Operand('a', iB) - -band_imm = Instruction( - 'band_imm', """ - Bitwise and with immediate. - - Same as :inst:`band`, but one operand is an immediate constant. - - Polymorphic over all scalar integer types, but does not support vector - types. - """, - ins=(x, Y), outs=a) - -bor_imm = Instruction( - 'bor_imm', """ - Bitwise or with immediate. - - Same as :inst:`bor`, but one operand is an immediate constant. - - Polymorphic over all scalar integer types, but does not support vector - types. - """, - ins=(x, Y), outs=a) - -bxor_imm = Instruction( - 'bxor_imm', """ - Bitwise xor with immediate. - - Same as :inst:`bxor`, but one operand is an immediate constant. - - Polymorphic over all scalar integer types, but does not support vector - types. - """, - ins=(x, Y), outs=a) - -# Shift/rotate. -x = Operand('x', Int, doc='Scalar or vector value to shift') -y = Operand('y', iB, doc='Number of bits to shift') -Y = Operand('Y', imm64) - -a = Operand('a', Int) - -rotl = Instruction( - 'rotl', r""" - Rotate left. - - Rotate the bits in ``x`` by ``y`` places. - """, - ins=(x, y), outs=a) - -rotr = Instruction( - 'rotr', r""" - Rotate right. - - Rotate the bits in ``x`` by ``y`` places. - """, - ins=(x, y), outs=a) - -rotl_imm = Instruction( - 'rotl_imm', r""" - Rotate left by immediate. - """, - ins=(x, Y), outs=a) - -rotr_imm = Instruction( - 'rotr_imm', r""" - Rotate right by immediate. - """, - ins=(x, Y), outs=a) - -ishl = Instruction( - 'ishl', r""" - Integer shift left. Shift the bits in ``x`` towards the MSB by ``y`` - places. Shift in zero bits to the LSB. - - The shift amount is masked to the size of ``x``. - - When shifting a B-bits integer type, this instruction computes: - - .. math:: - s &:= y \pmod B, \\ - a &:= x \cdot 2^s \pmod{2^B}. - """, - ins=(x, y), outs=a) - -ushr = Instruction( - 'ushr', r""" - Unsigned shift right. Shift bits in ``x`` towards the LSB by ``y`` - places, shifting in zero bits to the MSB. Also called a *logical - shift*. - - The shift amount is masked to the size of the register. - - When shifting a B-bits integer type, this instruction computes: - - .. math:: - s &:= y \pmod B, \\ - a &:= \lfloor x \cdot 2^{-s} \rfloor. - """, - ins=(x, y), outs=a) - -sshr = Instruction( - 'sshr', r""" - Signed shift right. Shift bits in ``x`` towards the LSB by ``y`` - places, shifting in sign bits to the MSB. Also called an *arithmetic - shift*. - - The shift amount is masked to the size of the register. - """, - ins=(x, y), outs=a) - -ishl_imm = Instruction( - 'ishl_imm', r""" - Integer shift left by immediate. - - The shift amount is masked to the size of ``x``. - """, - ins=(x, Y), outs=a) - -ushr_imm = Instruction( - 'ushr_imm', r""" - Unsigned shift right by immediate. - - The shift amount is masked to the size of the register. - """, - ins=(x, Y), outs=a) - -sshr_imm = Instruction( - 'sshr_imm', r""" - Signed shift right by immediate. - - The shift amount is masked to the size of the register. - """, - ins=(x, Y), outs=a) - -# -# Bit counting. -# - -x = Operand('x', iB) -a = Operand('a', iB) - -bitrev = Instruction( - 'bitrev', r""" - Reverse the bits of a integer. - - Reverses the bits in ``x``. - """, - ins=x, outs=a) - -clz = Instruction( - 'clz', r""" - Count leading zero bits. - - Starting from the MSB in ``x``, count the number of zero bits before - reaching the first one bit. When ``x`` is zero, returns the size of x - in bits. - """, - ins=x, outs=a) - -cls = Instruction( - 'cls', r""" - Count leading sign bits. - - Starting from the MSB after the sign bit in ``x``, count the number of - consecutive bits identical to the sign bit. When ``x`` is 0 or -1, - returns one less than the size of x in bits. - """, - ins=x, outs=a) - -ctz = Instruction( - 'ctz', r""" - Count trailing zeros. - - Starting from the LSB in ``x``, count the number of zero bits before - reaching the first one bit. When ``x`` is zero, returns the size of x - in bits. - """, - ins=x, outs=a) - -popcnt = Instruction( - 'popcnt', r""" - Population count - - Count the number of one bits in ``x``. - """, - ins=x, outs=a) - -# -# Floating point. -# - -Float = TypeVar( - 'Float', 'A scalar or vector floating point number', - floats=True, simd=True) -fB = TypeVar('fB', 'A scalar floating point number', floats=True) - -Cond = Operand('Cond', floatcc) -x = Operand('x', Float) -y = Operand('y', Float) -a = Operand('a', Float.as_bool()) - -fcmp = Instruction( - 'fcmp', r""" - Floating point comparison. - - Two IEEE 754-2008 floating point numbers, `x` and `y`, relate to each - other in exactly one of four ways: - - == ========================================== - UN Unordered when one or both numbers is NaN. - EQ When :math:`x = y`. (And :math:`0.0 = -0.0`). - LT When :math:`x < y`. - GT When :math:`x > y`. - == ========================================== - - The 14 :type:`floatcc` condition codes each correspond to a subset of - the four relations, except for the empty set which would always be - false, and the full set which would always be true. - - The condition codes are divided into 7 'ordered' conditions which don't - include UN, and 7 unordered conditions which all include UN. - - +-------+------------+---------+------------+-------------------------+ - |Ordered |Unordered |Condition | - +=======+============+=========+============+=========================+ - |ord |EQ | LT | GT|uno |UN |NaNs absent / present. | - +-------+------------+---------+------------+-------------------------+ - |eq |EQ |ueq |UN | EQ |Equal | - +-------+------------+---------+------------+-------------------------+ - |one |LT | GT |ne |UN | LT | GT|Not equal | - +-------+------------+---------+------------+-------------------------+ - |lt |LT |ult |UN | LT |Less than | - +-------+------------+---------+------------+-------------------------+ - |le |LT | EQ |ule |UN | LT | EQ|Less than or equal | - +-------+------------+---------+------------+-------------------------+ - |gt |GT |ugt |UN | GT |Greater than | - +-------+------------+---------+------------+-------------------------+ - |ge |GT | EQ |uge |UN | GT | EQ|Greater than or equal | - +-------+------------+---------+------------+-------------------------+ - - The standard C comparison operators, `<, <=, >, >=`, are all ordered, - so they are false if either operand is NaN. The C equality operator, - `==`, is ordered, and since inequality is defined as the logical - inverse it is *unordered*. They map to the :type:`floatcc` condition - codes as follows: - - ==== ====== ============ - C `Cond` Subset - ==== ====== ============ - `==` eq EQ - `!=` ne UN | LT | GT - `<` lt LT - `<=` le LT | EQ - `>` gt GT - `>=` ge GT | EQ - ==== ====== ============ - - This subset of condition codes also corresponds to the WebAssembly - floating point comparisons of the same name. - - When this instruction compares floating point vectors, it returns a - boolean vector with the results of lane-wise comparisons. - """, - ins=(Cond, x, y), outs=a) - -f = Operand('f', fflags) - -ffcmp = Instruction( - 'ffcmp', r""" - Floating point comparison returning flags. - - Compares two numbers like :inst:`fcmp`, but returns floating point CPU - flags instead of testing a specific condition. - """, - ins=(x, y), outs=f) - -x = Operand('x', Float) -y = Operand('y', Float) -z = Operand('z', Float) -a = Operand('a', Float, 'Result of applying operator to each lane') - -fadd = Instruction( - 'fadd', r""" - Floating point addition. - """, - ins=(x, y), outs=a) - -fsub = Instruction( - 'fsub', r""" - Floating point subtraction. - """, - ins=(x, y), outs=a) - -fmul = Instruction( - 'fmul', r""" - Floating point multiplication. - """, - ins=(x, y), outs=a) - -fdiv = Instruction( - 'fdiv', r""" - Floating point division. - - Unlike the integer division instructions :clif:inst:`sdiv` and - :clif:inst:`udiv`, this can't trap. Division by zero is infinity or - NaN, depending on the dividend. - """, - ins=(x, y), outs=a) - -sqrt = Instruction( - 'sqrt', r""" - Floating point square root. - """, - ins=x, outs=a) - -fma = Instruction( - 'fma', r""" - Floating point fused multiply-and-add. - - Computes :math:`a := xy+z` without any intermediate rounding of the - product. - """, - ins=(x, y, z), outs=a) - -a = Operand('a', Float, '``x`` with its sign bit inverted') -fneg = Instruction( - 'fneg', r""" - Floating point negation. - - Note that this is a pure bitwise operation. - """, - ins=x, outs=a) - -a = Operand('a', Float, '``x`` with its sign bit cleared') -fabs = Instruction( - 'fabs', r""" - Floating point absolute value. - - Note that this is a pure bitwise operation. - """, - ins=x, outs=a) - -a = Operand('a', Float, '``x`` with its sign bit changed to that of ``y``') -fcopysign = Instruction( - 'fcopysign', r""" - Floating point copy sign. - - Note that this is a pure bitwise operation. The sign bit from ``y`` is - copied to the sign bit of ``x``. - """, - ins=(x, y), outs=a) - -a = Operand('a', Float, 'The smaller of ``x`` and ``y``') - -fmin = Instruction( - 'fmin', r""" - Floating point minimum, propagating NaNs. - - If either operand is NaN, this returns a NaN. - """, - ins=(x, y), outs=a) - -a = Operand('a', Float, 'The larger of ``x`` and ``y``') - -fmax = Instruction( - 'fmax', r""" - Floating point maximum, propagating NaNs. - - If either operand is NaN, this returns a NaN. - """, - ins=(x, y), outs=a) - -a = Operand('a', Float, '``x`` rounded to integral value') - -ceil = Instruction( - 'ceil', r""" - Round floating point round to integral, towards positive infinity. - """, - ins=x, outs=a) - -floor = Instruction( - 'floor', r""" - Round floating point round to integral, towards negative infinity. - """, - ins=x, outs=a) - -trunc = Instruction( - 'trunc', r""" - Round floating point round to integral, towards zero. - """, - ins=x, outs=a) - -nearest = Instruction( - 'nearest', r""" - Round floating point round to integral, towards nearest with ties to - even. - """, - ins=x, outs=a) - -# -# CPU flag operations -# - - -Cond = Operand('Cond', intcc) -f = Operand('f', iflags) -a = Operand('a', b1) - -trueif = Instruction( - 'trueif', r""" - Test integer CPU flags for a specific condition. - - Check the CPU flags in ``f`` against the ``Cond`` condition code and - return true when the condition code is satisfied. - """, - ins=(Cond, f), outs=a) - -Cond = Operand('Cond', floatcc) -f = Operand('f', fflags) - -trueff = Instruction( - 'trueff', r""" - Test floating point CPU flags for a specific condition. - - Check the CPU flags in ``f`` against the ``Cond`` condition code and - return true when the condition code is satisfied. - """, - ins=(Cond, f), outs=a) - -# -# Conversions -# - -x = Operand('x', Mem) -a = Operand('a', MemTo, 'Bits of `x` reinterpreted') - -bitcast = Instruction( - 'bitcast', r""" - Reinterpret the bits in `x` as a different type. - - The input and output types must be storable to memory and of the same - size. A bitcast is equivalent to storing one type and loading the other - type from the same address. - """, - ins=x, outs=a) - -Bool = TypeVar( - 'Bool', - 'A scalar or vector boolean type', - bools=True, simd=True) -BoolTo = TypeVar( - 'BoolTo', - 'A smaller boolean type with the same number of lanes', - bools=True, simd=True) - -x = Operand('x', Bool) -a = Operand('a', BoolTo) - -breduce = Instruction( - 'breduce', r""" - Convert `x` to a smaller boolean type in the platform-defined way. - - The result type must have the same number of vector lanes as the input, - and each lane must not have more bits that the input lanes. If the - input and output types are the same, this is a no-op. - """, ins=x, outs=a, constraints=WiderOrEq(Bool, BoolTo)) - -BoolTo = TypeVar( - 'BoolTo', - 'A larger boolean type with the same number of lanes', - bools=True, simd=True) - -x = Operand('x', Bool) -a = Operand('a', BoolTo) - -bextend = Instruction( - 'bextend', r""" - Convert `x` to a larger boolean type in the platform-defined way. - - The result type must have the same number of vector lanes as the input, - and each lane must not have fewer bits that the input lanes. If the - input and output types are the same, this is a no-op. - """, ins=x, outs=a, constraints=WiderOrEq(BoolTo, Bool)) - -IntTo = TypeVar( - 'IntTo', 'An integer type with the same number of lanes', - ints=True, simd=True) - -x = Operand('x', Bool) -a = Operand('a', IntTo) - -bint = Instruction( - 'bint', r""" - Convert `x` to an integer. - - True maps to 1 and false maps to 0. The result type must have the same - number of vector lanes as the input. - """, ins=x, outs=a) - -bmask = Instruction( - 'bmask', r""" - Convert `x` to an integer mask. - - True maps to all 1s and false maps to all 0s. The result type must have - the same number of vector lanes as the input. - """, ins=x, outs=a) - -Int = TypeVar('Int', 'A scalar or vector integer type', ints=True, simd=True) -IntTo = TypeVar( - 'IntTo', 'A smaller integer type with the same number of lanes', - ints=True, simd=True) - -x = Operand('x', Int) -a = Operand('a', IntTo) - -ireduce = Instruction( - 'ireduce', r""" - Convert `x` to a smaller integer type by dropping high bits. - - Each lane in `x` is converted to a smaller integer type by discarding - the most significant bits. This is the same as reducing modulo - :math:`2^n`. - - The result type must have the same number of vector lanes as the input, - and each lane must not have more bits that the input lanes. If the - input and output types are the same, this is a no-op. - """, - ins=x, outs=a, constraints=WiderOrEq(Int, IntTo)) - - -IntTo = TypeVar( - 'IntTo', 'A larger integer type with the same number of lanes', - ints=True, simd=True) - -x = Operand('x', Int) -a = Operand('a', IntTo) - -uextend = Instruction( - 'uextend', r""" - Convert `x` to a larger integer type by zero-extending. - - Each lane in `x` is converted to a larger integer type by adding - zeroes. The result has the same numerical value as `x` when both are - interpreted as unsigned integers. - - The result type must have the same number of vector lanes as the input, - and each lane must not have fewer bits that the input lanes. If the - input and output types are the same, this is a no-op. - """, - ins=x, outs=a, constraints=WiderOrEq(IntTo, Int)) - -sextend = Instruction( - 'sextend', r""" - Convert `x` to a larger integer type by sign-extending. - - Each lane in `x` is converted to a larger integer type by replicating - the sign bit. The result has the same numerical value as `x` when both - are interpreted as signed integers. - - The result type must have the same number of vector lanes as the input, - and each lane must not have fewer bits that the input lanes. If the - input and output types are the same, this is a no-op. - """, - ins=x, outs=a, constraints=WiderOrEq(IntTo, Int)) - -FloatTo = TypeVar( - 'FloatTo', 'A scalar or vector floating point number', - floats=True, simd=True) - -x = Operand('x', Float) -a = Operand('a', FloatTo) - -fpromote = Instruction( - 'fpromote', r""" - Convert `x` to a larger floating point format. - - Each lane in `x` is converted to the destination floating point format. - This is an exact operation. - - Cranelift currently only supports two floating point formats - - :type:`f32` and :type:`f64`. This may change in the future. - - The result type must have the same number of vector lanes as the input, - and the result lanes must not have fewer bits than the input lanes. If - the input and output types are the same, this is a no-op. - """, - ins=x, outs=a, constraints=WiderOrEq(FloatTo, Float)) - -fdemote = Instruction( - 'fdemote', r""" - Convert `x` to a smaller floating point format. - - Each lane in `x` is converted to the destination floating point format - by rounding to nearest, ties to even. - - Cranelift currently only supports two floating point formats - - :type:`f32` and :type:`f64`. This may change in the future. - - The result type must have the same number of vector lanes as the input, - and the result lanes must not have more bits than the input lanes. If - the input and output types are the same, this is a no-op. - """, - ins=x, outs=a, constraints=WiderOrEq(Float, FloatTo)) - -x = Operand('x', Float) -a = Operand('a', IntTo) - -fcvt_to_uint = Instruction( - 'fcvt_to_uint', r""" - Convert floating point to unsigned integer. - - Each lane in `x` is converted to an unsigned integer by rounding - towards zero. If `x` is NaN or if the unsigned integral value cannot be - represented in the result type, this instruction traps. - - The result type must have the same number of vector lanes as the input. - """, - ins=x, outs=a, can_trap=True) - -fcvt_to_uint_sat = Instruction( - 'fcvt_to_uint_sat', r""" - Convert floating point to unsigned integer as fcvt_to_uint does, but - saturates the input instead of trapping. NaN and negative values are - converted to 0. - """, - ins=x, outs=a) - -fcvt_to_sint = Instruction( - 'fcvt_to_sint', r""" - Convert floating point to signed integer. - - Each lane in `x` is converted to a signed integer by rounding towards - zero. If `x` is NaN or if the signed integral value cannot be - represented in the result type, this instruction traps. - - The result type must have the same number of vector lanes as the input. - """, - ins=x, outs=a, can_trap=True) - -fcvt_to_sint_sat = Instruction( - 'fcvt_to_sint_sat', r""" - Convert floating point to signed integer as fcvt_to_sint does, but - saturates the input instead of trapping. NaN values are converted to 0. - """, - ins=x, outs=a) - -x = Operand('x', Int) -a = Operand('a', FloatTo) - -fcvt_from_uint = Instruction( - 'fcvt_from_uint', r""" - Convert unsigned integer to floating point. - - Each lane in `x` is interpreted as an unsigned integer and converted to - floating point using round to nearest, ties to even. - - The result type must have the same number of vector lanes as the input. - """, - ins=x, outs=a) - -fcvt_from_sint = Instruction( - 'fcvt_from_sint', r""" - Convert signed integer to floating point. - - Each lane in `x` is interpreted as a signed integer and converted to - floating point using round to nearest, ties to even. - - The result type must have the same number of vector lanes as the input. - """, - ins=x, outs=a) - -# -# Legalization helper instructions. -# - -WideInt = TypeVar( - 'WideInt', 'An integer type with lanes from `i16` upwards', - ints=(16, 64), simd=True) -x = Operand('x', WideInt) -lo = Operand( - 'lo', WideInt.half_width(), 'The low bits of `x`') -hi = Operand( - 'hi', WideInt.half_width(), 'The high bits of `x`') - -isplit = Instruction( - 'isplit', r""" - Split an integer into low and high parts. - - Vectors of integers are split lane-wise, so the results have the same - number of lanes as the input, but the lanes are half the size. - - Returns the low half of `x` and the high half of `x` as two independent - values. - """, - ins=x, outs=(lo, hi), is_ghost=True) - - -NarrowInt = TypeVar( - 'NarrowInt', 'An integer type with lanes type to `i32`', - ints=(8, 32), simd=True) -lo = Operand('lo', NarrowInt) -hi = Operand('hi', NarrowInt) -a = Operand( - 'a', NarrowInt.double_width(), - doc='The concatenation of `lo` and `hi`') - -iconcat = Instruction( - 'iconcat', r""" - Concatenate low and high bits to form a larger integer type. - - Vectors of integers are concatenated lane-wise such that the result has - the same number of lanes as the inputs, but the lanes are twice the - size. - """, - ins=(lo, hi), outs=a, is_ghost=True) - -GROUP.close() diff --git a/cranelift/codegen/meta-python/base/legalize.py b/cranelift/codegen/meta-python/base/legalize.py deleted file mode 100644 index 3e287a7b4e..0000000000 --- a/cranelift/codegen/meta-python/base/legalize.py +++ /dev/null @@ -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) - )) diff --git a/cranelift/codegen/meta-python/base/predicates.py b/cranelift/codegen/meta-python/base/predicates.py deleted file mode 100644 index 2a521f7ccb..0000000000 --- a/cranelift/codegen/meta-python/base/predicates.py +++ /dev/null @@ -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')) diff --git a/cranelift/codegen/meta-python/base/semantics.py b/cranelift/codegen/meta-python/base/semantics.py deleted file mode 100644 index ec1852133e..0000000000 --- a/cranelift/codegen/meta-python/base/semantics.py +++ /dev/null @@ -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), - )) diff --git a/cranelift/codegen/meta-python/base/settings.py b/cranelift/codegen/meta-python/base/settings.py deleted file mode 100644 index 534ef28830..0000000000 --- a/cranelift/codegen/meta-python/base/settings.py +++ /dev/null @@ -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()) diff --git a/cranelift/codegen/meta-python/base/types.py b/cranelift/codegen/meta-python/base/types.py deleted file mode 100644 index 9141f4fbce..0000000000 --- a/cranelift/codegen/meta-python/base/types.py +++ /dev/null @@ -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. - """) diff --git a/cranelift/codegen/meta-python/build.py b/cranelift/codegen/meta-python/build.py deleted file mode 100644 index 90a0cf254d..0000000000 --- a/cranelift/codegen/meta-python/build.py +++ /dev/null @@ -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() diff --git a/cranelift/codegen/meta-python/cdsl/__init__.py b/cranelift/codegen/meta-python/cdsl/__init__.py deleted file mode 100644 index a0b5d4c13b..0000000000 --- a/cranelift/codegen/meta-python/cdsl/__init__.py +++ /dev/null @@ -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 diff --git a/cranelift/codegen/meta-python/cdsl/ast.py b/cranelift/codegen/meta-python/cdsl/ast.py deleted file mode 100644 index 4a72fc486a..0000000000 --- a/cranelift/codegen/meta-python/cdsl/ast.py +++ /dev/null @@ -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) diff --git a/cranelift/codegen/meta-python/cdsl/formats.py b/cranelift/codegen/meta-python/cdsl/formats.py deleted file mode 100644 index 3eee94d248..0000000000 --- a/cranelift/codegen/meta-python/cdsl/formats.py +++ /dev/null @@ -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) diff --git a/cranelift/codegen/meta-python/cdsl/instructions.py b/cranelift/codegen/meta-python/cdsl/instructions.py deleted file mode 100644 index 34741991da..0000000000 --- a/cranelift/codegen/meta-python/cdsl/instructions.py +++ /dev/null @@ -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) diff --git a/cranelift/codegen/meta-python/cdsl/isa.py b/cranelift/codegen/meta-python/cdsl/isa.py deleted file mode 100644 index 3e3ac0b4fe..0000000000 --- a/cranelift/codegen/meta-python/cdsl/isa.py +++ /dev/null @@ -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 diff --git a/cranelift/codegen/meta-python/cdsl/operands.py b/cranelift/codegen/meta-python/cdsl/operands.py deleted file mode 100644 index cf99645df9..0000000000 --- a/cranelift/codegen/meta-python/cdsl/operands.py +++ /dev/null @@ -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'] diff --git a/cranelift/codegen/meta-python/cdsl/predicates.py b/cranelift/codegen/meta-python/cdsl/predicates.py deleted file mode 100644 index 0177c09fff..0000000000 --- a/cranelift/codegen/meta-python/cdsl/predicates.py +++ /dev/null @@ -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()) diff --git a/cranelift/codegen/meta-python/cdsl/registers.py b/cranelift/codegen/meta-python/cdsl/registers.py deleted file mode 100644 index 1e4ffe75b1..0000000000 --- a/cranelift/codegen/meta-python/cdsl/registers.py +++ /dev/null @@ -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)' diff --git a/cranelift/codegen/meta-python/cdsl/settings.py b/cranelift/codegen/meta-python/cdsl/settings.py deleted file mode 100644 index fe79024221..0000000000 --- a/cranelift/codegen/meta-python/cdsl/settings.py +++ /dev/null @@ -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 diff --git a/cranelift/codegen/meta-python/cdsl/test_ast.py b/cranelift/codegen/meta-python/cdsl/test_ast.py deleted file mode 100644 index 750142af0a..0000000000 --- a/cranelift/codegen/meta-python/cdsl/test_ast.py +++ /dev/null @@ -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'))") diff --git a/cranelift/codegen/meta-python/cdsl/test_package.py b/cranelift/codegen/meta-python/cdsl/test_package.py deleted file mode 100644 index b66d60d694..0000000000 --- a/cranelift/codegen/meta-python/cdsl/test_package.py +++ /dev/null @@ -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 diff --git a/cranelift/codegen/meta-python/cdsl/test_ti.py b/cranelift/codegen/meta-python/cdsl/test_ti.py deleted file mode 100644 index b88113a20f..0000000000 --- a/cranelift/codegen/meta-python/cdsl/test_ti.py +++ /dev/null @@ -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) - )) diff --git a/cranelift/codegen/meta-python/cdsl/test_typevar.py b/cranelift/codegen/meta-python/cdsl/test_typevar.py deleted file mode 100644 index 48806cc4aa..0000000000 --- a/cranelift/codegen/meta-python/cdsl/test_typevar.py +++ /dev/null @@ -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) diff --git a/cranelift/codegen/meta-python/cdsl/test_xform.py b/cranelift/codegen/meta-python/cdsl/test_xform.py deleted file mode 100644 index 424a7c824d..0000000000 --- a/cranelift/codegen/meta-python/cdsl/test_xform.py +++ /dev/null @@ -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 diff --git a/cranelift/codegen/meta-python/cdsl/ti.py b/cranelift/codegen/meta-python/cdsl/ti.py deleted file mode 100644 index 26f01f9e6b..0000000000 --- a/cranelift/codegen/meta-python/cdsl/ti.py +++ /dev/null @@ -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) diff --git a/cranelift/codegen/meta-python/cdsl/types.py b/cranelift/codegen/meta-python/cdsl/types.py deleted file mode 100644 index 26777152da..0000000000 --- a/cranelift/codegen/meta-python/cdsl/types.py +++ /dev/null @@ -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 diff --git a/cranelift/codegen/meta-python/cdsl/typevar.py b/cranelift/codegen/meta-python/cdsl/typevar.py deleted file mode 100644 index 9d2dace044..0000000000 --- a/cranelift/codegen/meta-python/cdsl/typevar.py +++ /dev/null @@ -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::({}),' - .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 diff --git a/cranelift/codegen/meta-python/cdsl/xform.py b/cranelift/codegen/meta-python/cdsl/xform.py deleted file mode 100644 index 27aa515f94..0000000000 --- a/cranelift/codegen/meta-python/cdsl/xform.py +++ /dev/null @@ -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 diff --git a/cranelift/codegen/meta-python/check.sh b/cranelift/codegen/meta-python/check.sh deleted file mode 100755 index fec63798df..0000000000 --- a/cranelift/codegen/meta-python/check.sh +++ /dev/null @@ -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 diff --git a/cranelift/codegen/meta-python/constant_hash.py b/cranelift/codegen/meta-python/constant_hash.py deleted file mode 100644 index e282936723..0000000000 --- a/cranelift/codegen/meta-python/constant_hash.py +++ /dev/null @@ -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 diff --git a/cranelift/codegen/meta-python/gen_binemit.py b/cranelift/codegen/meta-python/gen_binemit.py deleted file mode 100644 index c813d12977..0000000000 --- a/cranelift/codegen/meta-python/gen_binemit.py +++ /dev/null @@ -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('): - 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('): - 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) diff --git a/cranelift/codegen/meta-python/gen_build_deps.py b/cranelift/codegen/meta-python/gen_build_deps.py deleted file mode 100644 index 637865a55c..0000000000 --- a/cranelift/codegen/meta-python/gen_build_deps.py +++ /dev/null @@ -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)) diff --git a/cranelift/codegen/meta-python/gen_encoding.py b/cranelift/codegen/meta-python/gen_encoding.py deleted file mode 100644 index 5f70c97eca..0000000000 --- a/cranelift/codegen/meta-python/gen_encoding.py +++ /dev/null @@ -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< 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) diff --git a/cranelift/codegen/meta-python/isa/__init__.py b/cranelift/codegen/meta-python/isa/__init__.py deleted file mode 100644 index d3cff62c91..0000000000 --- a/cranelift/codegen/meta-python/isa/__init__.py +++ /dev/null @@ -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] diff --git a/cranelift/codegen/meta-python/isa/arm32/__init__.py b/cranelift/codegen/meta-python/isa/arm32/__init__.py deleted file mode 100644 index 06773f8754..0000000000 --- a/cranelift/codegen/meta-python/isa/arm32/__init__.py +++ /dev/null @@ -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 diff --git a/cranelift/codegen/meta-python/isa/arm32/defs.py b/cranelift/codegen/meta-python/isa/arm32/defs.py deleted file mode 100644 index 88b8c53db4..0000000000 --- a/cranelift/codegen/meta-python/isa/arm32/defs.py +++ /dev/null @@ -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) diff --git a/cranelift/codegen/meta-python/isa/arm32/registers.py b/cranelift/codegen/meta-python/isa/arm32/registers.py deleted file mode 100644 index 054e95fa0e..0000000000 --- a/cranelift/codegen/meta-python/isa/arm32/registers.py +++ /dev/null @@ -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()) diff --git a/cranelift/codegen/meta-python/isa/arm32/settings.py b/cranelift/codegen/meta-python/isa/arm32/settings.py deleted file mode 100644 index 5cc948cf2d..0000000000 --- a/cranelift/codegen/meta-python/isa/arm32/settings.py +++ /dev/null @@ -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()) diff --git a/cranelift/codegen/meta-python/isa/arm64/__init__.py b/cranelift/codegen/meta-python/isa/arm64/__init__.py deleted file mode 100644 index fb9005c037..0000000000 --- a/cranelift/codegen/meta-python/isa/arm64/__init__.py +++ /dev/null @@ -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 diff --git a/cranelift/codegen/meta-python/isa/arm64/defs.py b/cranelift/codegen/meta-python/isa/arm64/defs.py deleted file mode 100644 index 0350908f9b..0000000000 --- a/cranelift/codegen/meta-python/isa/arm64/defs.py +++ /dev/null @@ -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) diff --git a/cranelift/codegen/meta-python/isa/arm64/registers.py b/cranelift/codegen/meta-python/isa/arm64/registers.py deleted file mode 100644 index df680b1a14..0000000000 --- a/cranelift/codegen/meta-python/isa/arm64/registers.py +++ /dev/null @@ -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()) diff --git a/cranelift/codegen/meta-python/isa/arm64/settings.py b/cranelift/codegen/meta-python/isa/arm64/settings.py deleted file mode 100644 index 9a2fc13dc7..0000000000 --- a/cranelift/codegen/meta-python/isa/arm64/settings.py +++ /dev/null @@ -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()) diff --git a/cranelift/codegen/meta-python/isa/riscv/__init__.py b/cranelift/codegen/meta-python/isa/riscv/__init__.py deleted file mode 100644 index b58dd68ae2..0000000000 --- a/cranelift/codegen/meta-python/isa/riscv/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -""" -RISC-V Target -------------- - -`RISC-V `_ 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 diff --git a/cranelift/codegen/meta-python/isa/riscv/defs.py b/cranelift/codegen/meta-python/isa/riscv/defs.py deleted file mode 100644 index 404895c502..0000000000 --- a/cranelift/codegen/meta-python/isa/riscv/defs.py +++ /dev/null @@ -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) diff --git a/cranelift/codegen/meta-python/isa/riscv/encodings.py b/cranelift/codegen/meta-python/isa/riscv/encodings.py deleted file mode 100644 index 980a88ba00..0000000000 --- a/cranelift/codegen/meta-python/isa/riscv/encodings.py +++ /dev/null @@ -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) diff --git a/cranelift/codegen/meta-python/isa/riscv/recipes.py b/cranelift/codegen/meta-python/isa/riscv/recipes.py deleted file mode 100644 index ff27c31f8f..0000000000 --- a/cranelift/codegen/meta-python/isa/riscv/recipes.py +++ /dev/null @@ -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='') diff --git a/cranelift/codegen/meta-python/isa/riscv/registers.py b/cranelift/codegen/meta-python/isa/riscv/registers.py deleted file mode 100644 index d9d43f0432..0000000000 --- a/cranelift/codegen/meta-python/isa/riscv/registers.py +++ /dev/null @@ -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()) diff --git a/cranelift/codegen/meta-python/isa/riscv/settings.py b/cranelift/codegen/meta-python/isa/riscv/settings.py deleted file mode 100644 index c8b88db55e..0000000000 --- a/cranelift/codegen/meta-python/isa/riscv/settings.py +++ /dev/null @@ -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()) diff --git a/cranelift/codegen/meta-python/isa/x86/__init__.py b/cranelift/codegen/meta-python/isa/x86/__init__.py deleted file mode 100644 index 9691ce6470..0000000000 --- a/cranelift/codegen/meta-python/isa/x86/__init__.py +++ /dev/null @@ -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 diff --git a/cranelift/codegen/meta-python/isa/x86/defs.py b/cranelift/codegen/meta-python/isa/x86/defs.py deleted file mode 100644 index 00ac2bbbf1..0000000000 --- a/cranelift/codegen/meta-python/isa/x86/defs.py +++ /dev/null @@ -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] diff --git a/cranelift/codegen/meta-python/isa/x86/encodings.py b/cranelift/codegen/meta-python/isa/x86/encodings.py deleted file mode 100644 index 8da4912e77..0000000000 --- a/cranelift/codegen/meta-python/isa/x86/encodings.py +++ /dev/null @@ -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) diff --git a/cranelift/codegen/meta-python/isa/x86/instructions.py b/cranelift/codegen/meta-python/isa/x86/instructions.py deleted file mode 100644 index 6adc2ad689..0000000000 --- a/cranelift/codegen/meta-python/isa/x86/instructions.py +++ /dev/null @@ -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() diff --git a/cranelift/codegen/meta-python/isa/x86/legalize.py b/cranelift/codegen/meta-python/isa/x86/legalize.py deleted file mode 100644 index 15f08a07b9..0000000000 --- a/cranelift/codegen/meta-python/isa/x86/legalize.py +++ /dev/null @@ -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)) - )) diff --git a/cranelift/codegen/meta-python/isa/x86/recipes.py b/cranelift/codegen/meta-python/isa/x86/recipes.py deleted file mode 100644 index c596fcd108..0000000000 --- a/cranelift/codegen/meta-python/isa/x86/recipes.py +++ /dev/null @@ -1,2059 +0,0 @@ -""" -x86 Encoding recipes. -""" -from __future__ import absolute_import -from cdsl.isa import EncRecipe -from cdsl.predicates import IsSignedInt, IsEqual, Or -from cdsl.predicates import IsZero32BitFloat, IsZero64BitFloat -from cdsl.registers import RegClass -from base.formats import Unary, UnaryIeee32, UnaryIeee64, UnaryImm, UnaryBool -from base.formats import Binary, BinaryImm -from base.formats import MultiAry, NullAry -from base.formats import Trap, Call, CallIndirect, Store, Load -from base.formats import IntCompare, IntCompareImm, FloatCompare -from base.formats import IntCond, FloatCond -from base.formats import IntSelect, IntCondTrap, FloatCondTrap -from base.formats import Jump, Branch, BranchInt, BranchFloat -from base.formats import BranchTableEntry, BranchTableBase, IndirectJump -from base.formats import Ternary, FuncAddr, UnaryGlobalValue -from base.formats import RegMove, RegSpill, RegFill, CopySpecial -from base.formats import LoadComplex, StoreComplex -from base.formats import StackLoad -from .registers import GPR, ABCD, FPR -from .registers import GPR8, FPR8, FLAG -from .registers import StackGPR32, StackFPR32 -from .defs import supported_floatccs -from .settings import use_sse41 - -try: - from typing import Tuple, Dict, Sequence, Any # noqa - from cdsl.instructions import InstructionFormat # noqa - from cdsl.isa import ConstraintSeq, BranchRange, PredNode, OperandConstraint # noqa -except ImportError: - pass - - -# Opcode representation. -# -# Cranelift requires each recipe to have a single encoding size in bytes, and -# x86 opcodes are variable length, so we use separate recipes for different -# styles of opcodes and prefixes. The opcode format is indicated by the recipe -# name prefix: - -OPCODE_PREFIX = { - # Prefix bytes Name mmpp - (): ('Op1', 0b0000), - (0x66,): ('Mp1', 0b0001), - (0xf3,): ('Mp1', 0b0010), - (0xf2,): ('Mp1', 0b0011), - (0x0f,): ('Op2', 0b0100), - (0x66, 0x0f): ('Mp2', 0b0101), - (0xf3, 0x0f): ('Mp2', 0b0110), - (0xf2, 0x0f): ('Mp2', 0b0111), - (0x0f, 0x38): ('Op3', 0b1000), - (0x66, 0x0f, 0x38): ('Mp3', 0b1001), - (0xf3, 0x0f, 0x38): ('Mp3', 0b1010), - (0xf2, 0x0f, 0x38): ('Mp3', 0b1011), - (0x0f, 0x3a): ('Op3', 0b1100), - (0x66, 0x0f, 0x3a): ('Mp3', 0b1101), - (0xf3, 0x0f, 0x3a): ('Mp3', 0b1110), - (0xf2, 0x0f, 0x3a): ('Mp3', 0b1111) - } - -# The table above does not include the REX prefix which goes after the -# mandatory prefix. VEX/XOP and EVEX prefixes are not yet supported. Encodings -# using any of these prefixes are represented by separate recipes. -# -# The encoding bits are: -# -# 0-7: The opcode byte . -# 8-9: pp, mandatory prefix: -# 00 none (Op*) -# 01 66 (Mp*) -# 10 F3 (Mp*) -# 11 F2 (Mp*) -# 10-11: mm, opcode map: -# 00 (Op1/Mp1) -# 01 0F (Op2/Mp2) -# 10 0F 38 (Op3/Mp3) -# 11 0F 3A (Op3/Mp3) -# 12-14 rrr, opcode bits for the ModR/M byte for certain opcodes. -# 15: REX.W bit (or VEX.W/E) -# -# There is some redundancy between bits 8-11 and the recipe names, but we have -# enough bits, and the pp+mm format is ready for supporting VEX prefixes. - - -def decode_ops(ops, rrr=0, w=0): - # type: (Tuple[int, ...], int, int) -> Tuple[str, int] - """ - Given a sequence of opcode bytes, compute the recipe name prefix and - encoding bits. - """ - assert rrr <= 0b111 - assert w <= 1 - name, mmpp = OPCODE_PREFIX[ops[:-1]] - op = ops[-1] - assert op <= 256 - return (name, op | (mmpp << 8) | (rrr << 12) | (w << 15)) - - -def replace_put_op(emit, prefix): - # type: (str, str) -> str - """ - Given a snippet of Rust code (or None), replace the `PUT_OP` macro with the - corresponding `put_*` function from the `binemit.rs` module. - """ - if emit is None: - return None - else: - return emit.replace('PUT_OP', 'put_' + prefix.lower()) - - -# Register class mapping for no-REX instructions. -NOREX_MAP = { - GPR: GPR8, - FPR: FPR8 - } - - -def map_regs_norex(regs): - # type: (Sequence[OperandConstraint]) -> Sequence[OperandConstraint] - return tuple(NOREX_MAP.get(rc, rc) if isinstance(rc, RegClass) else rc - for rc in regs) - - -class TailRecipe: - """ - Generate encoding recipes on demand. - - x86 encodings are somewhat orthogonal with the opcode representation on - one side and the ModR/M, SIB and immediate fields on the other side. - - A `TailRecipe` represents the part of an encoding that follow the opcode. - It is used to generate full encoding recipes on demand when combined with - an opcode. - - The arguments are the same as for an `EncRecipe`, except for `size` which - does not include the size of the opcode. - - The `when_prefixed` parameter specifies a recipe that should be substituted - for this one when a REX (or VEX) prefix is present. This is relevant for - recipes that can only access the ABCD registers without a REX prefix, but - are able to access all registers with a prefix. - - The `requires_prefix` parameter indicates that the recipe can't be used - without a REX prefix. - - The `emit` parameter contains Rust code to actually emit an encoding, like - `EncRecipe` does it. Additionally, the text `PUT_OP` is substituted with - the proper `put_*` function from the `x86/binemit.rs` module. - """ - - def __init__( - self, - name, # type: str - format, # type: InstructionFormat - base_size, # type: int - ins, # type: ConstraintSeq - outs, # type: ConstraintSeq - branch_range=None, # type: int - clobbers_flags=True, # type: bool - instp=None, # type: PredNode - isap=None, # type: PredNode - when_prefixed=None, # type: TailRecipe - requires_prefix=False, # type: bool - emit=None, # type: str - compute_size=None # type: str - ): - # type: (...) -> None - self.name = name - self.format = format - self.base_size = base_size - self.ins = ins - self.outs = outs - self.branch_range = branch_range - self.clobbers_flags = clobbers_flags - self.instp = instp - self.isap = isap - self.when_prefixed = when_prefixed - self.requires_prefix = requires_prefix - self.emit = emit - self.compute_size = compute_size - - # Cached recipes, keyed by name prefix. - self.recipes = dict() # type: Dict[str, EncRecipe] - - def __call__(self, *ops, **kwargs): - # type: (*int, **int) -> Tuple[EncRecipe, int] - """ - Create an encoding recipe and encoding bits for the opcode bytes in - `ops`. - """ - assert not self.requires_prefix, "Tail recipe requires REX prefix." - rrr = kwargs.get('rrr', 0) - w = kwargs.get('w', 0) - name, bits = decode_ops(ops, rrr, w) - base_size = len(ops) + self.base_size - - # All branch ranges are relative to the end of the instruction. - branch_range = None # type BranchRange - if self.branch_range is not None: - branch_range = (base_size, self.branch_range) - - if name not in self.recipes: - recipe = EncRecipe( - name + self.name, - self.format, - base_size, - ins=self.ins, - outs=self.outs, - branch_range=branch_range, - clobbers_flags=self.clobbers_flags, - instp=self.instp, - isap=self.isap, - emit=replace_put_op(self.emit, name), - compute_size=self.compute_size) - - recipe.ins = map_regs_norex(recipe.ins) - recipe.outs = map_regs_norex(recipe.outs) - self.recipes[name] = recipe - return (self.recipes[name], bits) - - def rex(self, *ops, **kwargs): - # type: (*int, **int) -> Tuple[EncRecipe, int] - """ - Create a REX encoding recipe and encoding bits for the opcode bytes in - `ops`. - - The recipe will always generate a REX prefix, whether it is required or - not. For instructions that don't require a REX prefix, two encodings - should be added: One with REX and one without. - """ - # Use the prefixed alternative recipe when applicable. - if self.when_prefixed: - return self.when_prefixed.rex(*ops, **kwargs) - - rrr = kwargs.get('rrr', 0) - w = kwargs.get('w', 0) - name, bits = decode_ops(ops, rrr, w) - name = 'Rex' + name - base_size = 1 + len(ops) + self.base_size - - # All branch ranges are relative to the end of the instruction. - branch_range = None # type BranchRange - if self.branch_range is not None: - branch_range = (base_size, self.branch_range) - - if name not in self.recipes: - recipe = EncRecipe( - name + self.name, - self.format, - base_size, - ins=self.ins, - outs=self.outs, - branch_range=branch_range, - clobbers_flags=self.clobbers_flags, - instp=self.instp, - isap=self.isap, - emit=replace_put_op(self.emit, name), - compute_size=self.compute_size) - self.recipes[name] = recipe - - return (self.recipes[name], bits) - - @staticmethod - def check_names(globs): - # type: (Dict[str, Any]) -> None - for name, obj in globs.items(): - if isinstance(obj, TailRecipe): - assert name == obj.name, "Mismatched TailRecipe name: " + name - - -def floatccs(iform): - # type: (InstructionFormat) -> PredNode - """ - Return an instruction predicate that checks in `iform.cond` is one of the - directly supported floating point condition codes. - """ - return Or(*(IsEqual(iform.cond, cc) for cc in supported_floatccs)) - - -def valid_scale(iform): - # type: (InstructionFormat) -> PredNode - """ - Return an instruction predicate that checks if `iform.imm` is a valid - `scale` for a SIB byte. - """ - return Or(IsEqual(iform.imm, 1), - IsEqual(iform.imm, 2), - IsEqual(iform.imm, 4), - IsEqual(iform.imm, 8)) - - -# A null unary instruction that takes a GPR register. Can be used for identity -# copies and no-op conversions. -null = EncRecipe('null', Unary, base_size=0, ins=GPR, outs=0, emit='') - -stacknull = EncRecipe('stacknull', Unary, base_size=0, ins=StackGPR32, - outs=StackGPR32, emit='') - -debugtrap = EncRecipe('debugtrap', NullAry, base_size=1, ins=(), outs=(), - emit=''' - sink.put1(0xcc); - ''') - -# XX opcode, no ModR/M. -trap = TailRecipe( - 'trap', Trap, base_size=0, ins=(), outs=(), - emit=''' - sink.trap(code, func.srclocs[inst]); - PUT_OP(bits, BASE_REX, sink); - ''') - -# Macro: conditional jump over a ud2. -trapif = EncRecipe( - 'trapif', IntCondTrap, base_size=4, ins=FLAG.rflags, outs=(), - clobbers_flags=False, - emit=''' - // Jump over a 2-byte ud2. - sink.put1(0x70 | (icc2opc(cond.inverse()) as u8)); - sink.put1(2); - // ud2. - sink.trap(code, func.srclocs[inst]); - sink.put1(0x0f); - sink.put1(0x0b); - ''') - -trapff = EncRecipe( - 'trapff', FloatCondTrap, base_size=4, ins=FLAG.rflags, outs=(), - clobbers_flags=False, - instp=floatccs(FloatCondTrap), - emit=''' - // Jump over a 2-byte ud2. - sink.put1(0x70 | (fcc2opc(cond.inverse()) as u8)); - sink.put1(2); - // ud2. - sink.trap(code, func.srclocs[inst]); - sink.put1(0x0f); - sink.put1(0x0b); - ''') - - -# XX /r -rr = TailRecipe( - 'rr', Binary, base_size=1, ins=(GPR, GPR), outs=0, - emit=''' - PUT_OP(bits, rex2(in_reg0, in_reg1), sink); - modrm_rr(in_reg0, in_reg1, sink); - ''') - -# XX /r with operands swapped. (RM form). -rrx = TailRecipe( - 'rrx', Binary, base_size=1, ins=(GPR, GPR), outs=0, - emit=''' - PUT_OP(bits, rex2(in_reg1, in_reg0), sink); - modrm_rr(in_reg1, in_reg0, sink); - ''') - -# XX /r with FPR ins and outs. A form. -fa = TailRecipe( - 'fa', Binary, base_size=1, ins=(FPR, FPR), outs=0, - emit=''' - PUT_OP(bits, rex2(in_reg1, in_reg0), sink); - modrm_rr(in_reg1, in_reg0, sink); - ''') - -# XX /r with FPR ins and outs. A form with input operands swapped. -fax = TailRecipe( - 'fax', Binary, base_size=1, ins=(FPR, FPR), outs=1, - emit=''' - PUT_OP(bits, rex2(in_reg0, in_reg1), sink); - modrm_rr(in_reg0, in_reg1, sink); - ''') - -# XX /n for a unary operation with extension bits. -ur = TailRecipe( - 'ur', Unary, base_size=1, ins=GPR, outs=0, - emit=''' - PUT_OP(bits, rex1(in_reg0), sink); - modrm_r_bits(in_reg0, bits, sink); - ''') - -# XX /r, but for a unary operator with separate input/output register, like -# copies. MR form, preserving flags. -umr = TailRecipe( - 'umr', Unary, base_size=1, ins=GPR, outs=GPR, - clobbers_flags=False, - emit=''' - PUT_OP(bits, rex2(out_reg0, in_reg0), sink); - modrm_rr(out_reg0, in_reg0, sink); - ''') - -# Same as umr, but with FPR -> GPR registers. -rfumr = TailRecipe( - 'rfumr', Unary, base_size=1, ins=FPR, outs=GPR, - clobbers_flags=False, - emit=''' - PUT_OP(bits, rex2(out_reg0, in_reg0), sink); - modrm_rr(out_reg0, in_reg0, sink); - ''') - -# XX /r, but for a unary operator with separate input/output register. -# RM form. Clobbers FLAGS. -urm = TailRecipe( - 'urm', Unary, base_size=1, ins=GPR, outs=GPR, - emit=''' - PUT_OP(bits, rex2(in_reg0, out_reg0), sink); - modrm_rr(in_reg0, out_reg0, sink); - ''') - -# XX /r. Same as urm, but doesn't clobber FLAGS. -urm_noflags = TailRecipe( - 'urm_noflags', Unary, base_size=1, ins=GPR, outs=GPR, - clobbers_flags=False, - emit=''' - PUT_OP(bits, rex2(in_reg0, out_reg0), sink); - modrm_rr(in_reg0, out_reg0, sink); - ''') - -# XX /r. Same as urm_noflags, but input limited to ABCD. -urm_noflags_abcd = TailRecipe( - 'urm_noflags_abcd', Unary, base_size=1, ins=ABCD, outs=GPR, - when_prefixed=urm_noflags, - clobbers_flags=False, - emit=''' - PUT_OP(bits, rex2(in_reg0, out_reg0), sink); - modrm_rr(in_reg0, out_reg0, sink); - ''') - -# XX /r, RM form, FPR -> FPR. -furm = TailRecipe( - 'furm', Unary, base_size=1, ins=FPR, outs=FPR, - clobbers_flags=False, - emit=''' - PUT_OP(bits, rex2(in_reg0, out_reg0), sink); - modrm_rr(in_reg0, out_reg0, sink); - ''') - -# XX /r, RM form, GPR -> FPR. -frurm = TailRecipe( - 'frurm', Unary, base_size=1, ins=GPR, outs=FPR, - clobbers_flags=False, - emit=''' - PUT_OP(bits, rex2(in_reg0, out_reg0), sink); - modrm_rr(in_reg0, out_reg0, sink); - ''') - -# XX /r, RM form, FPR -> GPR. -rfurm = TailRecipe( - 'rfurm', Unary, base_size=1, ins=FPR, outs=GPR, - clobbers_flags=False, - emit=''' - PUT_OP(bits, rex2(in_reg0, out_reg0), sink); - modrm_rr(in_reg0, out_reg0, sink); - ''') - -# XX /r, RMI form for one of the roundXX SSE 4.1 instructions. -furmi_rnd = TailRecipe( - 'furmi_rnd', Unary, base_size=2, ins=FPR, outs=FPR, - isap=use_sse41, - emit=''' - PUT_OP(bits, rex2(in_reg0, out_reg0), sink); - modrm_rr(in_reg0, out_reg0, sink); - sink.put1(match opcode { - Opcode::Nearest => 0b00, - Opcode::Floor => 0b01, - Opcode::Ceil => 0b10, - Opcode::Trunc => 0b11, - x => panic!("{} unexpected for furmi_rnd", opcode), - }); - ''') - -# XX /r, for regmove instructions. -rmov = TailRecipe( - 'rmov', RegMove, base_size=1, ins=GPR, outs=(), - clobbers_flags=False, - emit=''' - PUT_OP(bits, rex2(dst, src), sink); - modrm_rr(dst, src, sink); - ''') - -# XX /r, for regmove instructions (FPR version, RM encoded). -frmov = TailRecipe( - 'frmov', RegMove, base_size=1, ins=FPR, outs=(), - clobbers_flags=False, - emit=''' - PUT_OP(bits, rex2(src, dst), sink); - modrm_rr(src, dst, sink); - ''') - -# XX /n with one arg in %rcx, for shifts. -rc = TailRecipe( - 'rc', Binary, base_size=1, ins=(GPR, GPR.rcx), outs=0, - emit=''' - PUT_OP(bits, rex1(in_reg0), sink); - modrm_r_bits(in_reg0, bits, sink); - ''') - -# XX /n for division: inputs in %rax, %rdx, r. Outputs in %rax, %rdx. -div = TailRecipe( - 'div', Ternary, base_size=1, - ins=(GPR.rax, GPR.rdx, GPR), outs=(GPR.rax, GPR.rdx), - emit=''' - sink.trap(TrapCode::IntegerDivisionByZero, func.srclocs[inst]); - PUT_OP(bits, rex1(in_reg2), sink); - modrm_r_bits(in_reg2, bits, sink); - ''') - -# XX /n for {s,u}mulx: inputs in %rax, r. Outputs in %rdx(hi):%rax(lo) -mulx = TailRecipe( - 'mulx', Binary, base_size=1, - ins=(GPR.rax, GPR), outs=(GPR.rax, GPR.rdx), - emit=''' - PUT_OP(bits, rex1(in_reg1), sink); - modrm_r_bits(in_reg1, bits, sink); - ''') - -# XX /n ib with 8-bit immediate sign-extended. -r_ib = TailRecipe( - 'r_ib', BinaryImm, base_size=2, ins=GPR, outs=0, - instp=IsSignedInt(BinaryImm.imm, 8), - emit=''' - PUT_OP(bits, rex1(in_reg0), sink); - modrm_r_bits(in_reg0, bits, sink); - let imm: i64 = imm.into(); - sink.put1(imm as u8); - ''') - -# XX /n id with 32-bit immediate sign-extended. -r_id = TailRecipe( - 'r_id', BinaryImm, base_size=5, ins=GPR, outs=0, - instp=IsSignedInt(BinaryImm.imm, 32), - emit=''' - PUT_OP(bits, rex1(in_reg0), sink); - modrm_r_bits(in_reg0, bits, sink); - let imm: i64 = imm.into(); - sink.put4(imm as u32); - ''') - -# XX /n id with 32-bit immediate sign-extended. UnaryImm version. -u_id = TailRecipe( - 'u_id', UnaryImm, base_size=5, ins=(), outs=GPR, - instp=IsSignedInt(UnaryImm.imm, 32), - emit=''' - PUT_OP(bits, rex1(out_reg0), sink); - modrm_r_bits(out_reg0, bits, sink); - let imm: i64 = imm.into(); - sink.put4(imm as u32); - ''') - -# XX+rd id unary with 32-bit immediate. Note no recipe predicate. -pu_id = TailRecipe( - 'pu_id', UnaryImm, base_size=4, ins=(), outs=GPR, - emit=''' - // The destination register is encoded in the low bits of the opcode. - // No ModR/M. - PUT_OP(bits | (out_reg0 & 7), rex1(out_reg0), sink); - let imm: i64 = imm.into(); - sink.put4(imm as u32); - ''') - -# XX+rd id unary with bool immediate. Note no recipe predicate. -pu_id_bool = TailRecipe( - 'pu_id_bool', UnaryBool, base_size=4, ins=(), outs=GPR, - emit=''' - // The destination register is encoded in the low bits of the opcode. - // No ModR/M. - PUT_OP(bits | (out_reg0 & 7), rex1(out_reg0), sink); - let imm: u32 = if imm { 1 } else { 0 }; - sink.put4(imm); - ''') - -# XX+rd iq unary with 64-bit immediate. -pu_iq = TailRecipe( - 'pu_iq', UnaryImm, base_size=8, ins=(), outs=GPR, - emit=''' - PUT_OP(bits | (out_reg0 & 7), rex1(out_reg0), sink); - let imm: i64 = imm.into(); - sink.put8(imm as u64); - ''') - -# XX /n Unary with floating point 32-bit immediate equal to zero. -f32imm_z = TailRecipe( - 'f32imm_z', UnaryIeee32, base_size=1, ins=(), outs=FPR, - instp=IsZero32BitFloat(UnaryIeee32.imm), - emit=''' - PUT_OP(bits, rex2(out_reg0, out_reg0), sink); - modrm_rr(out_reg0, out_reg0, sink); - ''') - -# XX /n Unary with floating point 64-bit immediate equal to zero. -f64imm_z = TailRecipe( - 'f64imm_z', UnaryIeee64, base_size=1, ins=(), outs=FPR, - instp=IsZero64BitFloat(UnaryIeee64.imm), - emit=''' - PUT_OP(bits, rex2(out_reg0, out_reg0), sink); - modrm_rr(out_reg0, out_reg0, sink); - ''') - -pushq = TailRecipe( - 'pushq', Unary, base_size=0, ins=GPR, outs=(), - emit=''' - sink.trap(TrapCode::StackOverflow, func.srclocs[inst]); - PUT_OP(bits | (in_reg0 & 7), rex1(in_reg0), sink); - ''') - -popq = TailRecipe( - 'popq', NullAry, base_size=0, ins=(), outs=GPR, - emit=''' - PUT_OP(bits | (out_reg0 & 7), rex1(out_reg0), sink); - ''') - -# XX /r, for regmove instructions. -copysp = TailRecipe( - 'copysp', CopySpecial, base_size=1, ins=(), outs=(), - clobbers_flags=False, - emit=''' - PUT_OP(bits, rex2(dst, src), sink); - modrm_rr(dst, src, sink); - ''') - -adjustsp = TailRecipe( - 'adjustsp', Unary, base_size=1, ins=(GPR), outs=(), - emit=''' - PUT_OP(bits, rex2(RU::rsp.into(), in_reg0), sink); - modrm_rr(RU::rsp.into(), in_reg0, sink); - ''') - -adjustsp_ib = TailRecipe( - 'adjustsp_ib', UnaryImm, base_size=2, ins=(), outs=(), - instp=IsSignedInt(UnaryImm.imm, 8), - emit=''' - PUT_OP(bits, rex1(RU::rsp.into()), sink); - modrm_r_bits(RU::rsp.into(), bits, sink); - let imm: i64 = imm.into(); - sink.put1(imm as u8); - ''') - -adjustsp_id = TailRecipe( - 'adjustsp_id', UnaryImm, base_size=5, ins=(), outs=(), - instp=IsSignedInt(UnaryImm.imm, 32), - emit=''' - PUT_OP(bits, rex1(RU::rsp.into()), sink); - modrm_r_bits(RU::rsp.into(), bits, sink); - let imm: i64 = imm.into(); - sink.put4(imm as u32); - ''') - - -# XX+rd id with Abs4 function relocation. -fnaddr4 = TailRecipe( - 'fnaddr4', FuncAddr, base_size=4, ins=(), outs=GPR, - emit=''' - PUT_OP(bits | (out_reg0 & 7), rex1(out_reg0), sink); - sink.reloc_external(Reloc::Abs4, - &func.dfg.ext_funcs[func_ref].name, - 0); - sink.put4(0); - ''') - -# XX+rd iq with Abs8 function relocation. -fnaddr8 = TailRecipe( - 'fnaddr8', FuncAddr, base_size=8, ins=(), outs=GPR, - emit=''' - PUT_OP(bits | (out_reg0 & 7), rex1(out_reg0), sink); - sink.reloc_external(Reloc::Abs8, - &func.dfg.ext_funcs[func_ref].name, - 0); - sink.put8(0); - ''') - -# Similar to fnaddr4, but writes !0 (this is used by BaldrMonkey). -allones_fnaddr4 = TailRecipe( - 'allones_fnaddr4', FuncAddr, base_size=4, ins=(), outs=GPR, - emit=''' - PUT_OP(bits | (out_reg0 & 7), rex1(out_reg0), sink); - sink.reloc_external(Reloc::Abs4, - &func.dfg.ext_funcs[func_ref].name, - 0); - // Write the immediate as `!0` for the benefit of BaldrMonkey. - sink.put4(!0); - ''') - -# Similar to fnaddr8, but writes !0 (this is used by BaldrMonkey). -allones_fnaddr8 = TailRecipe( - 'allones_fnaddr8', FuncAddr, base_size=8, ins=(), outs=GPR, - emit=''' - PUT_OP(bits | (out_reg0 & 7), rex1(out_reg0), sink); - sink.reloc_external(Reloc::Abs8, - &func.dfg.ext_funcs[func_ref].name, - 0); - // Write the immediate as `!0` for the benefit of BaldrMonkey. - sink.put8(!0); - ''') - -pcrel_fnaddr8 = TailRecipe( - 'pcrel_fnaddr8', FuncAddr, base_size=5, ins=(), outs=GPR, - # rex2 gets passed 0 for r/m register because the upper bit of - # r/m doesnt get decoded when in rip-relative addressing mode. - emit=''' - PUT_OP(bits, rex2(0, out_reg0), sink); - modrm_riprel(out_reg0, sink); - // The addend adjusts for the difference between the end of the - // instruction and the beginning of the immediate field. - sink.reloc_external(Reloc::X86PCRel4, - &func.dfg.ext_funcs[func_ref].name, - -4); - sink.put4(0); - ''') - -got_fnaddr8 = TailRecipe( - 'got_fnaddr8', FuncAddr, base_size=5, ins=(), outs=GPR, - # rex2 gets passed 0 for r/m register because the upper bit of - # r/m doesnt get decoded when in rip-relative addressing mode. - emit=''' - PUT_OP(bits, rex2(0, out_reg0), sink); - modrm_riprel(out_reg0, sink); - // The addend adjusts for the difference between the end of the - // instruction and the beginning of the immediate field. - sink.reloc_external(Reloc::X86GOTPCRel4, - &func.dfg.ext_funcs[func_ref].name, - -4); - sink.put4(0); - ''') - - -# XX+rd id with Abs4 globalsym relocation. -gvaddr4 = TailRecipe( - 'gvaddr4', UnaryGlobalValue, base_size=4, ins=(), outs=GPR, - emit=''' - PUT_OP(bits | (out_reg0 & 7), rex1(out_reg0), sink); - sink.reloc_external(Reloc::Abs4, - &func.global_values[global_value].symbol_name(), - 0); - sink.put4(0); - ''') - -# XX+rd iq with Abs8 globalsym relocation. -gvaddr8 = TailRecipe( - 'gvaddr8', UnaryGlobalValue, base_size=8, ins=(), outs=GPR, - emit=''' - PUT_OP(bits | (out_reg0 & 7), rex1(out_reg0), sink); - sink.reloc_external(Reloc::Abs8, - &func.global_values[global_value].symbol_name(), - 0); - sink.put8(0); - ''') - -# XX+rd iq with PCRel4 globalsym relocation. -pcrel_gvaddr8 = TailRecipe( - 'pcrel_gvaddr8', UnaryGlobalValue, base_size=5, ins=(), outs=GPR, - emit=''' - PUT_OP(bits, rex2(0, out_reg0), sink); - modrm_rm(5, out_reg0, sink); - // The addend adjusts for the difference between the end of the - // instruction and the beginning of the immediate field. - sink.reloc_external(Reloc::X86PCRel4, - &func.global_values[global_value].symbol_name(), - -4); - sink.put4(0); - ''') - -# XX+rd iq with Abs8 globalsym relocation. -got_gvaddr8 = TailRecipe( - 'got_gvaddr8', UnaryGlobalValue, base_size=5, ins=(), outs=GPR, - emit=''' - PUT_OP(bits, rex2(0, out_reg0), sink); - modrm_rm(5, out_reg0, sink); - // The addend adjusts for the difference between the end of the - // instruction and the beginning of the immediate field. - sink.reloc_external(Reloc::X86GOTPCRel4, - &func.global_values[global_value].symbol_name(), - -4); - sink.put4(0); - ''') - -# -# Stack addresses. -# -# TODO: Alternative forms for 8-bit immediates, when applicable. -# - -spaddr4_id = TailRecipe( - 'spaddr4_id', StackLoad, base_size=6, ins=(), outs=GPR, - emit=''' - let sp = StackRef::sp(stack_slot, &func.stack_slots); - let base = stk_base(sp.base); - PUT_OP(bits, rex2(out_reg0, base), sink); - modrm_sib_disp8(out_reg0, sink); - sib_noindex(base, sink); - let imm : i32 = offset.into(); - sink.put4(sp.offset.checked_add(imm).unwrap() as u32); - ''') - -spaddr8_id = TailRecipe( - 'spaddr8_id', StackLoad, base_size=6, ins=(), outs=GPR, - emit=''' - let sp = StackRef::sp(stack_slot, &func.stack_slots); - let base = stk_base(sp.base); - PUT_OP(bits, rex2(base, out_reg0), sink); - modrm_sib_disp32(out_reg0, sink); - sib_noindex(base, sink); - let imm : i32 = offset.into(); - sink.put4(sp.offset.checked_add(imm).unwrap() as u32); - ''') - - -# -# Store recipes. -# - -# XX /r register-indirect store with no offset. -st = TailRecipe( - 'st', Store, base_size=1, ins=(GPR, GPR), outs=(), - instp=IsEqual(Store.offset, 0), - clobbers_flags=False, - compute_size="size_plus_maybe_sib_or_offset_for_in_reg_1", - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex2(in_reg1, in_reg0), sink); - if needs_sib_byte(in_reg1) { - modrm_sib(in_reg0, sink); - sib_noindex(in_reg1, sink); - } else if needs_offset(in_reg1) { - modrm_disp8(in_reg1, in_reg0, sink); - sink.put1(0); - } else { - modrm_rm(in_reg1, in_reg0, sink); - } - ''') - -# XX /r register-indirect store with index and no offset. -stWithIndex = TailRecipe( - 'stWithIndex', StoreComplex, base_size=2, - ins=(GPR, GPR, GPR), - outs=(), - instp=IsEqual(StoreComplex.offset, 0), - clobbers_flags=False, - compute_size="size_plus_maybe_offset_for_in_reg_1", - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex3(in_reg1, in_reg0, in_reg2), sink); - // The else branch always inserts an SIB byte. - if needs_offset(in_reg1) { - modrm_sib_disp8(in_reg0, sink); - sib(0, in_reg2, in_reg1, sink); - sink.put1(0); - } else { - modrm_sib(in_reg0, sink); - sib(0, in_reg2, in_reg1, sink); - } - ''') - -# XX /r register-indirect store with no offset. -# Only ABCD allowed for stored value. This is for byte stores with no REX. -st_abcd = TailRecipe( - 'st_abcd', Store, base_size=1, ins=(ABCD, GPR), outs=(), - instp=IsEqual(Store.offset, 0), - when_prefixed=st, - clobbers_flags=False, - compute_size="size_plus_maybe_sib_or_offset_for_in_reg_1", - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex2(in_reg1, in_reg0), sink); - if needs_sib_byte(in_reg1) { - modrm_sib(in_reg0, sink); - sib_noindex(in_reg1, sink); - } else if needs_offset(in_reg1) { - modrm_disp8(in_reg1, in_reg0, sink); - sink.put1(0); - } else { - modrm_rm(in_reg1, in_reg0, sink); - } - ''') - -# XX /r register-indirect store with index and no offset. -# Only ABCD allowed for stored value. This is for byte stores with no REX. -stWithIndex_abcd = TailRecipe( - 'stWithIndex_abcd', StoreComplex, base_size=2, - ins=(ABCD, GPR, GPR), - outs=(), - instp=IsEqual(StoreComplex.offset, 0), - clobbers_flags=False, - compute_size="size_plus_maybe_offset_for_in_reg_1", - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex3(in_reg1, in_reg0, in_reg2), sink); - // The else branch always inserts an SIB byte. - if needs_offset(in_reg1) { - modrm_sib_disp8(in_reg0, sink); - sib(0, in_reg2, in_reg1, sink); - sink.put1(0); - } else { - modrm_sib(in_reg0, sink); - sib(0, in_reg2, in_reg1, sink); - } - ''') - -# XX /r register-indirect store of FPR with no offset. -fst = TailRecipe( - 'fst', Store, base_size=1, ins=(FPR, GPR), outs=(), - instp=IsEqual(Store.offset, 0), - clobbers_flags=False, - compute_size="size_plus_maybe_sib_or_offset_for_in_reg_1", - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex2(in_reg1, in_reg0), sink); - if needs_sib_byte(in_reg1) { - modrm_sib(in_reg0, sink); - sib_noindex(in_reg1, sink); - } else if needs_offset(in_reg1) { - modrm_disp8(in_reg1, in_reg0, sink); - sink.put1(0); - } else { - modrm_rm(in_reg1, in_reg0, sink); - } - ''') -# XX /r register-indirect store with index and no offset of FPR. -fstWithIndex = TailRecipe( - 'fstWithIndex', StoreComplex, base_size=2, - ins=(FPR, GPR, GPR), outs=(), - instp=IsEqual(StoreComplex.offset, 0), - clobbers_flags=False, - compute_size="size_plus_maybe_offset_for_in_reg_1", - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex3(in_reg1, in_reg0, in_reg2), sink); - // The else branch always inserts an SIB byte. - if needs_offset(in_reg1) { - modrm_sib_disp8(in_reg0, sink); - sib(0, in_reg2, in_reg1, sink); - sink.put1(0); - } else { - modrm_sib(in_reg0, sink); - sib(0, in_reg2, in_reg1, sink); - } - ''') - -# XX /r register-indirect store with 8-bit offset. -stDisp8 = TailRecipe( - 'stDisp8', Store, base_size=2, ins=(GPR, GPR), outs=(), - instp=IsSignedInt(Store.offset, 8), - clobbers_flags=False, - compute_size="size_plus_maybe_sib_for_in_reg_1", - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex2(in_reg1, in_reg0), sink); - if needs_sib_byte(in_reg1) { - modrm_sib_disp8(in_reg0, sink); - sib_noindex(in_reg1, sink); - } else { - modrm_disp8(in_reg1, in_reg0, sink); - } - let offset: i32 = offset.into(); - sink.put1(offset as u8); - ''') - -# XX /r register-indirect store with index and 8-bit offset. -stWithIndexDisp8 = TailRecipe( - 'stWithIndexDisp8', StoreComplex, base_size=3, - ins=(GPR, GPR, GPR), - outs=(), - instp=IsSignedInt(StoreComplex.offset, 8), - clobbers_flags=False, - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex3(in_reg1, in_reg0, in_reg2), sink); - modrm_sib_disp8(in_reg0, sink); - sib(0, in_reg2, in_reg1, sink); - let offset: i32 = offset.into(); - sink.put1(offset as u8); - ''') - -# XX /r register-indirect store with 8-bit offset. -# Only ABCD allowed for stored value. This is for byte stores with no REX. -stDisp8_abcd = TailRecipe( - 'stDisp8_abcd', Store, base_size=2, ins=(ABCD, GPR), outs=(), - instp=IsSignedInt(Store.offset, 8), - when_prefixed=stDisp8, - clobbers_flags=False, - compute_size="size_plus_maybe_sib_for_in_reg_1", - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex2(in_reg1, in_reg0), sink); - if needs_sib_byte(in_reg1) { - modrm_sib_disp8(in_reg0, sink); - sib_noindex(in_reg1, sink); - } else { - modrm_disp8(in_reg1, in_reg0, sink); - } - let offset: i32 = offset.into(); - sink.put1(offset as u8); - ''') - -# XX /r register-indirect store with index and 8-bit offset. -# Only ABCD allowed for stored value. This is for byte stores with no REX. -stWithIndexDisp8_abcd = TailRecipe( - 'stWithIndexDisp8_abcd', StoreComplex, base_size=3, - ins=(ABCD, GPR, GPR), - outs=(), - instp=IsSignedInt(StoreComplex.offset, 8), - clobbers_flags=False, - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex3(in_reg1, in_reg0, in_reg2), sink); - modrm_sib_disp8(in_reg0, sink); - sib(0, in_reg2, in_reg1, sink); - let offset: i32 = offset.into(); - sink.put1(offset as u8); - ''') - -# XX /r register-indirect store with 8-bit offset of FPR. -fstDisp8 = TailRecipe( - 'fstDisp8', Store, base_size=2, ins=(FPR, GPR), outs=(), - instp=IsSignedInt(Store.offset, 8), - clobbers_flags=False, - compute_size='size_plus_maybe_sib_for_in_reg_1', - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex2(in_reg1, in_reg0), sink); - if needs_sib_byte(in_reg1) { - modrm_sib_disp8(in_reg0, sink); - sib_noindex(in_reg1, sink); - } else { - modrm_disp8(in_reg1, in_reg0, sink); - } - let offset: i32 = offset.into(); - sink.put1(offset as u8); - ''') - -# XX /r register-indirect store with index and 8-bit offset of FPR. -fstWithIndexDisp8 = TailRecipe( - 'fstWithIndexDisp8', StoreComplex, base_size=3, - ins=(FPR, GPR, GPR), - outs=(), - instp=IsSignedInt(StoreComplex.offset, 8), - clobbers_flags=False, - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex3(in_reg1, in_reg0, in_reg2), sink); - modrm_sib_disp8(in_reg0, sink); - sib(0, in_reg2, in_reg1, sink); - let offset: i32 = offset.into(); - sink.put1(offset as u8); - ''') - -# XX /r register-indirect store with 32-bit offset. -stDisp32 = TailRecipe( - 'stDisp32', Store, base_size=5, ins=(GPR, GPR), outs=(), - clobbers_flags=False, - compute_size='size_plus_maybe_sib_for_in_reg_1', - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex2(in_reg1, in_reg0), sink); - if needs_sib_byte(in_reg1) { - modrm_sib_disp32(in_reg0, sink); - sib_noindex(in_reg1, sink); - } else { - modrm_disp32(in_reg1, in_reg0, sink); - } - let offset: i32 = offset.into(); - sink.put4(offset as u32); - ''') - -# XX /r register-indirect store with index and 32-bit offset. -stWithIndexDisp32 = TailRecipe( - 'stWithIndexDisp32', StoreComplex, base_size=6, - ins=(GPR, GPR, GPR), - outs=(), - instp=IsSignedInt(StoreComplex.offset, 32), - clobbers_flags=False, - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex3(in_reg1, in_reg0, in_reg2), sink); - modrm_sib_disp32(in_reg0, sink); - sib(0, in_reg2, in_reg1, sink); - let offset: i32 = offset.into(); - sink.put4(offset as u32); - ''') - -# XX /r register-indirect store with 32-bit offset. -# Only ABCD allowed for stored value. This is for byte stores with no REX. -stDisp32_abcd = TailRecipe( - 'stDisp32_abcd', Store, base_size=5, ins=(ABCD, GPR), outs=(), - when_prefixed=stDisp32, - clobbers_flags=False, - compute_size="size_plus_maybe_sib_for_in_reg_1", - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex2(in_reg1, in_reg0), sink); - if needs_sib_byte(in_reg1) { - modrm_sib_disp32(in_reg0, sink); - sib_noindex(in_reg1, sink); - } else { - modrm_disp32(in_reg1, in_reg0, sink); - } - let offset: i32 = offset.into(); - sink.put4(offset as u32); - ''') - -# XX /r register-indirect store with index and 32-bit offset. -# Only ABCD allowed for stored value. This is for byte stores with no REX. -stWithIndexDisp32_abcd = TailRecipe( - 'stWithIndexDisp32_abcd', StoreComplex, base_size=6, - ins=(ABCD, GPR, GPR), - outs=(), - instp=IsSignedInt(StoreComplex.offset, 32), - clobbers_flags=False, - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex3(in_reg1, in_reg0, in_reg2), sink); - modrm_sib_disp32(in_reg0, sink); - sib(0, in_reg2, in_reg1, sink); - let offset: i32 = offset.into(); - sink.put4(offset as u32); - ''') - -# XX /r register-indirect store with 32-bit offset of FPR. -fstDisp32 = TailRecipe( - 'fstDisp32', Store, base_size=5, ins=(FPR, GPR), outs=(), - clobbers_flags=False, - compute_size='size_plus_maybe_sib_for_in_reg_1', - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex2(in_reg1, in_reg0), sink); - if needs_sib_byte(in_reg1) { - modrm_sib_disp32(in_reg0, sink); - sib_noindex(in_reg1, sink); - } else { - modrm_disp32(in_reg1, in_reg0, sink); - } - let offset: i32 = offset.into(); - sink.put4(offset as u32); - ''') - -# XX /r register-indirect store with index and 32-bit offset of FPR. -fstWithIndexDisp32 = TailRecipe( - 'fstWithIndexDisp32', StoreComplex, base_size=6, - ins=(FPR, GPR, GPR), - outs=(), - instp=IsSignedInt(StoreComplex.offset, 32), - clobbers_flags=False, - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex3(in_reg1, in_reg0, in_reg2), sink); - modrm_sib_disp32(in_reg0, sink); - sib(0, in_reg2, in_reg1, sink); - let offset: i32 = offset.into(); - sink.put4(offset as u32); - ''') - -# Unary spill with SIB and 32-bit displacement. -spillSib32 = TailRecipe( - 'spillSib32', Unary, base_size=6, ins=GPR, outs=StackGPR32, - clobbers_flags=False, - emit=''' - sink.trap(TrapCode::StackOverflow, func.srclocs[inst]); - let base = stk_base(out_stk0.base); - PUT_OP(bits, rex2(base, in_reg0), sink); - modrm_sib_disp32(in_reg0, sink); - sib_noindex(base, sink); - sink.put4(out_stk0.offset as u32); - ''') - -# Like spillSib32, but targeting an FPR rather than a GPR. -fspillSib32 = TailRecipe( - 'fspillSib32', Unary, base_size=6, ins=FPR, outs=StackFPR32, - clobbers_flags=False, - emit=''' - sink.trap(TrapCode::StackOverflow, func.srclocs[inst]); - let base = stk_base(out_stk0.base); - PUT_OP(bits, rex2(base, in_reg0), sink); - modrm_sib_disp32(in_reg0, sink); - sib_noindex(base, sink); - sink.put4(out_stk0.offset as u32); - ''') - -# Regspill using RSP-relative addressing. -regspill32 = TailRecipe( - 'regspill32', RegSpill, base_size=6, ins=GPR, outs=(), - clobbers_flags=False, - emit=''' - sink.trap(TrapCode::StackOverflow, func.srclocs[inst]); - let dst = StackRef::sp(dst, &func.stack_slots); - let base = stk_base(dst.base); - PUT_OP(bits, rex2(base, src), sink); - modrm_sib_disp32(src, sink); - sib_noindex(base, sink); - sink.put4(dst.offset as u32); - ''') - -# Like regspill32, but targeting an FPR rather than a GPR. -fregspill32 = TailRecipe( - 'fregspill32', RegSpill, base_size=6, ins=FPR, outs=(), - clobbers_flags=False, - emit=''' - sink.trap(TrapCode::StackOverflow, func.srclocs[inst]); - let dst = StackRef::sp(dst, &func.stack_slots); - let base = stk_base(dst.base); - PUT_OP(bits, rex2(base, src), sink); - modrm_sib_disp32(src, sink); - sib_noindex(base, sink); - sink.put4(dst.offset as u32); - ''') - -# -# Load recipes -# - -# XX /r load with no offset. -ld = TailRecipe( - 'ld', Load, base_size=1, ins=(GPR), outs=(GPR), - instp=IsEqual(Load.offset, 0), - clobbers_flags=False, - compute_size="size_plus_maybe_sib_or_offset_for_in_reg_0", - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex2(in_reg0, out_reg0), sink); - if needs_sib_byte(in_reg0) { - modrm_sib(out_reg0, sink); - sib_noindex(in_reg0, sink); - } else if needs_offset(in_reg0) { - modrm_disp8(in_reg0, out_reg0, sink); - sink.put1(0); - } else { - modrm_rm(in_reg0, out_reg0, sink); - } - ''') - -# XX /r load with index and no offset. -ldWithIndex = TailRecipe( - 'ldWithIndex', LoadComplex, base_size=2, - ins=(GPR, GPR), - outs=(GPR), - instp=IsEqual(LoadComplex.offset, 0), - clobbers_flags=False, - compute_size="size_plus_maybe_offset_for_in_reg_0", - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex3(in_reg0, out_reg0, in_reg1), sink); - // The else branch always inserts an SIB byte. - if needs_offset(in_reg0) { - modrm_sib_disp8(out_reg0, sink); - sib(0, in_reg1, in_reg0, sink); - sink.put1(0); - } else { - modrm_sib(out_reg0, sink); - sib(0, in_reg1, in_reg0, sink); - } - ''') - -# XX /r float load with no offset. -fld = TailRecipe( - 'fld', Load, base_size=1, ins=(GPR), outs=(FPR), - instp=IsEqual(Load.offset, 0), - clobbers_flags=False, - compute_size="size_plus_maybe_sib_or_offset_for_in_reg_0", - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex2(in_reg0, out_reg0), sink); - if needs_sib_byte(in_reg0) { - modrm_sib(out_reg0, sink); - sib_noindex(in_reg0, sink); - } else if needs_offset(in_reg0) { - modrm_disp8(in_reg0, out_reg0, sink); - sink.put1(0); - } else { - modrm_rm(in_reg0, out_reg0, sink); - } - ''') - -# XX /r float load with index and no offset. -fldWithIndex = TailRecipe( - 'fldWithIndex', LoadComplex, base_size=2, - ins=(GPR, GPR), - outs=(FPR), - instp=IsEqual(LoadComplex.offset, 0), - clobbers_flags=False, - compute_size="size_plus_maybe_offset_for_in_reg_0", - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex3(in_reg0, out_reg0, in_reg1), sink); - // The else branch always inserts an SIB byte. - if needs_offset(in_reg0) { - modrm_sib_disp8(out_reg0, sink); - sib(0, in_reg1, in_reg0, sink); - sink.put1(0); - } else { - modrm_sib(out_reg0, sink); - sib(0, in_reg1, in_reg0, sink); - } - ''') - -# XX /r load with 8-bit offset. -ldDisp8 = TailRecipe( - 'ldDisp8', Load, base_size=2, ins=(GPR), outs=(GPR), - instp=IsSignedInt(Load.offset, 8), - clobbers_flags=False, - compute_size="size_plus_maybe_sib_for_in_reg_0", - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex2(in_reg0, out_reg0), sink); - if needs_sib_byte(in_reg0) { - modrm_sib_disp8(out_reg0, sink); - sib_noindex(in_reg0, sink); - } else { - modrm_disp8(in_reg0, out_reg0, sink); - } - let offset: i32 = offset.into(); - sink.put1(offset as u8); - ''') - -# XX /r load with index and 8-bit offset. -ldWithIndexDisp8 = TailRecipe( - 'ldWithIndexDisp8', LoadComplex, base_size=3, - ins=(GPR, GPR), - outs=(GPR), - instp=IsSignedInt(LoadComplex.offset, 8), - clobbers_flags=False, - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex3(in_reg0, out_reg0, in_reg1), sink); - modrm_sib_disp8(out_reg0, sink); - sib(0, in_reg1, in_reg0, sink); - let offset: i32 = offset.into(); - sink.put1(offset as u8); - ''') - -# XX /r float load with 8-bit offset. -fldDisp8 = TailRecipe( - 'fldDisp8', Load, base_size=2, ins=(GPR), outs=(FPR), - instp=IsSignedInt(Load.offset, 8), - clobbers_flags=False, - compute_size="size_plus_maybe_sib_for_in_reg_0", - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex2(in_reg0, out_reg0), sink); - if needs_sib_byte(in_reg0) { - modrm_sib_disp8(out_reg0, sink); - sib_noindex(in_reg0, sink); - } else { - modrm_disp8(in_reg0, out_reg0, sink); - } - let offset: i32 = offset.into(); - sink.put1(offset as u8); - ''') - -# XX /r float load with 8-bit offset. -fldWithIndexDisp8 = TailRecipe( - 'fldWithIndexDisp8', LoadComplex, base_size=3, - ins=(GPR, GPR), - outs=(FPR), - instp=IsSignedInt(LoadComplex.offset, 8), - clobbers_flags=False, - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex3(in_reg0, out_reg0, in_reg1), sink); - modrm_sib_disp8(out_reg0, sink); - sib(0, in_reg1, in_reg0, sink); - let offset: i32 = offset.into(); - sink.put1(offset as u8); - ''') - -# XX /r load with 32-bit offset. -ldDisp32 = TailRecipe( - 'ldDisp32', Load, base_size=5, ins=(GPR), outs=(GPR), - instp=IsSignedInt(Load.offset, 32), - clobbers_flags=False, - compute_size='size_plus_maybe_sib_for_in_reg_0', - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex2(in_reg0, out_reg0), sink); - if needs_sib_byte(in_reg0) { - modrm_sib_disp32(out_reg0, sink); - sib_noindex(in_reg0, sink); - } else { - modrm_disp32(in_reg0, out_reg0, sink); - } - let offset: i32 = offset.into(); - sink.put4(offset as u32); - ''') - -# XX /r load with index and 32-bit offset. -ldWithIndexDisp32 = TailRecipe( - 'ldWithIndexDisp32', LoadComplex, base_size=6, - ins=(GPR, GPR), - outs=(GPR), - instp=IsSignedInt(LoadComplex.offset, 32), - clobbers_flags=False, - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex3(in_reg0, out_reg0, in_reg1), sink); - modrm_sib_disp32(out_reg0, sink); - sib(0, in_reg1, in_reg0, sink); - let offset: i32 = offset.into(); - sink.put4(offset as u32); - ''') - -# XX /r float load with 32-bit offset. -fldDisp32 = TailRecipe( - 'fldDisp32', Load, base_size=5, ins=(GPR), outs=(FPR), - instp=IsSignedInt(Load.offset, 32), - clobbers_flags=False, - compute_size="size_plus_maybe_sib_for_in_reg_0", - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex2(in_reg0, out_reg0), sink); - if needs_sib_byte(in_reg0) { - modrm_sib_disp32(out_reg0, sink); - sib_noindex(in_reg0, sink); - } else { - modrm_disp32(in_reg0, out_reg0, sink); - } - let offset: i32 = offset.into(); - sink.put4(offset as u32); - ''') - -# XX /r float load with index and 32-bit offset. -fldWithIndexDisp32 = TailRecipe( - 'fldWithIndexDisp32', LoadComplex, base_size=6, - ins=(GPR, GPR), - outs=(FPR), - instp=IsSignedInt(LoadComplex.offset, 32), - clobbers_flags=False, - emit=''' - if !flags.notrap() { - sink.trap(TrapCode::HeapOutOfBounds, func.srclocs[inst]); - } - PUT_OP(bits, rex3(in_reg0, out_reg0, in_reg1), sink); - modrm_sib_disp32(out_reg0, sink); - sib(0, in_reg1, in_reg0, sink); - let offset: i32 = offset.into(); - sink.put4(offset as u32); - ''') - -# Unary fill with SIB and 32-bit displacement. -fillSib32 = TailRecipe( - 'fillSib32', Unary, base_size=6, ins=StackGPR32, outs=GPR, - clobbers_flags=False, - emit=''' - let base = stk_base(in_stk0.base); - PUT_OP(bits, rex2(base, out_reg0), sink); - modrm_sib_disp32(out_reg0, sink); - sib_noindex(base, sink); - sink.put4(in_stk0.offset as u32); - ''') - -# Like fillSib32, but targeting an FPR rather than a GPR. -ffillSib32 = TailRecipe( - 'ffillSib32', Unary, base_size=6, ins=StackFPR32, outs=FPR, - clobbers_flags=False, - emit=''' - let base = stk_base(in_stk0.base); - PUT_OP(bits, rex2(base, out_reg0), sink); - modrm_sib_disp32(out_reg0, sink); - sib_noindex(base, sink); - sink.put4(in_stk0.offset as u32); - ''') - -# Regfill with RSP-relative 32-bit displacement. -regfill32 = TailRecipe( - 'regfill32', RegFill, base_size=6, ins=StackGPR32, outs=(), - clobbers_flags=False, - emit=''' - let src = StackRef::sp(src, &func.stack_slots); - let base = stk_base(src.base); - PUT_OP(bits, rex2(base, dst), sink); - modrm_sib_disp32(dst, sink); - sib_noindex(base, sink); - sink.put4(src.offset as u32); - ''') - -# Like regfill32, but targeting an FPR rather than a GPR. -fregfill32 = TailRecipe( - 'fregfill32', RegFill, base_size=6, ins=StackFPR32, outs=(), - clobbers_flags=False, - emit=''' - let src = StackRef::sp(src, &func.stack_slots); - let base = stk_base(src.base); - PUT_OP(bits, rex2(base, dst), sink); - modrm_sib_disp32(dst, sink); - sib_noindex(base, sink); - sink.put4(src.offset as u32); - ''') - -# -# Call/return -# -call_id = TailRecipe( - 'call_id', Call, base_size=4, ins=(), outs=(), - emit=''' - sink.trap(TrapCode::StackOverflow, func.srclocs[inst]); - PUT_OP(bits, BASE_REX, sink); - // The addend adjusts for the difference between the end of the - // instruction and the beginning of the immediate field. - sink.reloc_external(Reloc::X86CallPCRel4, - &func.dfg.ext_funcs[func_ref].name, - -4); - sink.put4(0); - ''') - -call_plt_id = TailRecipe( - 'call_plt_id', Call, base_size=4, ins=(), outs=(), - emit=''' - sink.trap(TrapCode::StackOverflow, func.srclocs[inst]); - PUT_OP(bits, BASE_REX, sink); - sink.reloc_external(Reloc::X86CallPLTRel4, - &func.dfg.ext_funcs[func_ref].name, - -4); - sink.put4(0); - ''') - -call_r = TailRecipe( - 'call_r', CallIndirect, base_size=1, ins=GPR, outs=(), - emit=''' - sink.trap(TrapCode::StackOverflow, func.srclocs[inst]); - PUT_OP(bits, rex1(in_reg0), sink); - modrm_r_bits(in_reg0, bits, sink); - ''') - -ret = TailRecipe( - 'ret', MultiAry, base_size=0, ins=(), outs=(), - emit=''' - PUT_OP(bits, BASE_REX, sink); - ''') - -# -# Branches -# -jmpb = TailRecipe( - 'jmpb', Jump, base_size=1, ins=(), outs=(), - branch_range=8, - clobbers_flags=False, - emit=''' - PUT_OP(bits, BASE_REX, sink); - disp1(destination, func, sink); - ''') - -jmpd = TailRecipe( - 'jmpd', Jump, base_size=4, ins=(), outs=(), - branch_range=32, - clobbers_flags=False, - emit=''' - PUT_OP(bits, BASE_REX, sink); - disp4(destination, func, sink); - ''') - -brib = TailRecipe( - 'brib', BranchInt, base_size=1, ins=FLAG.rflags, outs=(), - branch_range=8, - clobbers_flags=False, - emit=''' - PUT_OP(bits | icc2opc(cond), BASE_REX, sink); - disp1(destination, func, sink); - ''') - -brid = TailRecipe( - 'brid', BranchInt, base_size=4, ins=FLAG.rflags, outs=(), - branch_range=32, - clobbers_flags=False, - emit=''' - PUT_OP(bits | icc2opc(cond), BASE_REX, sink); - disp4(destination, func, sink); - ''') - -brfb = TailRecipe( - 'brfb', BranchFloat, base_size=1, ins=FLAG.rflags, outs=(), - branch_range=8, - clobbers_flags=False, - instp=floatccs(BranchFloat), - emit=''' - PUT_OP(bits | fcc2opc(cond), BASE_REX, sink); - disp1(destination, func, sink); - ''') - -brfd = TailRecipe( - 'brfd', BranchFloat, base_size=4, ins=FLAG.rflags, outs=(), - branch_range=32, - clobbers_flags=False, - instp=floatccs(BranchFloat), - emit=''' - PUT_OP(bits | fcc2opc(cond), BASE_REX, sink); - disp4(destination, func, sink); - ''') - -indirect_jmp = TailRecipe( - 'indirect_jmp', IndirectJump, base_size=1, ins=GPR, outs=(), - clobbers_flags=False, - emit=''' - PUT_OP(bits, rex1(in_reg0), sink); - modrm_r_bits(in_reg0, bits, sink); - ''') - -jt_entry = TailRecipe( - 'jt_entry', BranchTableEntry, base_size=2, - ins=(GPR, GPR), - outs=(GPR), - clobbers_flags=False, - instp=valid_scale(BranchTableEntry), - compute_size="size_plus_maybe_offset_for_in_reg_1", - emit=''' - PUT_OP(bits, rex3(in_reg1, out_reg0, in_reg0), sink); - if needs_offset(in_reg1) { - modrm_sib_disp8(out_reg0, sink); - sib(imm.trailing_zeros() as u8, in_reg0, in_reg1, sink); - sink.put1(0); - } else { - modrm_sib(out_reg0, sink); - sib(imm.trailing_zeros() as u8, in_reg0, in_reg1, sink); - } - ''') - -jt_base = TailRecipe( - 'jt_base', BranchTableBase, base_size=5, ins=(), outs=(GPR), - clobbers_flags=False, - emit=''' - PUT_OP(bits, rex2(0, out_reg0), sink); - modrm_riprel(out_reg0, sink); - - // No reloc is needed here as the jump table is emitted directly after - // the function body. - jt_disp4(table, func, sink); - ''') - -# -# Test flags and set a register. -# -# These setCC instructions only set the low 8 bits, and they can only write -# ABCD registers without a REX prefix. -# -# Other instruction encodings accepting `b1` inputs have the same constraints -# and only look at the low 8 bits of the input register. -# - -seti = TailRecipe( - 'seti', IntCond, base_size=1, ins=FLAG.rflags, outs=GPR, - requires_prefix=True, - clobbers_flags=False, - emit=''' - PUT_OP(bits | icc2opc(cond), rex1(out_reg0), sink); - modrm_r_bits(out_reg0, bits, sink); - ''') -seti_abcd = TailRecipe( - 'seti_abcd', IntCond, base_size=1, ins=FLAG.rflags, outs=ABCD, - when_prefixed=seti, - clobbers_flags=False, - emit=''' - PUT_OP(bits | icc2opc(cond), rex1(out_reg0), sink); - modrm_r_bits(out_reg0, bits, sink); - ''') - -setf = TailRecipe( - 'setf', FloatCond, base_size=1, ins=FLAG.rflags, outs=GPR, - requires_prefix=True, - clobbers_flags=False, - emit=''' - PUT_OP(bits | fcc2opc(cond), rex1(out_reg0), sink); - modrm_r_bits(out_reg0, bits, sink); - ''') -setf_abcd = TailRecipe( - 'setf_abcd', FloatCond, base_size=1, ins=FLAG.rflags, outs=ABCD, - when_prefixed=setf, - clobbers_flags=False, - emit=''' - PUT_OP(bits | fcc2opc(cond), rex1(out_reg0), sink); - modrm_r_bits(out_reg0, bits, sink); - ''') - -# -# Conditional move (a.k.a integer select) -# (maybe-REX.W) 0F 4x modrm(r,r) -# 1 byte, modrm(r,r), is after the opcode -# -cmov = TailRecipe( - 'cmov', IntSelect, base_size=1, ins=(FLAG.rflags, GPR, GPR), outs=2, - requires_prefix=False, - clobbers_flags=False, - emit=''' - PUT_OP(bits | icc2opc(cond), rex2(in_reg1, in_reg2), sink); - modrm_rr(in_reg1, in_reg2, sink); - ''') - -# -# Bit scan forwards and reverse -# -bsf_and_bsr = TailRecipe( - 'bsf_and_bsr', Unary, base_size=1, ins=GPR, outs=(GPR, FLAG.rflags), - requires_prefix=False, - clobbers_flags=True, - emit=''' - PUT_OP(bits, rex2(in_reg0, out_reg0), sink); - modrm_rr(in_reg0, out_reg0, sink); - ''') - -# -# Compare and set flags. -# - -# XX /r, MR form. Compare two GPR registers and set flags. -rcmp = TailRecipe( - 'rcmp', Binary, base_size=1, ins=(GPR, GPR), outs=FLAG.rflags, - emit=''' - PUT_OP(bits, rex2(in_reg0, in_reg1), sink); - modrm_rr(in_reg0, in_reg1, sink); - ''') - -# XX /r, RM form. Compare two FPR registers and set flags. -fcmp = TailRecipe( - 'fcmp', Binary, base_size=1, ins=(FPR, FPR), outs=FLAG.rflags, - emit=''' - PUT_OP(bits, rex2(in_reg1, in_reg0), sink); - modrm_rr(in_reg1, in_reg0, sink); - ''') - -# XX /n, MI form with imm8. -rcmp_ib = TailRecipe( - 'rcmp_ib', BinaryImm, base_size=2, ins=GPR, outs=FLAG.rflags, - instp=IsSignedInt(BinaryImm.imm, 8), - emit=''' - PUT_OP(bits, rex1(in_reg0), sink); - modrm_r_bits(in_reg0, bits, sink); - let imm: i64 = imm.into(); - sink.put1(imm as u8); - ''') - -# XX /n, MI form with imm32. -rcmp_id = TailRecipe( - 'rcmp_id', BinaryImm, base_size=5, ins=GPR, outs=FLAG.rflags, - instp=IsSignedInt(BinaryImm.imm, 32), - emit=''' - PUT_OP(bits, rex1(in_reg0), sink); - modrm_r_bits(in_reg0, bits, sink); - let imm: i64 = imm.into(); - sink.put4(imm as u32); - ''') - -# Same as rcmp, but second operand is the stack pointer. -rcmp_sp = TailRecipe( - 'rcmp_sp', Unary, base_size=1, ins=GPR, outs=FLAG.rflags, - emit=''' - PUT_OP(bits, rex2(in_reg0, RU::rsp.into()), sink); - modrm_rr(in_reg0, RU::rsp.into(), sink); - ''') - -# Test-and-branch. -# -# This recipe represents the macro fusion of a test and a conditional branch. -# This serves two purposes: -# -# 1. Guarantee that the test and branch get scheduled next to each other so -# macro fusion is guaranteed to be possible. -# 2. Hide the status flags from Cranelift which doesn't currently model flags. -# -# The encoding bits affect both the test and the branch instruction: -# -# Bits 0-7 are the Jcc opcode. -# Bits 8-15 control the test instruction which always has opcode byte 0x85. -tjccb = TailRecipe( - 'tjccb', Branch, base_size=1 + 2, ins=GPR, outs=(), - branch_range=8, - emit=''' - // test r, r. - PUT_OP((bits & 0xff00) | 0x85, rex2(in_reg0, in_reg0), sink); - modrm_rr(in_reg0, in_reg0, sink); - // Jcc instruction. - sink.put1(bits as u8); - disp1(destination, func, sink); - ''') - -tjccd = TailRecipe( - 'tjccd', Branch, base_size=1 + 6, ins=GPR, outs=(), - branch_range=32, - emit=''' - // test r, r. - PUT_OP((bits & 0xff00) | 0x85, rex2(in_reg0, in_reg0), sink); - modrm_rr(in_reg0, in_reg0, sink); - // Jcc instruction. - sink.put1(0x0f); - sink.put1(bits as u8); - disp4(destination, func, sink); - ''') - -# 8-bit test-and-branch. -# -# Same as tjccb, but only looks at the low 8 bits of the register, for b1 -# types. -t8jccb = TailRecipe( - 't8jccb', Branch, base_size=1 + 2, ins=GPR, outs=(), - branch_range=8, - requires_prefix=True, - emit=''' - // test8 r, r. - PUT_OP((bits & 0xff00) | 0x84, rex2(in_reg0, in_reg0), sink); - modrm_rr(in_reg0, in_reg0, sink); - // Jcc instruction. - sink.put1(bits as u8); - disp1(destination, func, sink); - ''') -t8jccb_abcd = TailRecipe( - 't8jccb_abcd', Branch, base_size=1 + 2, ins=ABCD, outs=(), - branch_range=8, - when_prefixed=t8jccb, - emit=''' - // test8 r, r. - PUT_OP((bits & 0xff00) | 0x84, rex2(in_reg0, in_reg0), sink); - modrm_rr(in_reg0, in_reg0, sink); - // Jcc instruction. - sink.put1(bits as u8); - disp1(destination, func, sink); - ''') - -t8jccd = TailRecipe( - 't8jccd', Branch, base_size=1 + 6, ins=GPR, outs=(), - branch_range=32, - requires_prefix=True, - emit=''' - // test8 r, r. - PUT_OP((bits & 0xff00) | 0x84, rex2(in_reg0, in_reg0), sink); - modrm_rr(in_reg0, in_reg0, sink); - // Jcc instruction. - sink.put1(0x0f); - sink.put1(bits as u8); - disp4(destination, func, sink); - ''') -t8jccd_abcd = TailRecipe( - 't8jccd_abcd', Branch, base_size=1 + 6, ins=ABCD, outs=(), - branch_range=32, - when_prefixed=t8jccd, - emit=''' - // test8 r, r. - PUT_OP((bits & 0xff00) | 0x84, rex2(in_reg0, in_reg0), sink); - modrm_rr(in_reg0, in_reg0, sink); - // Jcc instruction. - sink.put1(0x0f); - sink.put1(bits as u8); - disp4(destination, func, sink); - ''') - -# Worst case test-and-branch recipe for brz.b1 and brnz.b1 in 32-bit mode. -# The register allocator can't handle a branch instruction with constrained -# operands like the t8jccd_abcd above. This variant can accept the b1 opernd in -# any register, but is is larger because it uses a 32-bit test instruction with -# a 0xff immediate. -t8jccd_long = TailRecipe( - 't8jccd_long', Branch, base_size=5 + 6, ins=GPR, outs=(), - branch_range=32, - emit=''' - // test32 r, 0xff. - PUT_OP((bits & 0xff00) | 0xf7, rex1(in_reg0), sink); - modrm_r_bits(in_reg0, bits, sink); - sink.put4(0xff); - // Jcc instruction. - sink.put1(0x0f); - sink.put1(bits as u8); - disp4(destination, func, sink); - ''') - -# Comparison that produces a `b1` result in a GPR. -# -# This is a macro of a `cmp` instruction followed by a `setCC` instruction. -# This is not a great solution because: -# -# - The cmp+setcc combination is not recognized by CPU's macro fusion. -# - The 64-bit encoding has issues with REX prefixes. The `cmp` and `setCC` -# instructions may need a REX independently. -# - Modeling CPU flags in the type system would be better. -# -# Since the `setCC` instructions only write an 8-bit register, we use that as -# our `b1` representation: A `b1` value is represented as a GPR where the low 8 -# bits are known to be 0 or 1. The high bits are undefined. -# -# This bandaid macro doesn't support a REX prefix for the final `setCC` -# instruction, so it is limited to the `ABCD` register class for booleans. -# The omission of a `when_prefixed` alternative is deliberate here. -icscc = TailRecipe( - 'icscc', IntCompare, base_size=1 + 3, ins=(GPR, GPR), outs=ABCD, - emit=''' - // Comparison instruction. - PUT_OP(bits, rex2(in_reg0, in_reg1), sink); - modrm_rr(in_reg0, in_reg1, sink); - // `setCC` instruction, no REX. - use crate::ir::condcodes::IntCC::*; - let setcc = match cond { - Equal => 0x94, - NotEqual => 0x95, - SignedLessThan => 0x9c, - SignedGreaterThanOrEqual => 0x9d, - SignedGreaterThan => 0x9f, - SignedLessThanOrEqual => 0x9e, - UnsignedLessThan => 0x92, - UnsignedGreaterThanOrEqual => 0x93, - UnsignedGreaterThan => 0x97, - UnsignedLessThanOrEqual => 0x96, - }; - sink.put1(0x0f); - sink.put1(setcc); - modrm_rr(out_reg0, 0, sink); - ''') - -icscc_ib = TailRecipe( - 'icscc_ib', IntCompareImm, base_size=2 + 3, ins=GPR, outs=ABCD, - instp=IsSignedInt(IntCompareImm.imm, 8), - emit=''' - // Comparison instruction. - PUT_OP(bits, rex1(in_reg0), sink); - modrm_r_bits(in_reg0, bits, sink); - let imm: i64 = imm.into(); - sink.put1(imm as u8); - // `setCC` instruction, no REX. - use crate::ir::condcodes::IntCC::*; - let setcc = match cond { - Equal => 0x94, - NotEqual => 0x95, - SignedLessThan => 0x9c, - SignedGreaterThanOrEqual => 0x9d, - SignedGreaterThan => 0x9f, - SignedLessThanOrEqual => 0x9e, - UnsignedLessThan => 0x92, - UnsignedGreaterThanOrEqual => 0x93, - UnsignedGreaterThan => 0x97, - UnsignedLessThanOrEqual => 0x96, - }; - sink.put1(0x0f); - sink.put1(setcc); - modrm_rr(out_reg0, 0, sink); - ''') - -icscc_id = TailRecipe( - 'icscc_id', IntCompareImm, base_size=5 + 3, ins=GPR, outs=ABCD, - instp=IsSignedInt(IntCompareImm.imm, 32), - emit=''' - // Comparison instruction. - PUT_OP(bits, rex1(in_reg0), sink); - modrm_r_bits(in_reg0, bits, sink); - let imm: i64 = imm.into(); - sink.put4(imm as u32); - // `setCC` instruction, no REX. - use crate::ir::condcodes::IntCC::*; - let setcc = match cond { - Equal => 0x94, - NotEqual => 0x95, - SignedLessThan => 0x9c, - SignedGreaterThanOrEqual => 0x9d, - SignedGreaterThan => 0x9f, - SignedLessThanOrEqual => 0x9e, - UnsignedLessThan => 0x92, - UnsignedGreaterThanOrEqual => 0x93, - UnsignedGreaterThan => 0x97, - UnsignedLessThanOrEqual => 0x96, - }; - sink.put1(0x0f); - sink.put1(setcc); - modrm_rr(out_reg0, 0, sink); - ''') - -# Make a FloatCompare instruction predicate with the supported condition codes. - -# Same thing for floating point. -# -# The ucomiss/ucomisd instructions set the FLAGS bits CF/PF/CF like this: -# -# ZPC OSA -# UN 111 000 -# GT 000 000 -# LT 001 000 -# EQ 100 000 -# -# Not all floating point condition codes are supported. -# The omission of a `when_prefixed` alternative is deliberate here. -fcscc = TailRecipe( - 'fcscc', FloatCompare, base_size=1 + 3, ins=(FPR, FPR), outs=ABCD, - instp=floatccs(FloatCompare), - emit=''' - // Comparison instruction. - PUT_OP(bits, rex2(in_reg1, in_reg0), sink); - modrm_rr(in_reg1, in_reg0, sink); - // `setCC` instruction, no REX. - use crate::ir::condcodes::FloatCC::*; - let setcc = match cond { - Ordered => 0x9b, // EQ|LT|GT => setnp (P=0) - Unordered => 0x9a, // UN => setp (P=1) - OrderedNotEqual => 0x95, // LT|GT => setne (Z=0), - UnorderedOrEqual => 0x94, // UN|EQ => sete (Z=1) - GreaterThan => 0x97, // GT => seta (C=0&Z=0) - GreaterThanOrEqual => 0x93, // GT|EQ => setae (C=0) - UnorderedOrLessThan => 0x92, // UN|LT => setb (C=1) - UnorderedOrLessThanOrEqual => 0x96, // UN|LT|EQ => setbe (Z=1|C=1) - Equal | // EQ - NotEqual | // UN|LT|GT - LessThan | // LT - LessThanOrEqual | // LT|EQ - UnorderedOrGreaterThan | // UN|GT - UnorderedOrGreaterThanOrEqual // UN|GT|EQ - => panic!("{} not supported by fcscc", cond), - }; - sink.put1(0x0f); - sink.put1(setcc); - modrm_rr(out_reg0, 0, sink); - ''') - -TailRecipe.check_names(globals()) diff --git a/cranelift/codegen/meta-python/isa/x86/registers.py b/cranelift/codegen/meta-python/isa/x86/registers.py deleted file mode 100644 index 3cca0bc377..0000000000 --- a/cranelift/codegen/meta-python/isa/x86/registers.py +++ /dev/null @@ -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()) diff --git a/cranelift/codegen/meta-python/isa/x86/settings.py b/cranelift/codegen/meta-python/isa/x86/settings.py deleted file mode 100644 index 74633349e2..0000000000 --- a/cranelift/codegen/meta-python/isa/x86/settings.py +++ /dev/null @@ -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()) diff --git a/cranelift/codegen/meta-python/mypy.ini b/cranelift/codegen/meta-python/mypy.ini deleted file mode 100644 index 877e4c9ff5..0000000000 --- a/cranelift/codegen/meta-python/mypy.ini +++ /dev/null @@ -1,5 +0,0 @@ -[mypy] -disallow_untyped_defs = True -warn_unused_ignores = True -warn_return_any = True -strict_optional = False diff --git a/cranelift/codegen/meta-python/semantics/__init__.py b/cranelift/codegen/meta-python/semantics/__init__.py deleted file mode 100644 index 1ce6b46712..0000000000 --- a/cranelift/codegen/meta-python/semantics/__init__.py +++ /dev/null @@ -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) diff --git a/cranelift/codegen/meta-python/semantics/elaborate.py b/cranelift/codegen/meta-python/semantics/elaborate.py deleted file mode 100644 index 1525b1deef..0000000000 --- a/cranelift/codegen/meta-python/semantics/elaborate.py +++ /dev/null @@ -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) diff --git a/cranelift/codegen/meta-python/semantics/macros.py b/cranelift/codegen/meta-python/semantics/macros.py deleted file mode 100644 index 566bf92eae..0000000000 --- a/cranelift/codegen/meta-python/semantics/macros.py +++ /dev/null @@ -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() diff --git a/cranelift/codegen/meta-python/semantics/primitives.py b/cranelift/codegen/meta-python/semantics/primitives.py deleted file mode 100644 index a3e498f4e6..0000000000 --- a/cranelift/codegen/meta-python/semantics/primitives.py +++ /dev/null @@ -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() diff --git a/cranelift/codegen/meta-python/semantics/smtlib.py b/cranelift/codegen/meta-python/semantics/smtlib.py deleted file mode 100644 index 9ae9fbfaa7..0000000000 --- a/cranelift/codegen/meta-python/semantics/smtlib.py +++ /dev/null @@ -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 diff --git a/cranelift/codegen/meta-python/semantics/test_elaborate.py b/cranelift/codegen/meta-python/semantics/test_elaborate.py deleted file mode 100644 index 9ca938bc1f..0000000000 --- a/cranelift/codegen/meta-python/semantics/test_elaborate.py +++ /dev/null @@ -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) diff --git a/cranelift/codegen/meta-python/srcgen.py b/cranelift/codegen/meta-python/srcgen.py deleted file mode 100644 index df5794cedb..0000000000 --- a/cranelift/codegen/meta-python/srcgen.py +++ /dev/null @@ -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 diff --git a/cranelift/codegen/meta-python/stubs/z3/__init__.pyi b/cranelift/codegen/meta-python/stubs/z3/__init__.pyi deleted file mode 100644 index 2fd6c8341f..0000000000 --- a/cranelift/codegen/meta-python/stubs/z3/__init__.pyi +++ /dev/null @@ -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: ... diff --git a/cranelift/codegen/meta-python/stubs/z3/z3core.pyi b/cranelift/codegen/meta-python/stubs/z3/z3core.pyi deleted file mode 100644 index 36f1f88792..0000000000 --- a/cranelift/codegen/meta-python/stubs/z3/z3core.pyi +++ /dev/null @@ -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: ... diff --git a/cranelift/codegen/meta-python/stubs/z3/z3types.pyi b/cranelift/codegen/meta-python/stubs/z3/z3types.pyi deleted file mode 100644 index fa8fc446d1..0000000000 --- a/cranelift/codegen/meta-python/stubs/z3/z3types.pyi +++ /dev/null @@ -1,12 +0,0 @@ -from typing import Any - -class Z3Exception(Exception): - def __init__(self, a: Any) -> None: - self.value = a - ... - -class ContextObj: - ... - -class Ast: - ... diff --git a/cranelift/codegen/meta-python/test_constant_hash.py b/cranelift/codegen/meta-python/test_constant_hash.py deleted file mode 100644 index e76f09aed9..0000000000 --- a/cranelift/codegen/meta-python/test_constant_hash.py +++ /dev/null @@ -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 diff --git a/cranelift/codegen/meta-python/test_srcgen.py b/cranelift/codegen/meta-python/test_srcgen.py deleted file mode 100644 index 2fb5e0fb61..0000000000 --- a/cranelift/codegen/meta-python/test_srcgen.py +++ /dev/null @@ -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 diff --git a/cranelift/codegen/meta-python/unique_table.py b/cranelift/codegen/meta-python/unique_table.py deleted file mode 100644 index d8482b3c7d..0000000000 --- a/cranelift/codegen/meta-python/unique_table.py +++ /dev/null @@ -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 diff --git a/cranelift/test-all.sh b/cranelift/test-all.sh index 0ee57a0c82..64f4343bf5 100755 --- a/cranelift/test-all.sh +++ b/cranelift/test-all.sh @@ -4,7 +4,6 @@ set -euo pipefail # This is the top-level test script: # # - Check code formatting. -# - Perform checks on Python code. # - Make a debug build. # - Make a release build. # - 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. -# 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. topdir=$(dirname "$0") cd "$topdir" @@ -40,20 +35,6 @@ else echo "https://github.com/rust-lang-nursery/rustfmt for more information." 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. banner "Rust release build" cargo build --release