Check in the wasmstandalone code.
This is based on the code in https://github.com/denismerigoux/cretonne/commits/wasm2cretonne before wasmstandalone was removed, with minor updates for the new library structure. It is not yet updated for the latest cretonne API changes.
This commit is contained in:
@@ -1,19 +0,0 @@
|
||||
[package]
|
||||
authors = ["The Cretonne Project Developers"]
|
||||
name = "cretonne"
|
||||
version = "0.0.0"
|
||||
description = "Low-level code generator library"
|
||||
license = "Apache-2.0"
|
||||
documentation = "https://cretonne.readthedocs.io/"
|
||||
repository = "https://github.com/stoklund/cretonne"
|
||||
publish = false
|
||||
build = "build.rs"
|
||||
|
||||
[lib]
|
||||
name = "cretonne"
|
||||
|
||||
[dependencies]
|
||||
# It is a goal of the cretonne crate to have minimal external dependencies.
|
||||
# Please don't add any unless they are essential to the task of creating binary
|
||||
# machine code. Integration tests that need external dependencies can be
|
||||
# accomodated in `tests`.
|
||||
@@ -1,151 +0,0 @@
|
||||
// Build script.
|
||||
//
|
||||
// This program is run by Cargo when building lib/cretonne. It is used to generate Rust code from
|
||||
// the language definitions in the lib/cretonne/meta directory.
|
||||
//
|
||||
// Environment:
|
||||
//
|
||||
// OUT_DIR
|
||||
// Directory where generated files should be placed.
|
||||
//
|
||||
// TARGET
|
||||
// Target triple provided by Cargo.
|
||||
//
|
||||
// CRETONNE_TARGETS (Optional)
|
||||
// A setting for conditional compilation of isa targets. Possible values can be "native" or
|
||||
// known isa targets separated by ','.
|
||||
//
|
||||
// The build script expects to be run from the directory where this build.rs file lives. The
|
||||
// current directory is used to find the sources.
|
||||
|
||||
|
||||
use std::env;
|
||||
use std::process;
|
||||
|
||||
fn main() {
|
||||
let out_dir = env::var("OUT_DIR").expect("The OUT_DIR environment variable must be set");
|
||||
let target_triple = env::var("TARGET").expect("The TARGET environment variable must be set");
|
||||
let cretonne_targets = env::var("CRETONNE_TARGETS").ok();
|
||||
let cretonne_targets = cretonne_targets.as_ref().map(|s| s.as_ref());
|
||||
|
||||
// Configure isa targets cfg.
|
||||
match isa_targets(cretonne_targets, &target_triple) {
|
||||
Ok(isa_targets) => {
|
||||
for isa in &isa_targets {
|
||||
println!("cargo:rustc-cfg=build_{}", isa.name());
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Error: {}", err);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
println!("Build script generating files in {}", out_dir);
|
||||
|
||||
let cur_dir = env::current_dir().expect("Can't access current working directory");
|
||||
let crate_dir = cur_dir.as_path();
|
||||
|
||||
// Make sure we rebuild is this build script changes.
|
||||
// I guess that won't happen if you have non-UTF8 bytes in your path names.
|
||||
// The `build.py` script prints out its own dependencies.
|
||||
println!("cargo:rerun-if-changed={}",
|
||||
crate_dir.join("build.rs").to_string_lossy());
|
||||
|
||||
// Scripts are in `$crate_dir/meta`.
|
||||
let meta_dir = crate_dir.join("meta");
|
||||
let build_script = meta_dir.join("build.py");
|
||||
|
||||
// Launch build script with Python. We'll just find python in the path.
|
||||
let status = process::Command::new("python")
|
||||
.current_dir(crate_dir)
|
||||
.arg(build_script)
|
||||
.arg("--out-dir")
|
||||
.arg(out_dir)
|
||||
.status()
|
||||
.expect("Failed to launch second-level build script");
|
||||
if !status.success() {
|
||||
process::exit(status.code().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents known ISA target.
|
||||
#[derive(Copy, Clone)]
|
||||
enum Isa {
|
||||
Riscv,
|
||||
Intel,
|
||||
Arm32,
|
||||
Arm64,
|
||||
}
|
||||
|
||||
impl Isa {
|
||||
/// Creates isa target using name.
|
||||
fn new(name: &str) -> Option<Self> {
|
||||
Isa::all()
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter(|isa| isa.name() == name)
|
||||
.next()
|
||||
}
|
||||
|
||||
/// Creates isa target from arch.
|
||||
fn from_arch(arch: &str) -> Option<Isa> {
|
||||
Isa::all()
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter(|isa| isa.is_arch_applicable(arch))
|
||||
.next()
|
||||
}
|
||||
|
||||
/// Returns all supported isa targets.
|
||||
fn all() -> [Isa; 4] {
|
||||
[Isa::Riscv, Isa::Intel, Isa::Arm32, Isa::Arm64]
|
||||
}
|
||||
|
||||
/// Returns name of the isa target.
|
||||
fn name(&self) -> &'static str {
|
||||
match *self {
|
||||
Isa::Riscv => "riscv",
|
||||
Isa::Intel => "intel",
|
||||
Isa::Arm32 => "arm32",
|
||||
Isa::Arm64 => "arm64",
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if arch is applicable for the isa target.
|
||||
fn is_arch_applicable(&self, arch: &str) -> bool {
|
||||
match *self {
|
||||
Isa::Riscv => arch == "riscv",
|
||||
Isa::Intel => ["x86_64", "i386", "i586", "i686"].contains(&arch),
|
||||
Isa::Arm32 => arch.starts_with("arm") || arch.starts_with("thumb"),
|
||||
Isa::Arm64 => arch == "aarch64",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns isa targets to configure conditional compilation.
|
||||
fn isa_targets(cretonne_targets: Option<&str>, target_triple: &str) -> Result<Vec<Isa>, String> {
|
||||
match cretonne_targets {
|
||||
Some("native") => {
|
||||
Isa::from_arch(target_triple.split('-').next().unwrap())
|
||||
.map(|isa| vec![isa])
|
||||
.ok_or_else(|| {
|
||||
format!("no supported isa found for target triple `{}`",
|
||||
target_triple)
|
||||
})
|
||||
}
|
||||
Some(targets) => {
|
||||
let unknown_isa_targets = targets
|
||||
.split(',')
|
||||
.filter(|target| Isa::new(target).is_none())
|
||||
.collect::<Vec<_>>();
|
||||
let isa_targets = targets.split(',').flat_map(Isa::new).collect::<Vec<_>>();
|
||||
match (unknown_isa_targets.is_empty(), isa_targets.is_empty()) {
|
||||
(true, true) => Ok(Isa::all().to_vec()),
|
||||
(true, _) => Ok(isa_targets),
|
||||
(_, _) => Err(format!("unknown isa targets: `{}`", unknown_isa_targets.join(", "))),
|
||||
}
|
||||
}
|
||||
None => Ok(Isa::all().to_vec()),
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
"""Definitions for the base Cretonne language."""
|
||||
@@ -1,29 +0,0 @@
|
||||
"""
|
||||
The `cretonne.entities` module predefines all the Cretonne entity reference
|
||||
operand types. There are corresponding definitions in the `cretonne.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 function sugnature declared in the function preamble.
|
||||
#: Tbis is used to provide the call signature in an indirect call 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')
|
||||
@@ -1,64 +0,0 @@
|
||||
"""
|
||||
The cretonne.formats defines all instruction formats.
|
||||
|
||||
Every instruction format has a corresponding `InstructionData` variant in the
|
||||
Rust representation of cretonne IL, 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, ieee32, ieee64, offset32, uoffset32
|
||||
from .immediates import boolean, intcc, floatcc, memflags, regunit
|
||||
from .entities import ebb, sig_ref, func_ref, jump_table, stack_slot
|
||||
|
||||
Nullary = InstructionFormat()
|
||||
|
||||
Unary = InstructionFormat(VALUE)
|
||||
UnaryImm = InstructionFormat(imm64)
|
||||
UnaryIeee32 = InstructionFormat(ieee32)
|
||||
UnaryIeee64 = InstructionFormat(ieee64)
|
||||
UnaryBool = InstructionFormat(boolean)
|
||||
|
||||
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)
|
||||
|
||||
InsertLane = InstructionFormat(VALUE, ('lane', uimm8), VALUE)
|
||||
ExtractLane = InstructionFormat(VALUE, ('lane', uimm8))
|
||||
|
||||
IntCompare = InstructionFormat(intcc, VALUE, VALUE)
|
||||
IntCompareImm = InstructionFormat(intcc, VALUE, imm64)
|
||||
FloatCompare = InstructionFormat(floatcc, VALUE, VALUE)
|
||||
|
||||
Jump = InstructionFormat(ebb, VARIABLE_ARGS)
|
||||
Branch = InstructionFormat(VALUE, ebb, VARIABLE_ARGS)
|
||||
BranchIcmp = InstructionFormat(intcc, VALUE, VALUE, ebb, VARIABLE_ARGS)
|
||||
BranchTable = InstructionFormat(VALUE, jump_table)
|
||||
|
||||
Call = InstructionFormat(func_ref, VARIABLE_ARGS)
|
||||
IndirectCall = InstructionFormat(sig_ref, VALUE, VARIABLE_ARGS)
|
||||
|
||||
Load = InstructionFormat(memflags, VALUE, offset32)
|
||||
Store = InstructionFormat(memflags, VALUE, VALUE, offset32)
|
||||
|
||||
StackLoad = InstructionFormat(stack_slot, offset32)
|
||||
StackStore = InstructionFormat(VALUE, stack_slot, offset32)
|
||||
|
||||
# Accessing a WebAssembly heap.
|
||||
# TODO: Add a reference to a `heap` declared in the preamble.
|
||||
HeapLoad = InstructionFormat(VALUE, uoffset32)
|
||||
HeapStore = InstructionFormat(VALUE, VALUE, uoffset32)
|
||||
|
||||
RegMove = InstructionFormat(VALUE, ('src', regunit), ('dst', regunit))
|
||||
|
||||
# Finally extract the names of global variables in this module.
|
||||
InstructionFormat.extract_names(globals())
|
||||
@@ -1,111 +0,0 @@
|
||||
"""
|
||||
The `cretonne.immediates` module predefines all the Cretonne 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:`cretonne.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.')
|
||||
|
||||
#: 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 unsigned offset.
|
||||
#:
|
||||
#: This is used to represent an immediate address offset in WebAssembly memory
|
||||
#: instructions.
|
||||
uoffset32 = ImmediateKind(
|
||||
'uoffset32',
|
||||
'A 32-bit immediate unsigned 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:`cretonne.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 :cton:inst:`icmp` instruction
|
||||
#: and corresponds to the `condcodes::IntCC` Rust type.
|
||||
intcc = ImmediateKind(
|
||||
'intcc',
|
||||
'An integer comparison condition code.',
|
||||
default_member='cond', rust_type='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 :cton: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='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 :cton:inst:`load` and :cton:inst:`store`.
|
||||
memflags = ImmediateKind(
|
||||
'memflags',
|
||||
'Memory operation flags',
|
||||
default_member='flags', rust_type='MemFlags')
|
||||
|
||||
#: A register unit in the current target ISA.
|
||||
regunit = ImmediateKind(
|
||||
'regunit',
|
||||
'A register unit in the target ISA',
|
||||
rust_type='RegUnit')
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,189 +0,0 @@
|
||||
"""
|
||||
Patterns for legalizing the `base` instruction set.
|
||||
|
||||
The base Cretonne 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
|
||||
from .instructions import iadd, iadd_cout, iadd_cin, iadd_carry, iadd_imm
|
||||
from .instructions import isub, isub_bin, isub_bout, isub_borrow
|
||||
from .instructions import band, bor, bxor, isplit, iconcat
|
||||
from .instructions import bnot, band_not, bor_not, bxor_not
|
||||
from .instructions import icmp, icmp_imm
|
||||
from .instructions import iconst, bint
|
||||
from .instructions import ishl, ishl_imm, sshr, sshr_imm, ushr, ushr_imm
|
||||
from .instructions import rotl, rotl_imm, rotr, rotr_imm
|
||||
from cdsl.ast import Var
|
||||
from cdsl.xform import Rtl, XFormGroup
|
||||
|
||||
|
||||
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.
|
||||
""")
|
||||
|
||||
x = Var('x')
|
||||
y = Var('y')
|
||||
a = Var('a')
|
||||
a1 = Var('a1')
|
||||
a2 = Var('a2')
|
||||
b = Var('b')
|
||||
b1 = Var('b1')
|
||||
b2 = Var('b2')
|
||||
b_in = Var('b_in')
|
||||
b_int = Var('b_int')
|
||||
c = Var('c')
|
||||
c1 = Var('c1')
|
||||
c2 = Var('c2')
|
||||
c_in = Var('c_in')
|
||||
c_int = Var('c_int')
|
||||
xl = Var('xl')
|
||||
xh = Var('xh')
|
||||
yl = Var('yl')
|
||||
yh = Var('yh')
|
||||
al = Var('al')
|
||||
ah = Var('ah')
|
||||
cc = Var('cc')
|
||||
|
||||
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)
|
||||
))
|
||||
|
||||
# Expand integer operations with carry for RISC architectures that don't have
|
||||
# the flags.
|
||||
expand.legalize(
|
||||
(a, c) << iadd_cout(x, y),
|
||||
Rtl(
|
||||
a << iadd(x, y),
|
||||
c << icmp(intcc.ult, a, x)
|
||||
))
|
||||
|
||||
expand.legalize(
|
||||
(a, b) << isub_bout(x, y),
|
||||
Rtl(
|
||||
a << isub(x, y),
|
||||
b << icmp(intcc.ugt, a, x)
|
||||
))
|
||||
|
||||
expand.legalize(
|
||||
a << iadd_cin(x, y, c),
|
||||
Rtl(
|
||||
a1 << iadd(x, y),
|
||||
c_int << bint(c),
|
||||
a << iadd(a1, c_int)
|
||||
))
|
||||
|
||||
expand.legalize(
|
||||
a << isub_bin(x, y, b),
|
||||
Rtl(
|
||||
a1 << isub(x, y),
|
||||
b_int << bint(b),
|
||||
a << isub(a1, b_int)
|
||||
))
|
||||
|
||||
expand.legalize(
|
||||
(a, c) << iadd_carry(x, y, c_in),
|
||||
Rtl(
|
||||
(a1, c1) << iadd_cout(x, y),
|
||||
c_int << bint(c_in),
|
||||
(a, c2) << iadd_cout(a1, c_int),
|
||||
c << bor(c1, c2)
|
||||
))
|
||||
|
||||
expand.legalize(
|
||||
(a, b) << isub_borrow(x, y, b_in),
|
||||
Rtl(
|
||||
(a1, b1) << isub_bout(x, y),
|
||||
b_int << bint(b_in),
|
||||
(a, b2) << isub_bout(a1, b_int),
|
||||
b << bor(b1, b2)
|
||||
))
|
||||
|
||||
# Expansions for immediate operands that are out of range.
|
||||
expand.legalize(
|
||||
a << iadd_imm(x, y),
|
||||
Rtl(
|
||||
a1 << iconst(y),
|
||||
a << iadd(x, a1)
|
||||
))
|
||||
|
||||
# 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)
|
||||
))
|
||||
@@ -1,181 +0,0 @@
|
||||
from __future__ import absolute_import
|
||||
from semantics.primitives import prim_to_bv, prim_from_bv, bvsplit, bvconcat,\
|
||||
bvadd, bvult, bvzeroext, bvsignext
|
||||
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
|
||||
from cdsl.ast import Var
|
||||
from cdsl.typevar import TypeSet
|
||||
from cdsl.ti import InTypeset
|
||||
|
||||
x = Var('x')
|
||||
y = Var('y')
|
||||
a = Var('a')
|
||||
b = Var('b')
|
||||
c_out = Var('c_out')
|
||||
c_in = Var('c_in')
|
||||
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),
|
||||
bvc_out << bvult(bva, bvx),
|
||||
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),
|
||||
bvc_out << bvult(bva, bvx),
|
||||
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)
|
||||
))
|
||||
|
||||
icmp.set_semantics(
|
||||
a << icmp(intcc.ult, x, y),
|
||||
(Rtl(
|
||||
bvx << prim_to_bv(x),
|
||||
bvy << prim_to_bv(y),
|
||||
bva << bvult(bvx, bvy),
|
||||
bva_wide << bvzeroext(bva),
|
||||
a << prim_from_bv(bva_wide),
|
||||
), [InTypeset(x.get_typevar(), ScalarTS)]),
|
||||
Rtl(
|
||||
(xlo, xhi) << vsplit(x),
|
||||
(ylo, yhi) << vsplit(y),
|
||||
alo << icmp(intcc.ult, xlo, ylo),
|
||||
ahi << icmp(intcc.ult, xhi, yhi),
|
||||
b << vconcat(alo, ahi),
|
||||
a << bextend(b)
|
||||
))
|
||||
|
||||
#
|
||||
# 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),
|
||||
))
|
||||
@@ -1,45 +0,0 @@
|
||||
"""
|
||||
Cretonne shared settings.
|
||||
|
||||
This module defines settings are are relevant for all code generators.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.settings import SettingGroup, BoolSetting, EnumSetting
|
||||
|
||||
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 Cretonne IL verifier at strategic times during compilation.
|
||||
|
||||
This makes compilation slower but catches many bugs. The verifier is
|
||||
disabled by default, except when reading Cretonne IL from a text file.
|
||||
""")
|
||||
|
||||
is_64bit = BoolSetting("Enable 64-bit code generation")
|
||||
|
||||
is_compressed = BoolSetting("Enable compressed instructions")
|
||||
|
||||
enable_float = BoolSetting(
|
||||
"""Enable the use of floating-point instructions""",
|
||||
default=True)
|
||||
|
||||
enable_simd = BoolSetting(
|
||||
"""Enable the use of SIMD instructions.""",
|
||||
default=True)
|
||||
|
||||
enable_atomics = BoolSetting(
|
||||
"""Enable the use of atomic instructions""",
|
||||
default=True)
|
||||
|
||||
group.close(globals())
|
||||
@@ -1,33 +0,0 @@
|
||||
"""
|
||||
The base.types module predefines all the Cretonne scalar types.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.types import IntType, FloatType, BoolType
|
||||
|
||||
#: Boolean.
|
||||
b1 = BoolType(1) #: 1-bit bool. Type is abstract (can't be stored in mem)
|
||||
b8 = BoolType(8) #: 8-bit bool.
|
||||
b16 = BoolType(16) #: 16-bit bool.
|
||||
b32 = BoolType(32) #: 32-bit bool.
|
||||
b64 = BoolType(64) #: 64-bit bool.
|
||||
|
||||
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.
|
||||
""")
|
||||
@@ -1,32 +0,0 @@
|
||||
# Second-level build script.
|
||||
#
|
||||
# This script is run from lib/cretonne/build.rs to generate Rust files.
|
||||
|
||||
from __future__ import absolute_import
|
||||
import argparse
|
||||
import isa
|
||||
import gen_types
|
||||
import gen_instr
|
||||
import gen_settings
|
||||
import gen_build_deps
|
||||
import gen_encoding
|
||||
import gen_legalizer
|
||||
import gen_registers
|
||||
import gen_binemit
|
||||
|
||||
parser = argparse.ArgumentParser(description='Generate sources for Cretonne.')
|
||||
parser.add_argument('--out-dir', help='set output directory')
|
||||
|
||||
args = parser.parse_args()
|
||||
out_dir = args.out_dir
|
||||
|
||||
isas = isa.all_isas()
|
||||
|
||||
gen_types.generate(out_dir)
|
||||
gen_instr.generate(isas, out_dir)
|
||||
gen_settings.generate(isas, out_dir)
|
||||
gen_encoding.generate(isas, out_dir)
|
||||
gen_legalizer.generate(isas, out_dir)
|
||||
gen_registers.generate(isas, out_dir)
|
||||
gen_binemit.generate(isas, out_dir)
|
||||
gen_build_deps.generate()
|
||||
@@ -1,59 +0,0 @@
|
||||
"""
|
||||
Cretonne DSL classes.
|
||||
|
||||
This module defines the classes that are used to define Cretonne instructions
|
||||
and other entitties.
|
||||
"""
|
||||
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
|
||||
@@ -1,501 +0,0 @@
|
||||
"""
|
||||
Abstract syntax trees.
|
||||
|
||||
This module defines classes that can be used to create abstract syntax trees
|
||||
for patern matching an rewriting of cretonne instructions.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from . import instructions
|
||||
from .typevar import TypeVar
|
||||
from .predicates import IsEqual, And, TypePredicate
|
||||
|
||||
try:
|
||||
from typing import Union, Tuple, Sequence, TYPE_CHECKING, Dict, List # noqa
|
||||
from typing import Optional, Set # noqa
|
||||
if TYPE_CHECKING:
|
||||
from .operands import ImmediateKind # noqa
|
||||
from .predicates import PredNode # noqa
|
||||
VarMap = Dict["Var", "Var"]
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def replace_var(arg, m):
|
||||
# type: (Expr, VarMap) -> 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: Var
|
||||
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: (VarMap) -> Def
|
||||
"""
|
||||
Return a copy of this Def with vars replaced with fresh variables,
|
||||
in accordance with the map m. Update m as neccessary.
|
||||
"""
|
||||
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, VarMap) -> Optional[VarMap]
|
||||
"""
|
||||
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 Var(Expr):
|
||||
"""
|
||||
A free variable.
|
||||
|
||||
When variables are used in `XForms` with source and destination patterns,
|
||||
they are classified as follows:
|
||||
|
||||
Input values
|
||||
Uses in the source pattern with no preceding def. These may appear as
|
||||
inputs in the destination pattern too, but no new inputs can be
|
||||
introduced.
|
||||
Output values
|
||||
Variables that are defined in both the source and destination pattern.
|
||||
These values may have uses outside the source pattern, and the
|
||||
destination pattern must compute the same value.
|
||||
Intermediate values
|
||||
Values that are defined in the source pattern, but not in the
|
||||
destination pattern. These may have uses outside the source pattern, so
|
||||
the defining instruction can't be deleted immediately.
|
||||
Temporary values
|
||||
Values that are defined only in the destination pattern.
|
||||
"""
|
||||
|
||||
def __init__(self, name, typevar=None):
|
||||
# type: (str, TypeVar) -> None
|
||||
self.name = name
|
||||
# The `Def` defining this variable in a source pattern.
|
||||
self.src_def = None # type: Def
|
||||
# The `Def` defining this variable in a destination pattern.
|
||||
self.dst_def = None # type: Def
|
||||
# TypeVar representing the type of this variable.
|
||||
self.typevar = typevar # type: TypeVar
|
||||
# The original 'typeof(x)' type variable that was created for this Var.
|
||||
# This one doesn't change. `self.typevar` above may be changed to
|
||||
# another typevar by type inference.
|
||||
self.original_typevar = self.typevar # type: TypeVar
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
return self.name
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
s = self.name
|
||||
if self.src_def:
|
||||
s += ", src"
|
||||
if self.dst_def:
|
||||
s += ", dst"
|
||||
return "Var({})".format(s)
|
||||
|
||||
# Context bits for `set_def` indicating which pattern has defines of this
|
||||
# var.
|
||||
SRCCTX = 1
|
||||
DSTCTX = 2
|
||||
|
||||
def set_def(self, context, d):
|
||||
# type: (int, Def) -> None
|
||||
"""
|
||||
Set the `Def` that defines this variable in the given context.
|
||||
|
||||
The `context` must be one of `SRCCTX` or `DSTCTX`
|
||||
"""
|
||||
if context == self.SRCCTX:
|
||||
self.src_def = d
|
||||
else:
|
||||
self.dst_def = d
|
||||
|
||||
def get_def(self, context):
|
||||
# type: (int) -> Def
|
||||
"""
|
||||
Get the def of this variable in context.
|
||||
|
||||
The `context` must be one of `SRCCTX` or `DSTCTX`
|
||||
"""
|
||||
if context == self.SRCCTX:
|
||||
return self.src_def
|
||||
else:
|
||||
return self.dst_def
|
||||
|
||||
def is_input(self):
|
||||
# type: () -> bool
|
||||
"""Is this an input value to the src pattern?"""
|
||||
return self.src_def is None and self.dst_def is None
|
||||
|
||||
def is_output(self):
|
||||
# type: () -> bool
|
||||
"""Is this an output value, defined in both src and dst patterns?"""
|
||||
return self.src_def is not None and self.dst_def is not None
|
||||
|
||||
def is_intermediate(self):
|
||||
# type: () -> bool
|
||||
"""Is this an intermediate value, defined only in the src pattern?"""
|
||||
return self.src_def is not None and self.dst_def is None
|
||||
|
||||
def is_temp(self):
|
||||
# type: () -> bool
|
||||
"""Is this a temp value, defined only in the dst pattern?"""
|
||||
return self.src_def is None and self.dst_def is not None
|
||||
|
||||
def get_typevar(self):
|
||||
# type: () -> TypeVar
|
||||
"""Get the type variable representing the type of this variable."""
|
||||
if not self.typevar:
|
||||
# Create a TypeVar allowing all types.
|
||||
tv = TypeVar(
|
||||
'typeof_{}'.format(self),
|
||||
'Type of the pattern variable `{}`'.format(self),
|
||||
ints=True, floats=True, bools=True,
|
||||
scalars=True, simd=True, bitvecs=True)
|
||||
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)
|
||||
|
||||
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 type variables.
|
||||
for bound_ty, tv in zip(self.typevars, self.inst.all_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 copy(self, m):
|
||||
# type: (VarMap) -> Apply
|
||||
"""
|
||||
Return a copy of this Expr with vars replaced with fresh variables,
|
||||
in accordance with the map m. Update m as neccessary.
|
||||
"""
|
||||
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, VarMap) -> Optional[VarMap]
|
||||
"""
|
||||
If the application self and other agree structurally, return a variable
|
||||
substitution to transform self to other. Otherwise return None. Two
|
||||
applications agree structurally if:
|
||||
1) They are over the same instruction
|
||||
2) Every Var v in self, maps to a single Var w in other. I.e for
|
||||
each use of v in self, w is used in the corresponding place in
|
||||
other.
|
||||
"""
|
||||
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):
|
||||
if (isinstance(self_a, Var)):
|
||||
if not isinstance(other_a, Var):
|
||||
return None
|
||||
|
||||
if (self_a not in s):
|
||||
s[self_a] = other_a
|
||||
else:
|
||||
if (s[self_a] != other_a):
|
||||
return None
|
||||
elif isinstance(self_a, ConstantInt):
|
||||
if not isinstance(other_a, ConstantInt):
|
||||
return None
|
||||
assert self_a.kind == other_a.kind
|
||||
if (self_a.value != other_a.value):
|
||||
return None
|
||||
else:
|
||||
assert isinstance(self_a, Enumerator)
|
||||
|
||||
if not isinstance(other_a, Enumerator):
|
||||
# Currently don't support substitutions Var->Enumerator
|
||||
return None
|
||||
|
||||
# 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 ConstantInt(Expr):
|
||||
"""
|
||||
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 greates a `ConstantInt`
|
||||
node.
|
||||
"""
|
||||
|
||||
def __init__(self, kind, value):
|
||||
# type: (ImmediateKind, int) -> None
|
||||
self.kind = kind
|
||||
self.value = value
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
"""
|
||||
Get the Rust expression form of this constant.
|
||||
"""
|
||||
return str(self.value)
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return '{}({})'.format(self.kind, self.value)
|
||||
|
||||
|
||||
class Enumerator(Expr):
|
||||
"""
|
||||
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 IL 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
|
||||
self.kind = kind
|
||||
self.value = value
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
"""
|
||||
Get the Rust expression form of this enumerator.
|
||||
"""
|
||||
return self.kind.rust_enumerator(self.value)
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return '{}.{}'.format(self.kind, self.value)
|
||||
@@ -1,232 +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:`cretonne.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 _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 variables 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 iformat: 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_name(self):
|
||||
# type: () -> str
|
||||
return self.member
|
||||
@@ -1,427 +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 # 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]
|
||||
RtlCase = Union[Rtl, Tuple[Rtl, Sequence[TypeConstraint]]]
|
||||
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_call: This is a call instruction.
|
||||
:param is_return: This is a return instruction.
|
||||
: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_call': 'Is this a call instruction?',
|
||||
'is_return': 'Is this a return 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_*',
|
||||
}
|
||||
|
||||
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))
|
||||
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
|
||||
if self.format.typevar_operand is not None:
|
||||
try:
|
||||
opnum = self.value_opnums[self.format.typevar_operand]
|
||||
tv = self.ins[opnum].typevar
|
||||
if tv is tv.free_typevar() or tv.singleton_type() is not None:
|
||||
self.other_typevars = self._verify_ctrl_typevar(tv)
|
||||
self.ctrl_typevar = tv
|
||||
self.use_typevar_operand = True
|
||||
except RuntimeError as e:
|
||||
typevar_error = e
|
||||
|
||||
if not self.use_typevar_operand:
|
||||
# The typevar_operand argument doesn't work. Can we infer from the
|
||||
# first result instead?
|
||||
if len(self.outs) == 0:
|
||||
if typevar_error:
|
||||
raise typevar_error
|
||||
else:
|
||||
raise RuntimeError(
|
||||
"typevar_operand must be a free type variable")
|
||||
tv = self.outs[0].typevar
|
||||
if tv is not tv.free_typevar():
|
||||
raise RuntimeError("first result must be a free type variable")
|
||||
self.other_typevars = self._verify_ctrl_typevar(tv)
|
||||
self.ctrl_typevar = tv
|
||||
|
||||
def _verify_ctrl_typevar(self, ctrl_typevar):
|
||||
# type: (TypeVar) -> List[TypeVar]
|
||||
"""
|
||||
Verify that the use of TypeVars is consistent with `ctrl_typevar` as
|
||||
the controlling type variable.
|
||||
|
||||
All polymorhic inputs must either be derived from `ctrl_typevar` or be
|
||||
independent free type variables only used once.
|
||||
|
||||
All polymorphic results must be derived from `ctrl_typevar`.
|
||||
|
||||
Return list of other type variables used, or raise an error.
|
||||
"""
|
||||
other_tvs = [] # type: List[TypeVar]
|
||||
# Check value inputs.
|
||||
for opnum in self.value_opnums:
|
||||
typ = self.ins[opnum].typevar
|
||||
tv = typ.free_typevar()
|
||||
# Non-polymorphic or derived form ctrl_typevar is OK.
|
||||
if tv is None or tv is ctrl_typevar:
|
||||
continue
|
||||
# No other derived typevars allowed.
|
||||
if typ is not tv:
|
||||
raise RuntimeError(
|
||||
"{}: type variable {} must be derived from {}"
|
||||
.format(self.ins[opnum], typ.name, ctrl_typevar))
|
||||
# Other free type variables can only be used once each.
|
||||
if tv in other_tvs:
|
||||
raise RuntimeError(
|
||||
"type variable {} can't be used more than once"
|
||||
.format(tv.name))
|
||||
other_tvs.append(tv)
|
||||
|
||||
# Check outputs.
|
||||
for result in self.outs:
|
||||
if not result.is_value():
|
||||
continue
|
||||
typ = result.typevar
|
||||
tv = typ.free_typevar()
|
||||
# Non-polymorphic or derived from ctrl_typevar is OK.
|
||||
if tv is None or tv is ctrl_typevar:
|
||||
continue
|
||||
raise RuntimeError(
|
||||
"type variable in output not derived from ctrl_typevar")
|
||||
|
||||
return other_tvs
|
||||
|
||||
def all_typevars(self):
|
||||
# type: () -> List[TypeVar]
|
||||
"""
|
||||
Get a list of all type variables in the instruction.
|
||||
"""
|
||||
if self.is_polymorphic:
|
||||
return [self.ctrl_typevar] + self.other_typevars
|
||||
else:
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def _to_operand_tuple(x):
|
||||
# type: (Union[Sequence[Operand], Operand]) -> Tuple[Operand, ...]
|
||||
# Allow a single Operand instance instead of the awkward singleton
|
||||
# tuple syntax.
|
||||
if isinstance(x, Operand):
|
||||
x = (x,)
|
||||
else:
|
||||
x = tuple(x)
|
||||
for op in x:
|
||||
assert isinstance(op, Operand)
|
||||
return x
|
||||
|
||||
@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):
|
||||
x = (x,)
|
||||
else:
|
||||
x = tuple(x)
|
||||
for op in x:
|
||||
assert isinstance(op, TypeConstraint)
|
||||
return x
|
||||
|
||||
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], *RtlCase) -> 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))
|
||||
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)
|
||||
@@ -1,462 +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, InstructionGroup, 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.regclasses = list() # type: List[RegClass]
|
||||
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_regclasses()
|
||||
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_regclasses(self):
|
||||
# type: () -> None
|
||||
"""
|
||||
Collect and number register classes.
|
||||
|
||||
Every register class needs a unique index, and the classes need to be
|
||||
topologically ordered.
|
||||
|
||||
We also want all the top-level register classes to be first.
|
||||
"""
|
||||
# Compute subclasses and top-level classes in each bank.
|
||||
# Collect the top-level classes so they get numbered consecutively.
|
||||
for bank in self.regbanks:
|
||||
bank.finish_regclasses()
|
||||
self.regclasses.extend(bank.toprcs)
|
||||
|
||||
# The limit on the number of top-level register classes can be raised.
|
||||
# This should be coordinated with the `MAX_TOPRCS` constant in
|
||||
# `isa/registers.rs`.
|
||||
assert len(self.regclasses) <= 4, "Too many top-level register classes"
|
||||
|
||||
# Collect all of the non-top-level register classes.
|
||||
# They are numbered strictly after the top-level classes.
|
||||
for bank in self.regbanks:
|
||||
self.regclasses.extend(
|
||||
rc for rc in bank.classes if not rc.is_toprc())
|
||||
|
||||
for idx, rc in enumerate(self.regclasses):
|
||||
rc.index = idx
|
||||
|
||||
# The limit on the number of register classes can be changed. It should
|
||||
# be coordinated with the `RegClassMask` and `RegClassIndex` types in
|
||||
# `isa/registers.rs`.
|
||||
assert len(self.regclasses) <= 32, "Too many register classes"
|
||||
|
||||
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 sorted(cpumode.type_legalize.values(),
|
||||
key=lambda x: x.name):
|
||||
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 = dict() # type: Dict[ValueType, XFormGroup]
|
||||
|
||||
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 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.
|
||||
|
||||
:param name: Short mnemonic name for this recipe.
|
||||
:param format: All encoded instructions must have this
|
||||
:py:class:`InstructionFormat`.
|
||||
:param size: Number of bytes in the binary encoded instruction.
|
||||
: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: instp Instruction predicate.
|
||||
:param: isap ISA predicate.
|
||||
:param: emit Rust code for binary emission.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name, # type: str
|
||||
format, # type: InstructionFormat
|
||||
size, # type: int
|
||||
ins, # type: ConstraintSeq
|
||||
outs, # type: ConstraintSeq
|
||||
branch_range=None, # type: BranchRange
|
||||
instp=None, # type: PredNode
|
||||
isap=None, # type: PredNode
|
||||
emit=None # type: str
|
||||
):
|
||||
# type: (...) -> None
|
||||
self.name = name
|
||||
self.format = format
|
||||
assert size >= 0
|
||||
self.size = size
|
||||
self.branch_range = branch_range
|
||||
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 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:
|
||||
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
|
||||
@@ -1,222 +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 # noqa
|
||||
OperandSpec = Union['OperandKind', ValueType, TypeVar]
|
||||
if TYPE_CHECKING:
|
||||
from .ast import Enumerator, ConstantInt # 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 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 arguemtns 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
|
||||
# `cretonne.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
|
||||
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 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])
|
||||
|
||||
|
||||
# Instances of entity reference operand types are provided in the
|
||||
# `cretonne.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
|
||||
@@ -1,375 +0,0 @@
|
||||
"""
|
||||
Cretonne 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']
|
||||
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 '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 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 `dfg` and `args` variables.
|
||||
"""
|
||||
return 'dfg.value_type(args[{}]) == {}'.format(
|
||||
self.value_arg, self.value_type.rust_name())
|
||||
@@ -1,354 +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, TYPE_CHECKING # noqa
|
||||
if TYPE_CHECKING:
|
||||
from .isa import TargetISA # noqa
|
||||
# A tuple uniquely identifying a register class inside a register bank.
|
||||
# (count, width, start)
|
||||
RCTup = Tuple[int, 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 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, isa, doc, units, prefix='r', names=()):
|
||||
# type: (str, TargetISA, str, int, str, Sequence[str]) -> None
|
||||
self.name = name
|
||||
self.isa = isa
|
||||
self.first_unit = 0
|
||||
self.units = units
|
||||
self.prefix = prefix
|
||||
self.names = names
|
||||
self.classes = list() # type: List[RegClass]
|
||||
self.toprcs = list() # type: List[RegClass]
|
||||
self.first_toprc_index = None # type: int
|
||||
|
||||
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=None, width=1, start=0):
|
||||
# type: (RegBank, int, int, int) -> None
|
||||
self.name = None # type: str
|
||||
self.index = None # type: int
|
||||
self.bank = bank
|
||||
self.start = start
|
||||
self.width = width
|
||||
|
||||
# This is computed later in `finish_regclasses()`.
|
||||
self.subclasses = list() # type: List[RegClass]
|
||||
self.toprc = None # type: RegClass
|
||||
|
||||
assert width > 0
|
||||
assert start >= 0 and start < bank.units
|
||||
|
||||
if count is None:
|
||||
count = bank.units // width
|
||||
self.count = count
|
||||
|
||||
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.count, self.width, self.start)
|
||||
|
||||
def intersect(self, other):
|
||||
# type: (RegClass) -> RCTup
|
||||
"""
|
||||
Get a tuple representing the intersction of two register classes.
|
||||
|
||||
Returns `None` if the two classes are disjoint.
|
||||
"""
|
||||
if self.width != other.width:
|
||||
return None
|
||||
s_end = self.start + self.count * self.width
|
||||
o_end = other.start + other.count * other.width
|
||||
if self.start >= o_end or other.start >= s_end:
|
||||
return None
|
||||
|
||||
# We have an overlap.
|
||||
start = max(self.start, other.start)
|
||||
end = min(s_end, o_end)
|
||||
count = (end - start) // self.width
|
||||
assert count > 0
|
||||
return (count, self.width, start)
|
||||
|
||||
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'
|
||||
|
||||
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 __getattr__(self, attr):
|
||||
# type: (str) -> Register
|
||||
"""
|
||||
Get a specific register in the class by name.
|
||||
|
||||
For example: `GPR.r5`.
|
||||
"""
|
||||
return Register(self, self.bank.unit_by_name(attr))
|
||||
|
||||
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.
|
||||
"""
|
||||
mask = [0] * MASK_LEN
|
||||
|
||||
start = self.bank.first_unit + self.start
|
||||
for a in range(self.count):
|
||||
u = start + a * self.width
|
||||
b = u % 32
|
||||
# We need fancier masking code if a register can straddle mask
|
||||
# words. This will only happen with widths that are not powers of
|
||||
# two.
|
||||
assert b + self.width <= 32, 'Register straddles words'
|
||||
mask[u // 32] |= 1 << b
|
||||
|
||||
return 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 variables 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
|
||||
@@ -1,407 +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 Cretonne.
|
||||
|
||||
Settings are normally not named when they are created. They get their name
|
||||
from the `extract_names` method.
|
||||
"""
|
||||
|
||||
def __init__(self, doc):
|
||||
# type: (str) -> None
|
||||
self.name = None # type: str # Assigned later by `extract_names()`.
|
||||
self.__doc__ = doc
|
||||
# Offset of byte in settings vector containing this setting.
|
||||
self.byte_offset = None # type: int
|
||||
# Index into the generated DESCRIPTORS table.
|
||||
self.descriptor_index = None # type: int
|
||||
|
||||
self.group = SettingGroup.append(self)
|
||||
|
||||
def __str__(self):
|
||||
# type: () -> str
|
||||
return '{}.{}'.format(self.group.name, self.name)
|
||||
|
||||
def default_byte(self):
|
||||
# type: () -> int
|
||||
raise NotImplementedError("default_byte is an abstract method")
|
||||
|
||||
def byte_for_value(self, value):
|
||||
# type: (Any) -> int
|
||||
"""Get the setting byte value that corresponds to `value`"""
|
||||
raise NotImplementedError("byte_for_value is an abstract method")
|
||||
|
||||
def byte_mask(self):
|
||||
# type: () -> int
|
||||
"""Get a mask of bits in our byte that are relevant to this setting."""
|
||||
# Only BoolSetting has a different mask.
|
||||
return 0xff
|
||||
|
||||
|
||||
class BoolSetting(Setting):
|
||||
"""
|
||||
A named setting with a boolean on/off value.
|
||||
|
||||
:param doc: Documentation string.
|
||||
:param default: The default value of this setting.
|
||||
"""
|
||||
|
||||
def __init__(self, doc, default=False):
|
||||
# type: (str, bool) -> None
|
||||
super(BoolSetting, self).__init__(doc)
|
||||
self.default = default
|
||||
self.bit_offset = None # type: int
|
||||
|
||||
def default_byte(self):
|
||||
# type: () -> int
|
||||
"""
|
||||
Get the default value of this setting, as a byte that can be bitwise
|
||||
or'ed with the other booleans sharing the same byte.
|
||||
"""
|
||||
if self.default:
|
||||
return 1 << self.bit_offset
|
||||
else:
|
||||
return 0
|
||||
|
||||
def byte_for_value(self, value):
|
||||
# type: (Any) -> int
|
||||
if value:
|
||||
return 1 << self.bit_offset
|
||||
else:
|
||||
return 0
|
||||
|
||||
def byte_mask(self):
|
||||
# type: () -> int
|
||||
return 1 << self.bit_offset
|
||||
|
||||
def predicate_context(self):
|
||||
# type: () -> SettingGroup
|
||||
"""
|
||||
Return the context where this setting can be evaluated as a (leaf)
|
||||
predicate.
|
||||
"""
|
||||
return self.group
|
||||
|
||||
def predicate_key(self):
|
||||
# type: () -> PredKey
|
||||
assert self.name, "Can't compute key before setting is named"
|
||||
return ('setting', self.group.name, self.name)
|
||||
|
||||
def predicate_leafs(self, leafs):
|
||||
# type: (Set[PredLeaf]) -> None
|
||||
leafs.add(self)
|
||||
|
||||
def rust_predicate(self, prec):
|
||||
# type: (int) -> str
|
||||
"""
|
||||
Return the Rust code to compute the value of this setting.
|
||||
|
||||
The emitted code assumes that the setting group exists as a local
|
||||
variable.
|
||||
"""
|
||||
return '{}.{}()'.format(self.group.name, self.name)
|
||||
|
||||
|
||||
class NumSetting(Setting):
|
||||
"""
|
||||
A named setting with an integral value in the range 0--255.
|
||||
|
||||
:param doc: Documentation string.
|
||||
:param default: The default value of this setting.
|
||||
"""
|
||||
|
||||
def __init__(self, doc, default=0):
|
||||
# type: (str, int) -> None
|
||||
super(NumSetting, self).__init__(doc)
|
||||
assert default == int(default)
|
||||
assert default >= 0 and default <= 255
|
||||
self.default = default
|
||||
|
||||
def default_byte(self):
|
||||
# type: () -> int
|
||||
return self.default
|
||||
|
||||
def byte_for_value(self, value):
|
||||
# type: (Any) -> int
|
||||
assert isinstance(value, int), "NumSetting must be set to an int"
|
||||
assert value >= 0 and value <= 255
|
||||
return value
|
||||
|
||||
|
||||
class EnumSetting(Setting):
|
||||
"""
|
||||
A named setting with an enumerated set of possible values.
|
||||
|
||||
The default value is always the first enumerator.
|
||||
|
||||
:param doc: Documentation string.
|
||||
:param args: Tuple of unique strings representing the possible values.
|
||||
"""
|
||||
|
||||
def __init__(self, doc, *args):
|
||||
# type: (str, *str) -> None
|
||||
super(EnumSetting, self).__init__(doc)
|
||||
assert len(args) > 0, "EnumSetting must have at least one value"
|
||||
self.values = tuple(str(x) for x in args)
|
||||
self.default = self.values[0]
|
||||
|
||||
def default_byte(self):
|
||||
# type: () -> int
|
||||
return 0
|
||||
|
||||
def byte_for_value(self, value):
|
||||
# type: (Any) -> int
|
||||
return self.values.index(value)
|
||||
|
||||
|
||||
class SettingGroup(object):
|
||||
"""
|
||||
A group of settings.
|
||||
|
||||
Whenever a :class:`Setting` object is created, it is added to the currently
|
||||
open group. A setting group must be closed explicitly before another can be
|
||||
opened.
|
||||
|
||||
:param name: Short mnemonic name for setting group.
|
||||
:param parent: Parent settings group.
|
||||
"""
|
||||
|
||||
# The currently open setting group.
|
||||
_current = None # type: SettingGroup
|
||||
|
||||
def __init__(self, name, parent=None):
|
||||
# type: (str, SettingGroup) -> None
|
||||
self.name = name
|
||||
self.parent = parent
|
||||
self.settings = [] # type: List[Setting]
|
||||
# Named predicates computed from settings in this group or its
|
||||
# parents.
|
||||
self.named_predicates = OrderedDict() # type: OrderedDict[str, Predicate] # noqa
|
||||
# All boolean predicates that can be accessed by number. This includes:
|
||||
# - All boolean settings in this group.
|
||||
# - All named predicates.
|
||||
# - Added anonymous predicates, see `number_predicate()`.
|
||||
# - Added parent predicates that are replicated in this group.
|
||||
# Maps predicate -> number.
|
||||
self.predicate_number = OrderedDict() # type: OrderedDict[PredNode, int] # noqa
|
||||
self.presets = [] # type: List[Preset]
|
||||
|
||||
# Fully qualified Rust module name. See gen_settings.py.
|
||||
self.qual_mod = None # type: str
|
||||
|
||||
self.open()
|
||||
|
||||
def open(self):
|
||||
# type: () -> None
|
||||
"""
|
||||
Open this setting group such that future new settings are added to this
|
||||
group.
|
||||
"""
|
||||
assert SettingGroup._current is None, (
|
||||
"Can't open {} since {} is already open"
|
||||
.format(self, SettingGroup._current))
|
||||
SettingGroup._current = self
|
||||
|
||||
def close(self, globs=None):
|
||||
# type: (Dict[str, Any]) -> None
|
||||
"""
|
||||
Close this setting group. This function must be called before opening
|
||||
another setting group.
|
||||
|
||||
:param globs: Pass in `globals()` to run `extract_names` on all
|
||||
settings defined in the module.
|
||||
"""
|
||||
assert SettingGroup._current is self, (
|
||||
"Can't close {}, the open setting group is {}"
|
||||
.format(self, SettingGroup._current))
|
||||
SettingGroup._current = None
|
||||
if globs:
|
||||
for name, obj in globs.items():
|
||||
if isinstance(obj, Setting):
|
||||
assert obj.name is None, obj.name
|
||||
obj.name = name
|
||||
if isinstance(obj, Predicate):
|
||||
self.named_predicates[name] = obj
|
||||
if isinstance(obj, Preset):
|
||||
assert obj.name is None, obj.name
|
||||
obj.name = name
|
||||
|
||||
self.layout()
|
||||
|
||||
@staticmethod
|
||||
def append(setting):
|
||||
# type: (Setting) -> SettingGroup
|
||||
g = SettingGroup._current
|
||||
assert g, "Open a setting group before defining settings."
|
||||
g.settings.append(setting)
|
||||
return g
|
||||
|
||||
@staticmethod
|
||||
def append_preset(preset):
|
||||
# type: (Preset) -> SettingGroup
|
||||
g = SettingGroup._current
|
||||
assert g, "Open a setting group before defining presets."
|
||||
g.presets.append(preset)
|
||||
return g
|
||||
|
||||
def number_predicate(self, pred):
|
||||
# type: (PredNode) -> int
|
||||
"""
|
||||
Make sure that `pred` has an assigned number, and will be included in
|
||||
this group's bit vector.
|
||||
|
||||
The numbered predicates include:
|
||||
- `BoolSetting` settings that belong to this group.
|
||||
- `Predicate` instances in `named_predicates`.
|
||||
- `Predicate` instances without a name.
|
||||
- Settings or computed predicates that belong to the parent group, but
|
||||
need to be accessible by number in this group.
|
||||
|
||||
The numbered predicates are referenced by the encoding tables as ISA
|
||||
predicates. See the `isap` field on `Encoding`.
|
||||
|
||||
:returns: The assigned predicate number in this group.
|
||||
"""
|
||||
if pred in self.predicate_number:
|
||||
return self.predicate_number[pred]
|
||||
else:
|
||||
number = len(self.predicate_number)
|
||||
self.predicate_number[pred] = number
|
||||
return number
|
||||
|
||||
def layout(self):
|
||||
# type: () -> None
|
||||
"""
|
||||
Compute the layout of the byte vector used to represent this settings
|
||||
group.
|
||||
|
||||
The byte vector contains the following entries in order:
|
||||
|
||||
1. Byte-sized settings like `NumSetting` and `EnumSetting`.
|
||||
2. `BoolSetting` settings.
|
||||
3. Precomputed named predicates.
|
||||
4. Other numbered predicates, including anonymous predicates and parent
|
||||
predicates that need to be accessible by number.
|
||||
|
||||
Set `self.settings_size` to the length of the byte vector prefix that
|
||||
contains the settings. All bytes after that are computed, not
|
||||
configured.
|
||||
|
||||
Set `self.boolean_offset` to the beginning of the numbered predicates,
|
||||
2. in the list above.
|
||||
|
||||
Assign `byte_offset` and `bit_offset` fields in all settings.
|
||||
|
||||
After calling this method, no more settings can be added, but
|
||||
additional predicates can be made accessible with `number_predicate()`.
|
||||
"""
|
||||
assert len(self.predicate_number) == 0, "Too late for layout"
|
||||
|
||||
# Assign the non-boolean settings.
|
||||
byte_offset = 0
|
||||
for s in self.settings:
|
||||
if not isinstance(s, BoolSetting):
|
||||
s.byte_offset = byte_offset
|
||||
byte_offset += 1
|
||||
|
||||
# Then the boolean settings.
|
||||
self.boolean_offset = byte_offset
|
||||
for s in self.settings:
|
||||
if isinstance(s, BoolSetting):
|
||||
number = self.number_predicate(s)
|
||||
s.byte_offset = byte_offset + number // 8
|
||||
s.bit_offset = number % 8
|
||||
|
||||
# This is the end of the settings. Round up to a whole number of bytes.
|
||||
self.boolean_settings = len(self.predicate_number)
|
||||
self.settings_size = self.byte_size()
|
||||
|
||||
# Now assign numbers to all our named predicates.
|
||||
for name, pred in self.named_predicates.items():
|
||||
self.number_predicate(pred)
|
||||
|
||||
def byte_size(self):
|
||||
# type: () -> int
|
||||
"""
|
||||
Compute the number of bytes required to hold all settings and
|
||||
precomputed predicates.
|
||||
|
||||
This is the size of the byte-sized settings plus all the numbered
|
||||
predcate 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.
|
||||
"""
|
||||
l = [(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 = l[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
|
||||
l[ofs] = (l_mask, l_val)
|
||||
|
||||
return l
|
||||
@@ -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'))")
|
||||
@@ -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
|
||||
@@ -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
|
||||
l = [(t[self.v0], t[self.v1]) for t in typing.concrete_typings()]
|
||||
assert (len(l) == len(set(l)) and len(l) == 90)
|
||||
for (tv0, tv1) in l:
|
||||
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
|
||||
l = [(t[self.v0], t[self.v1]) for t in typing.concrete_typings()]
|
||||
assert (len(l) == len(set(l)) and len(l) == 27)
|
||||
for (tv0, tv1) in l:
|
||||
(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
|
||||
# unneccessary 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)
|
||||
))
|
||||
@@ -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 instersections 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)
|
||||
@@ -1,94 +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_bad(self):
|
||||
src = Rtl(a << icmp(CC1, x, y))
|
||||
dst = Rtl(b << icmp(intcc.eq, z, u))
|
||||
assert src.substitution(dst, {}) is None
|
||||
|
||||
src = Rtl(a << icmp(intcc.eq, x, y))
|
||||
dst = Rtl(b << icmp(CC1, z, u))
|
||||
assert src.substitution(dst, {}) is None
|
||||
|
||||
src = Rtl(a << icmp(intcc.eq, x, y))
|
||||
dst = Rtl(b << icmp(intcc.sge, z, u))
|
||||
assert src.substitution(dst, {}) is None
|
||||
@@ -1,886 +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 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 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 neccessary 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
|
||||
cannonical 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 associted 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 correpsonding 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)
|
||||
@@ -1,286 +0,0 @@
|
||||
"""Cretonne 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
|
||||
|
||||
|
||||
# ValueType instances (i8, i32, ...) are provided in the cretonne.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 scalar types.
|
||||
all_scalars = list() # type: List[ScalarType]
|
||||
|
||||
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 ScalarType(ValueType):
|
||||
"""
|
||||
A concrete scalar (not vector) type.
|
||||
|
||||
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(ScalarType, self).__init__(name, membytes, doc)
|
||||
self._vectors = dict() # type: Dict[int, VectorType]
|
||||
# Assign numbers starting from 1. (0 is VOID).
|
||||
ValueType.all_scalars.append(self)
|
||||
self.number = len(ValueType.all_scalars)
|
||||
assert self.number < 16, 'Too many scalar types'
|
||||
|
||||
def __repr__(self):
|
||||
# type: () -> str
|
||||
return 'ScalarType({})'.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:`ScalarType`,
|
||||
and a positive number of lanes.
|
||||
"""
|
||||
|
||||
def __init__(self, base, lanes):
|
||||
# type: (ScalarType, int) -> None
|
||||
assert isinstance(base, ScalarType), 'SIMD lanes must be scalar types'
|
||||
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))
|
||||
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 IntType(ScalarType):
|
||||
"""A concrete scalar integer type."""
|
||||
|
||||
def __init__(self, bits):
|
||||
# type: (int) -> None
|
||||
assert bits > 0, 'IntType must have positive number of bits'
|
||||
super(IntType, self).__init__(
|
||||
name='i{:d}'.format(bits),
|
||||
membytes=bits // 8,
|
||||
doc="An integer type with {} bits.".format(bits))
|
||||
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(ScalarType):
|
||||
"""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(ScalarType):
|
||||
"""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 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
|
||||
@@ -1,853 +0,0 @@
|
||||
"""
|
||||
Type variables for Parametric polymorphism.
|
||||
|
||||
Cretonne 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 deepcopy
|
||||
|
||||
try:
|
||||
from typing import Tuple, Union, Iterable, Any, Set, TYPE_CHECKING # noqa
|
||||
if TYPE_CHECKING:
|
||||
from srcgen import Formatter # noqa
|
||||
from .types import ValueType # noqa
|
||||
Interval = Tuple[int, int]
|
||||
# An Interval where `True` means 'everything'
|
||||
BoolInterval = Union[bool, Interval]
|
||||
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 buig 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})
|
||||
|
||||
: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.
|
||||
"""
|
||||
|
||||
def __init__(self, lanes=None, ints=None, floats=None, bools=None,
|
||||
bitvecs=None):
|
||||
# type: (BoolInterval, BoolInterval, BoolInterval, BoolInterval, BoolInterval) -> None # noqa
|
||||
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)))
|
||||
|
||||
def copy(self):
|
||||
# type: (TypeSet) -> TypeSet
|
||||
"""
|
||||
Return a copy of our self. deepcopy is sufficient and safe here, since
|
||||
TypeSet contains only sets of numbers.
|
||||
"""
|
||||
return deepcopy(self)
|
||||
|
||||
def typeset_key(self):
|
||||
# type: () -> 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))))
|
||||
|
||||
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))
|
||||
return s + ')'
|
||||
|
||||
def emit_fields(self, fmt):
|
||||
# type: (Formatter) -> None
|
||||
"""Emit field initializers for this typeset."""
|
||||
assert len(self.bitvecs) == 0, "Bitvector types are not emitable."
|
||||
fmt.comment(repr(self))
|
||||
|
||||
fields = (('lanes', 16),
|
||||
('ints', 8),
|
||||
('floats', 8),
|
||||
('bools', 8))
|
||||
|
||||
for (field, bits) in fields:
|
||||
vals = [int_log2(x) for x in getattr(self, field)]
|
||||
fmt.line('{}: BitSet::<u{}>({}),'
|
||||
.format(field, bits, encode_bitset(vals, bits)))
|
||||
|
||||
def __iand__(self, other):
|
||||
# type: (TypeSet) -> TypeSet
|
||||
"""
|
||||
Intersect self with other type set.
|
||||
|
||||
>>> a = TypeSet(lanes=True, ints=(16, 32))
|
||||
>>> a
|
||||
TypeSet(lanes={1, 2, 4, 8, 16, 32, 64, 128, 256}, ints={16, 32})
|
||||
>>> b = TypeSet(lanes=(4, 16), ints=True)
|
||||
>>> a &= b
|
||||
>>> a
|
||||
TypeSet(lanes={4, 8, 16}, ints={16, 32})
|
||||
|
||||
>>> a = TypeSet(lanes=True, bools=(1, 8))
|
||||
>>> b = TypeSet(lanes=True, bools=(16, 32))
|
||||
>>> a &= b
|
||||
>>> a
|
||||
TypeSet(lanes={1, 2, 4, 8, 16, 32, 64, 128, 256})
|
||||
"""
|
||||
self.lanes.intersection_update(other.lanes)
|
||||
self.ints.intersection_update(other.ints)
|
||||
self.floats.intersection_update(other.floats)
|
||||
self.bools.intersection_update(other.bools)
|
||||
self.bitvecs.intersection_update(other.bitvecs)
|
||||
|
||||
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)
|
||||
|
||||
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])
|
||||
|
||||
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])
|
||||
|
||||
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])
|
||||
|
||||
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])
|
||||
|
||||
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])
|
||||
|
||||
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))
|
||||
|
||||
def concrete_types(self):
|
||||
# type: () -> Iterable[types.ValueType]
|
||||
def by(scalar, lanes):
|
||||
# type: (types.ScalarType, 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)
|
||||
|
||||
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, doc,
|
||||
ints=False, floats=False, bools=False,
|
||||
scalars=True, simd=False, bitvecs=False,
|
||||
base=None, derived_func=None):
|
||||
# type: (str, str, BoolInterval, BoolInterval, BoolInterval, bool, BoolInterval, BoolInterval, TypeVar, str) -> None # noqa
|
||||
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)
|
||||
|
||||
@staticmethod
|
||||
def singleton(typ):
|
||||
# type: (types.ValueType) -> TypeVar
|
||||
"""Create a type variable that can only assume a single type."""
|
||||
scalar = None # type: ValueType
|
||||
if isinstance(typ, types.VectorType):
|
||||
scalar = typ.base
|
||||
lanes = (typ.lanes, typ.lanes)
|
||||
elif isinstance(typ, types.ScalarType):
|
||||
scalar = typ
|
||||
lanes = (1, 1)
|
||||
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()
|
||||
|
||||
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: () -> 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
|
||||
@@ -1,395 +0,0 @@
|
||||
"""
|
||||
Instruction transformations.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from .ast import Def, Var, Apply
|
||||
from .ti import ti_xform, TypeEnv, get_type_env
|
||||
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, VarMap # noqa
|
||||
from .isa import TargetISA # noqa
|
||||
from .ti import TypeConstraint # noqa
|
||||
from .typevar import TypeVar # 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: (VarMap) -> Rtl
|
||||
"""
|
||||
Return a copy of this rtl with all Vars substituted with copies or
|
||||
according to m. Update m as neccessary.
|
||||
"""
|
||||
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, VarMap) -> Optional[VarMap]
|
||||
"""
|
||||
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])
|
||||
|
||||
|
||||
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[Sequence[TypeConstraint]]) -> 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()
|
||||
|
||||
if constraints is not None:
|
||||
for c in constraints:
|
||||
type_m = {tv: interp_tv(tv) for tv in c.tvs()}
|
||||
self.ti.add_constraint(c.translate(type_m))
|
||||
|
||||
# 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: VarMap
|
||||
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.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 '::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)
|
||||
@@ -1,26 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
cd $(dirname "$0")
|
||||
|
||||
runif() {
|
||||
if command -v "$1" > /dev/null; then
|
||||
echo " === $1 ==="
|
||||
"$@"
|
||||
else
|
||||
echo "$1 not found"
|
||||
fi
|
||||
}
|
||||
|
||||
# Style linting.
|
||||
runif flake8 .
|
||||
|
||||
# Type checking.
|
||||
runif mypy --py2 build.py
|
||||
|
||||
# Python unit tests.
|
||||
runif python -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
|
||||
@@ -1,63 +0,0 @@
|
||||
"""
|
||||
Generate constant hash tables.
|
||||
|
||||
The `constant_hash` module can generate constant pre-populated hash tables. We
|
||||
don't attempt parfect 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:
|
||||
>>> hex(simple_hash("Hello"))
|
||||
'0x2fa70c01'
|
||||
>>> hex(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
|
||||
@@ -1,157 +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 # 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
|
||||
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 == 'RegMove')
|
||||
|
||||
# First unpack the instruction.
|
||||
with fmt.indented(
|
||||
'if let InstructionData::{} {{'.format(iform.name),
|
||||
'}'):
|
||||
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] {')
|
||||
|
||||
# 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];')
|
||||
|
||||
# Unwrap interesting input arguments.
|
||||
# Don't bother with fixed registers.
|
||||
args = ''
|
||||
for i, arg in enumerate(recipe.ins):
|
||||
if isinstance(arg, RegClass) and not is_regmove:
|
||||
v = 'in_reg{}'.format(i)
|
||||
args += ', ' + v
|
||||
fmt.line(
|
||||
'let {} = divert.reg(args[{}], &func.locations);'
|
||||
.format(v, i))
|
||||
elif isinstance(arg, Stack):
|
||||
v = 'in_ss{}'.format(i)
|
||||
args += ', ' + v
|
||||
fmt.line(
|
||||
'let {} = func.locations[args[{}]].unwrap_stack();'
|
||||
.format(v, i))
|
||||
|
||||
# Pass arguments in this order: inputs, imm_fields, outputs.
|
||||
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);')
|
||||
for i, res in enumerate(recipe.outs):
|
||||
if isinstance(res, RegClass):
|
||||
v = 'out_reg{}'.format(i)
|
||||
args += ', ' + v
|
||||
fmt.line(
|
||||
'let {} = func.locations[results[{}]].unwrap_reg();'
|
||||
.format(v, i))
|
||||
elif isinstance(res, Stack):
|
||||
v = 'out_ss{}'.format(i)
|
||||
args += ', ' + v
|
||||
fmt.line(
|
||||
'let {} = func.locations[results[{}]].unwrap_stack();'
|
||||
.format(v, i))
|
||||
|
||||
# Special handling for regmove instructions. Update the register
|
||||
# diversion tracker.
|
||||
if recipe.format.name == 'RegMove':
|
||||
fmt.line('divert.regmove(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 gen_isa(isa, fmt):
|
||||
# type: (TargetISA, srcgen.Formatter) -> None
|
||||
"""
|
||||
Generate Binary emission code for `isa`.
|
||||
"""
|
||||
fmt.doc_comment(
|
||||
'''
|
||||
Emit binary machine code for `inst` for the {} ISA.
|
||||
'''.format(isa.name))
|
||||
if len(isa.all_recipes) == 0:
|
||||
# No encoding recipes: Emit a stub.
|
||||
with fmt.indented(
|
||||
'pub fn emit_inst<CS: CodeSink + ?Sized>'
|
||||
'(func: &Function, inst: Inst, '
|
||||
'_divert: &mut RegDiversions, _sink: &mut CS) {', '}'):
|
||||
fmt.line('bad_encoding(func, inst)')
|
||||
else:
|
||||
fmt.line('#[allow(unused_variables, unreachable_code)]')
|
||||
with fmt.indented(
|
||||
'pub fn emit_inst<CS: CodeSink + ?Sized>'
|
||||
'(func: &Function, inst: Inst, '
|
||||
'divert: &mut RegDiversions, sink: &mut CS) {', '}'):
|
||||
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.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)
|
||||
@@ -1,43 +0,0 @@
|
||||
"""
|
||||
Generate build dependencies for Cargo.
|
||||
|
||||
The `build.py` script is invoked by cargo when building lib/cretonne 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 source_files(top):
|
||||
# type: (str) -> Iterable[str]
|
||||
"""
|
||||
Recursively find all interesting source files and directories in the
|
||||
directory tree starting at top. Yield a path to each file.
|
||||
"""
|
||||
for (dirpath, dirnames, filenames) in os.walk(top):
|
||||
yield dirpath
|
||||
for f in filenames:
|
||||
if f.endswith('.py'):
|
||||
yield join(dirpath, f)
|
||||
|
||||
|
||||
def generate():
|
||||
# type: () -> None
|
||||
print("Dependencies from meta language directory:")
|
||||
meta = dirname(abspath(__file__))
|
||||
for path in source_files(meta):
|
||||
print("cargo:rerun-if-changed=" + path)
|
||||
@@ -1,889 +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 `VOID` 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_dfg=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(&dfg.value_lists);')
|
||||
fmt.format('return {};', 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_name())
|
||||
else:
|
||||
assert isinstance(p, TypePredicate)
|
||||
has_type_check = True
|
||||
fields = ', '.join(sorted(fnames))
|
||||
|
||||
with fmt.indented(
|
||||
'if let ir::InstructionData::{} {{ {}, .. }} = *inst {{'
|
||||
.format(iform.name, fields), '}'):
|
||||
if has_type_check:
|
||||
# We could implement this if we need to.
|
||||
assert has_dfg, "Recipe predicates can't check type variables."
|
||||
fmt.line('let args = inst.arguments(&dfg.value_lists);')
|
||||
elif has_dfg:
|
||||
# Silence dead argument warning.
|
||||
fmt.line('let _ = dfg;')
|
||||
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 {}(dfg: &ir::DataFlowGraph, inst: &ir::InstructionData)'
|
||||
'-> bool {{'.format(name), '}'):
|
||||
emit_instp(instp, fmt, has_dfg=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 {}({}: ::settings::PredicateView, '
|
||||
'inst: &ir::InstructionData) -> bool {{'
|
||||
.format(
|
||||
name,
|
||||
'isap' if isap else '_'), '}'):
|
||||
if isap:
|
||||
n = isa.settings.predicate_number[isap]
|
||||
with fmt.indented('if isap.test({})'.format(n), '}'):
|
||||
fmt.line('return false;')
|
||||
emit_instp(instp, fmt)
|
||||
|
||||
# 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. Try to be stable relative to dict ordering.
|
||||
for ty in sorted(cpumode.type_legalize.keys(), key=str):
|
||||
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(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: ir::types::VOID, log2len: !0, '
|
||||
'offset: 0, legalize: {} }},',
|
||||
level1.legalize_code)
|
||||
continue
|
||||
|
||||
if level2.ty is not None:
|
||||
tyname = level2.ty.rust_name()
|
||||
else:
|
||||
tyname = 'ir::types::VOID'
|
||||
|
||||
lcode = cpumode.isa.legalize_code(level2.legalize)
|
||||
|
||||
# Empty level 2 table: Only a specialized legalization action, no
|
||||
# actual table.
|
||||
# Set an offset that is out of bounds, but make sure it doesn't
|
||||
# overflow its type when adding `1<<log2len`.
|
||||
if level2.is_empty():
|
||||
fmt.format(
|
||||
'Level1Entry {{ '
|
||||
'ty: {}, log2len: 0, offset: !0 - 1, '
|
||||
'legalize: {} }}, // {}',
|
||||
tyname, lcode, level2.legalize)
|
||||
continue
|
||||
|
||||
# Proper level 2 hash table.
|
||||
l2l = int(math.log(level2.hash_table_len, 2))
|
||||
assert l2l > 0, "Level2 hash table too small"
|
||||
fmt.format(
|
||||
'Level1Entry {{ '
|
||||
'ty: {}, log2len: {}, offset: {:#08x}, '
|
||||
'legalize: {} }}, // {}',
|
||||
tyname, l2l, level2.hash_table_offset,
|
||||
lcode, level2.legalize)
|
||||
|
||||
|
||||
def offset_type(length):
|
||||
# type: (int) -> str
|
||||
"""
|
||||
Compute an appropriate Rust integer type to use for offsets into a table of
|
||||
the given length.
|
||||
"""
|
||||
if length <= 0x10000:
|
||||
return 'u16'
|
||||
else:
|
||||
assert length <= 0x100000000, "Table too big"
|
||||
return 'u32'
|
||||
|
||||
|
||||
def emit_recipe_names(isa, fmt):
|
||||
# type: (TargetISA, srcgen.Formatter) -> None
|
||||
"""
|
||||
Emit a table of encoding recipe names keyed by recipe number.
|
||||
|
||||
This is used for pretty-printing encodings.
|
||||
"""
|
||||
with fmt.indented(
|
||||
'static RECIPE_NAMES: [&str; {}] = ['
|
||||
.format(len(isa.all_recipes)), '];'):
|
||||
for r in isa.all_recipes:
|
||||
fmt.line('"{}",'.format(r.name))
|
||||
|
||||
|
||||
def emit_recipe_constraints(isa, fmt):
|
||||
# type: (TargetISA, srcgen.Formatter) -> None
|
||||
"""
|
||||
Emit a table of encoding recipe operand constraints keyed by recipe number.
|
||||
|
||||
These are used by the register allocator to pick registers that can be
|
||||
properly encoded.
|
||||
"""
|
||||
with fmt.indented(
|
||||
'static RECIPE_CONSTRAINTS: [RecipeConstraints; {}] = ['
|
||||
.format(len(isa.all_recipes)), '];'):
|
||||
for r in isa.all_recipes:
|
||||
fmt.comment(r.name)
|
||||
tied_i2o, tied_o2i = r.ties()
|
||||
with fmt.indented('RecipeConstraints {', '},'):
|
||||
emit_operand_constraints(r, r.ins, 'ins', tied_i2o, fmt)
|
||||
emit_operand_constraints(r, r.outs, 'outs', tied_o2i, fmt)
|
||||
fmt.format(
|
||||
'fixed_ins: {},',
|
||||
str(any(isinstance(c, Register)
|
||||
for c in r.ins)).lower())
|
||||
fmt.format(
|
||||
'fixed_outs: {},',
|
||||
str(any(isinstance(c, Register)
|
||||
for c in r.outs)).lower())
|
||||
fmt.format('tied_ops: {},', str(bool(tied_i2o)).lower())
|
||||
|
||||
|
||||
def emit_operand_constraints(
|
||||
recipe, # type: EncRecipe
|
||||
seq, # type: Sequence[OperandConstraint]
|
||||
field, # type: str
|
||||
tied, # type: Dict[int, int]
|
||||
fmt # type: srcgen.Formatter
|
||||
):
|
||||
# type: (...) -> None
|
||||
"""
|
||||
Emit a struct field initializer for an array of operand constraints.
|
||||
"""
|
||||
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: {},', cons)
|
||||
elif isinstance(cons, Register):
|
||||
assert n not in tied, "Can't tie fixed register operand"
|
||||
fmt.format(
|
||||
'kind: ConstraintKind::FixedReg({}),', cons.unit)
|
||||
fmt.format('regclass: {},', 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: {},', 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: {},', 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(r.name)
|
||||
with fmt.indented('RecipeSizing {', '},'):
|
||||
fmt.format('bytes: {},', r.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)
|
||||
@@ -1,675 +0,0 @@
|
||||
"""
|
||||
Generate sources with instruction info.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
import srcgen
|
||||
import constant_hash
|
||||
from unique_table import UniqueTable, UniqueSeqTable
|
||||
from cdsl import camel_case
|
||||
from cdsl.operands import ImmediateKind
|
||||
from cdsl.formats import InstructionFormat
|
||||
from cdsl.instructions import Instruction
|
||||
|
||||
# The typing module is only required by mypy, and we don't use these imports
|
||||
# outside type comments.
|
||||
try:
|
||||
from typing import List, Sequence, Set, TYPE_CHECKING # noqa
|
||||
if TYPE_CHECKING:
|
||||
from cdsl.isa import TargetISA # noqa
|
||||
from cdsl.instructions import InstructionGroup # noqa
|
||||
from cdsl.operands import Operand # noqa
|
||||
from cdsl.typevar import TypeVar # noqa
|
||||
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def gen_formats(fmt):
|
||||
# type: (srcgen.Formatter) -> None
|
||||
"""Generate an instruction format enumeration"""
|
||||
|
||||
fmt.doc_comment('An instruction format')
|
||||
fmt.doc_comment('')
|
||||
fmt.doc_comment('Every opcode has a corresponding instruction format')
|
||||
fmt.doc_comment('which is represented by both the `InstructionFormat`')
|
||||
fmt.doc_comment('and the `InstructionData` enums.')
|
||||
fmt.line('#[derive(Copy, Clone, PartialEq, Eq, Debug)]')
|
||||
with fmt.indented('pub enum InstructionFormat {', '}'):
|
||||
for f in InstructionFormat.all_formats:
|
||||
fmt.doc_comment(str(f))
|
||||
fmt.line(f.name + ',')
|
||||
fmt.line()
|
||||
|
||||
# Emit a From<InstructionData> which also serves to verify that
|
||||
# InstructionFormat and InstructionData are in sync.
|
||||
with fmt.indented(
|
||||
"impl<'a> From<&'a InstructionData> for InstructionFormat {", '}'):
|
||||
with fmt.indented(
|
||||
"fn from(inst: &'a InstructionData) -> InstructionFormat {",
|
||||
'}'):
|
||||
with fmt.indented('match *inst {', '}'):
|
||||
for f in InstructionFormat.all_formats:
|
||||
fmt.line(('InstructionData::{} {{ .. }} => ' +
|
||||
'InstructionFormat::{},')
|
||||
.format(f.name, f.name))
|
||||
fmt.line()
|
||||
|
||||
|
||||
def gen_arguments_method(fmt, is_mut):
|
||||
# type: (srcgen.Formatter, bool) -> None
|
||||
method = 'arguments'
|
||||
mut = ''
|
||||
rslice = 'ref_slice'
|
||||
as_slice = 'as_slice'
|
||||
if is_mut:
|
||||
method += '_mut'
|
||||
mut = 'mut '
|
||||
rslice += '_mut'
|
||||
as_slice = 'as_mut_slice'
|
||||
|
||||
with fmt.indented(
|
||||
'pub fn {f}<\'a>(&\'a {m}self, pool: &\'a {m}ValueListPool) -> '
|
||||
'&{m}[Value] {{'
|
||||
.format(f=method, m=mut), '}'):
|
||||
with fmt.indented('match *self {', '}'):
|
||||
for f in InstructionFormat.all_formats:
|
||||
n = 'InstructionData::' + f.name
|
||||
|
||||
# Formats with a value list put all of their arguments in the
|
||||
# list. We don't split them up, just return it all as variable
|
||||
# arguments. (I expect the distinction to go away).
|
||||
if f.has_value_list:
|
||||
arg = ''.format(mut)
|
||||
fmt.line(
|
||||
'{} {{ ref {}args, .. }} => args.{}(pool),'
|
||||
.format(n, mut, as_slice))
|
||||
continue
|
||||
|
||||
# Fixed args.
|
||||
if f.num_value_operands == 0:
|
||||
arg = '&{}[]'.format(mut)
|
||||
capture = ''
|
||||
elif f.num_value_operands == 1:
|
||||
capture = 'ref {}arg, '.format(mut)
|
||||
arg = '{}(arg)'.format(rslice)
|
||||
else:
|
||||
capture = 'ref {}args, '.format(mut)
|
||||
arg = 'args'
|
||||
fmt.line(
|
||||
'{} {{ {} .. }} => {},'
|
||||
.format(n, capture, arg))
|
||||
|
||||
|
||||
def gen_instruction_data_impl(fmt):
|
||||
# type: (srcgen.Formatter) -> None
|
||||
"""
|
||||
Generate the boring parts of the InstructionData implementation.
|
||||
|
||||
These methods in `impl InstructionData` can be generated automatically from
|
||||
the instruction formats:
|
||||
|
||||
- `pub fn opcode(&self) -> Opcode`
|
||||
- `pub fn arguments(&self, &pool) -> &[Value]`
|
||||
- `pub fn arguments_mut(&mut self, &pool) -> &mut [Value]`
|
||||
- `pub fn take_value_list(&mut self) -> Option<ValueList>`
|
||||
- `pub fn put_value_list(&mut self, args: ValueList>`
|
||||
"""
|
||||
|
||||
# The `opcode` method simply reads the `opcode` members. This is really a
|
||||
# workaround for Rust's enum types missing shared members.
|
||||
with fmt.indented('impl InstructionData {', '}'):
|
||||
fmt.doc_comment('Get the opcode of this instruction.')
|
||||
with fmt.indented('pub fn opcode(&self) -> Opcode {', '}'):
|
||||
with fmt.indented('match *self {', '}'):
|
||||
for f in InstructionFormat.all_formats:
|
||||
fmt.line(
|
||||
'InstructionData::{} {{ opcode, .. }} => opcode,'
|
||||
.format(f.name))
|
||||
|
||||
fmt.doc_comment('Get the controlling type variable operand.')
|
||||
with fmt.indented(
|
||||
'pub fn typevar_operand(&self, pool: &ValueListPool) -> '
|
||||
'Option<Value> {', '}'):
|
||||
with fmt.indented('match *self {', '}'):
|
||||
for f in InstructionFormat.all_formats:
|
||||
n = 'InstructionData::' + f.name
|
||||
if f.typevar_operand is None:
|
||||
fmt.line(n + ' { .. } => None,')
|
||||
elif f.has_value_list:
|
||||
# We keep all arguments in a value list.
|
||||
i = f.typevar_operand
|
||||
fmt.line(
|
||||
'{} {{ ref args, .. }} => '
|
||||
'args.get({}, pool),'.format(n, i))
|
||||
elif f.num_value_operands == 1:
|
||||
# We have a single value operand called 'arg'.
|
||||
fmt.line(n + ' { arg, .. } => Some(arg),')
|
||||
else:
|
||||
# We have multiple value operands and an array `args`.
|
||||
# Which `args` index to use?
|
||||
i = f.typevar_operand
|
||||
fmt.line(
|
||||
n +
|
||||
' {{ ref args, .. }} => Some(args[{}]),'
|
||||
.format(i))
|
||||
|
||||
fmt.doc_comment(
|
||||
"""
|
||||
Get the value arguments to this instruction.
|
||||
""")
|
||||
gen_arguments_method(fmt, False)
|
||||
fmt.doc_comment(
|
||||
"""
|
||||
Get mutable references to the value arguments to this
|
||||
instruction.
|
||||
""")
|
||||
gen_arguments_method(fmt, True)
|
||||
|
||||
fmt.doc_comment(
|
||||
"""
|
||||
Take out the value list with all the value arguments and return
|
||||
it.
|
||||
|
||||
This leaves the value list in the instruction empty. Use
|
||||
`put_value_list` to put the value list back.
|
||||
""")
|
||||
with fmt.indented(
|
||||
'pub fn take_value_list(&mut self) -> Option<ValueList> {',
|
||||
'}'):
|
||||
with fmt.indented('match *self {', '}'):
|
||||
for f in InstructionFormat.all_formats:
|
||||
n = 'InstructionData::' + f.name
|
||||
if f.has_value_list:
|
||||
fmt.line(
|
||||
n + ' { ref mut args, .. } => Some(args.take()),')
|
||||
fmt.line('_ => None,')
|
||||
|
||||
fmt.doc_comment(
|
||||
"""
|
||||
Put back a value list.
|
||||
|
||||
After removing a value list with `take_value_list()`, use this
|
||||
method to put it back. It is required that this instruction has
|
||||
a format that accepts a value list, and that the existing value
|
||||
list is empty. This avoids leaking list pool memory.
|
||||
""")
|
||||
with fmt.indented(
|
||||
'pub fn put_value_list(&mut self, vlist: ValueList) {', '}'):
|
||||
with fmt.indented('let args = match *self {', '};'):
|
||||
for f in InstructionFormat.all_formats:
|
||||
n = 'InstructionData::' + f.name
|
||||
if f.has_value_list:
|
||||
fmt.line(n + ' { ref mut args, .. } => args,')
|
||||
fmt.line('_ => panic!("No value list: {:?}", self),')
|
||||
fmt.line('assert!(args.is_empty(), "Value list already in use");')
|
||||
fmt.line('*args = vlist;')
|
||||
|
||||
|
||||
def collect_instr_groups(isas):
|
||||
# type: (Sequence[TargetISA]) -> List[InstructionGroup]
|
||||
seen = set() # type: Set[InstructionGroup]
|
||||
groups = []
|
||||
for isa in isas:
|
||||
for g in isa.instruction_groups:
|
||||
if g not in seen:
|
||||
groups.append(g)
|
||||
seen.add(g)
|
||||
return groups
|
||||
|
||||
|
||||
def gen_opcodes(groups, fmt):
|
||||
# type: (Sequence[InstructionGroup], srcgen.Formatter) -> Sequence[Instruction] # noqa
|
||||
"""
|
||||
Generate opcode enumerations.
|
||||
|
||||
Return a list of all instructions.
|
||||
"""
|
||||
|
||||
fmt.doc_comment('An instruction opcode.')
|
||||
fmt.doc_comment('')
|
||||
fmt.doc_comment('All instructions from all supported ISAs are present.')
|
||||
fmt.line('#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]')
|
||||
instrs = []
|
||||
|
||||
# We explicitly set the discriminant of the first variant to 1, which
|
||||
# allows us to take advantage of the NonZero optimization, meaning that
|
||||
# wrapping enums can use the 0 discriminant instead of increasing the size
|
||||
# if the whole type, and so SIZEOF(Option<Opcode>>) == SIZEOF(Opcode)
|
||||
is_first_opcode = True
|
||||
with fmt.indented('pub enum Opcode {', '}'):
|
||||
for g in groups:
|
||||
for i in g.instructions:
|
||||
instrs.append(i)
|
||||
i.number = len(instrs)
|
||||
fmt.doc_comment('`{}`. ({})'.format(i, i.format.name))
|
||||
# Document polymorphism.
|
||||
if i.is_polymorphic:
|
||||
if i.use_typevar_operand:
|
||||
opnum = i.value_opnums[i.format.typevar_operand]
|
||||
fmt.doc_comment(
|
||||
'Type inferred from {}.'
|
||||
.format(i.ins[opnum]))
|
||||
# Enum variant itself.
|
||||
if is_first_opcode:
|
||||
fmt.line(i.camel_name + ' = 1,')
|
||||
is_first_opcode = False
|
||||
else:
|
||||
fmt.line(i.camel_name + ',')
|
||||
fmt.line()
|
||||
|
||||
with fmt.indented('impl Opcode {', '}'):
|
||||
for attr in sorted(Instruction.ATTRIBS.keys()):
|
||||
fmt.doc_comment(Instruction.ATTRIBS[attr])
|
||||
with fmt.indented('pub fn {}(self) -> bool {{'
|
||||
.format(attr), '}'):
|
||||
with fmt.indented('match self {', '}'):
|
||||
for i in instrs:
|
||||
if getattr(i, attr):
|
||||
fmt.format(
|
||||
'Opcode::{} => true,',
|
||||
i.camel_name, i.name)
|
||||
|
||||
fmt.line('_ => false')
|
||||
|
||||
# Generate a private opcode_format table.
|
||||
with fmt.indented(
|
||||
'const OPCODE_FORMAT: [InstructionFormat; {}] = ['
|
||||
.format(len(instrs)),
|
||||
'];'):
|
||||
for i in instrs:
|
||||
fmt.format(
|
||||
'InstructionFormat::{}, // {}',
|
||||
i.format.name, i.name)
|
||||
fmt.line()
|
||||
|
||||
# Generate a private opcode_name function.
|
||||
with fmt.indented('fn opcode_name(opc: Opcode) -> &\'static str {', '}'):
|
||||
with fmt.indented('match opc {', '}'):
|
||||
for i in instrs:
|
||||
fmt.format('Opcode::{} => "{}",', i.camel_name, i.name)
|
||||
fmt.line()
|
||||
|
||||
# Generate an opcode hash table for looking up opcodes by name.
|
||||
hash_table = constant_hash.compute_quadratic(
|
||||
instrs,
|
||||
lambda i: constant_hash.simple_hash(i.name))
|
||||
with fmt.indented(
|
||||
'const OPCODE_HASH_TABLE: [Option<Opcode>; {}] = ['
|
||||
.format(len(hash_table)), '];'):
|
||||
for i in hash_table:
|
||||
if i is None:
|
||||
fmt.line('None,')
|
||||
else:
|
||||
fmt.format('Some(Opcode::{}),', i.camel_name)
|
||||
fmt.line()
|
||||
return instrs
|
||||
|
||||
|
||||
def get_constraint(op, ctrl_typevar, type_sets):
|
||||
# type: (Operand, TypeVar, UniqueTable) -> str
|
||||
"""
|
||||
Get the value type constraint for an SSA value operand, where
|
||||
`ctrl_typevar` is the controlling type variable.
|
||||
|
||||
Each operand constraint is represented as a string, one of:
|
||||
|
||||
- `Concrete(vt)`, where `vt` is a value type name.
|
||||
- `Free(idx)` where `idx` is an index into `type_sets`.
|
||||
- `Same`, `Lane`, `AsBool` for controlling typevar-derived constraints.
|
||||
"""
|
||||
assert op.is_value()
|
||||
tv = op.typevar
|
||||
|
||||
# A concrete value type.
|
||||
if tv.singleton_type():
|
||||
return 'Concrete({})'.format(tv.singleton_type().rust_name())
|
||||
|
||||
if tv.free_typevar() is not ctrl_typevar:
|
||||
assert not tv.is_derived
|
||||
return 'Free({})'.format(type_sets.add(tv.type_set))
|
||||
|
||||
if tv.is_derived:
|
||||
assert tv.base is ctrl_typevar, "Not derived from ctrl_typevar"
|
||||
return camel_case(tv.derived_func)
|
||||
|
||||
assert tv is ctrl_typevar
|
||||
return 'Same'
|
||||
|
||||
|
||||
# TypeSet indexes are encoded in 8 bits, with `0xff` reserved.
|
||||
typeset_limit = 0xff
|
||||
|
||||
|
||||
def gen_typesets_table(fmt, type_sets):
|
||||
# type: (srcgen.Formatter, UniqueTable) -> None
|
||||
"""
|
||||
Generate the table of ValueTypeSets described by type_sets.
|
||||
"""
|
||||
if len(type_sets.table) == 0:
|
||||
return
|
||||
fmt.comment('Table of value type sets.')
|
||||
assert len(type_sets.table) <= typeset_limit, "Too many type sets"
|
||||
with fmt.indented(
|
||||
'const TYPE_SETS : [ir::instructions::ValueTypeSet; {}] = ['
|
||||
.format(len(type_sets.table)), '];'):
|
||||
for ts in type_sets.table:
|
||||
with fmt.indented('ir::instructions::ValueTypeSet {', '},'):
|
||||
ts.emit_fields(fmt)
|
||||
|
||||
|
||||
def gen_type_constraints(fmt, instrs):
|
||||
# type: (srcgen.Formatter, Sequence[Instruction]) -> None
|
||||
"""
|
||||
Generate value type constraints for all instructions.
|
||||
|
||||
- Emit a compact constant table of ValueTypeSet objects.
|
||||
- Emit a compact constant table of OperandConstraint objects.
|
||||
- Emit an opcode-indexed table of instruction constraints.
|
||||
|
||||
"""
|
||||
|
||||
# Table of TypeSet instances.
|
||||
type_sets = UniqueTable()
|
||||
|
||||
# Table of operand constraint sequences (as tuples). Each operand
|
||||
# constraint is represented as a string, one of:
|
||||
# - `Concrete(vt)`, where `vt` is a value type name.
|
||||
# - `Free(idx)` where `idx` isan index into `type_sets`.
|
||||
# - `Same`, `Lane`, `AsBool` for controlling typevar-derived constraints.
|
||||
operand_seqs = UniqueSeqTable()
|
||||
|
||||
# Preload table with constraints for typical binops.
|
||||
operand_seqs.add(['Same'] * 3)
|
||||
|
||||
fmt.comment('Table of opcode constraints.')
|
||||
with fmt.indented(
|
||||
'const OPCODE_CONSTRAINTS : [OpcodeConstraints; {}] = ['
|
||||
.format(len(instrs)), '];'):
|
||||
for i in instrs:
|
||||
# Collect constraints for the value results, not including
|
||||
# `variable_args` results which are always special cased.
|
||||
constraints = list()
|
||||
ctrl_typevar = None
|
||||
ctrl_typeset = typeset_limit
|
||||
if i.is_polymorphic:
|
||||
ctrl_typevar = i.ctrl_typevar
|
||||
ctrl_typeset = type_sets.add(ctrl_typevar.type_set)
|
||||
for idx in i.value_results:
|
||||
constraints.append(
|
||||
get_constraint(i.outs[idx], ctrl_typevar, type_sets))
|
||||
for opnum in i.value_opnums:
|
||||
constraints.append(
|
||||
get_constraint(i.ins[opnum], ctrl_typevar, type_sets))
|
||||
offset = operand_seqs.add(constraints)
|
||||
fixed_results = len(i.value_results)
|
||||
fixed_values = len(i.value_opnums)
|
||||
# Can the controlling type variable be inferred from the designated
|
||||
# operand?
|
||||
use_typevar_operand = i.is_polymorphic and i.use_typevar_operand
|
||||
# Can the controlling type variable be inferred from the result?
|
||||
use_result = (fixed_results > 0 and
|
||||
i.outs[i.value_results[0]].typevar == ctrl_typevar)
|
||||
# Are we required to use the designated operand instead of the
|
||||
# result?
|
||||
requires_typevar_operand = use_typevar_operand and not use_result
|
||||
fmt.comment(
|
||||
'{}: fixed_results={}, use_typevar_operand={}, '
|
||||
'requires_typevar_operand={}, fixed_values={}'
|
||||
.format(i.camel_name, fixed_results, use_typevar_operand,
|
||||
requires_typevar_operand, fixed_values))
|
||||
fmt.comment('Constraints={}'.format(constraints))
|
||||
if i.is_polymorphic:
|
||||
fmt.comment(
|
||||
'Polymorphic over {}'.format(ctrl_typevar.type_set))
|
||||
# Compute the bit field encoding, c.f. instructions.rs.
|
||||
assert fixed_results < 8, "Bit field encoding too tight"
|
||||
flags = fixed_results
|
||||
if use_typevar_operand:
|
||||
flags |= 8
|
||||
if requires_typevar_operand:
|
||||
flags |= 0x10
|
||||
assert fixed_values < 8, "Bit field encoding too tight"
|
||||
flags |= fixed_values << 5
|
||||
|
||||
with fmt.indented('OpcodeConstraints {', '},'):
|
||||
fmt.line('flags: {:#04x},'.format(flags))
|
||||
fmt.line('typeset_offset: {},'.format(ctrl_typeset))
|
||||
fmt.line('constraint_offset: {},'.format(offset))
|
||||
|
||||
gen_typesets_table(fmt, type_sets)
|
||||
|
||||
fmt.comment('Table of operand constraint sequences.')
|
||||
with fmt.indented(
|
||||
'const OPERAND_CONSTRAINTS : [OperandConstraint; {}] = ['
|
||||
.format(len(operand_seqs.table)), '];'):
|
||||
for c in operand_seqs.table:
|
||||
fmt.line('OperandConstraint::{},'.format(c))
|
||||
|
||||
|
||||
def gen_format_constructor(iform, fmt):
|
||||
# type: (InstructionFormat, srcgen.Formatter) -> None
|
||||
"""
|
||||
Emit a method for creating and inserting inserting an `iform` instruction,
|
||||
where `iform` is an instruction format.
|
||||
|
||||
All instruction formats take an `opcode` argument and a `ctrl_typevar`
|
||||
argument for deducing the result types.
|
||||
"""
|
||||
|
||||
# Construct method arguments.
|
||||
args = ['self', 'opcode: Opcode', 'ctrl_typevar: Type']
|
||||
|
||||
# Normal operand arguments. Start with the immediate operands.
|
||||
for f in iform.imm_fields:
|
||||
args.append('{}: {}'.format(f.member, f.kind.rust_type))
|
||||
# Then the value operands.
|
||||
if iform.has_value_list:
|
||||
# Take all value arguments as a finished value list. The value lists
|
||||
# are created by the individual instruction constructors.
|
||||
args.append('args: ValueList')
|
||||
else:
|
||||
# Take a fixed number of value operands.
|
||||
for i in range(iform.num_value_operands):
|
||||
args.append('arg{}: Value'.format(i))
|
||||
|
||||
proto = '{}({})'.format(iform.name, ', '.join(args))
|
||||
proto += " -> (Inst, &'f mut DataFlowGraph)"
|
||||
|
||||
fmt.doc_comment(str(iform))
|
||||
fmt.line('#[allow(non_snake_case)]')
|
||||
with fmt.indented('fn {} {{'.format(proto), '}'):
|
||||
# Generate the instruction data.
|
||||
with fmt.indented(
|
||||
'let data = InstructionData::{} {{'.format(iform.name), '};'):
|
||||
fmt.line('opcode,')
|
||||
gen_member_inits(iform, fmt)
|
||||
|
||||
fmt.line('self.build(data, ctrl_typevar)')
|
||||
|
||||
|
||||
def gen_member_inits(iform, fmt):
|
||||
# type: (InstructionFormat, srcgen.Formatter) -> None
|
||||
"""
|
||||
Emit member initializers for an `iform` instruction.
|
||||
"""
|
||||
|
||||
# Immediate operands.
|
||||
# We have local variables with the same names as the members.
|
||||
for f in iform.imm_fields:
|
||||
fmt.line('{}: {},'.format(f.member, f.member))
|
||||
|
||||
# Value operands.
|
||||
if iform.has_value_list:
|
||||
fmt.line('args,')
|
||||
elif iform.num_value_operands == 1:
|
||||
fmt.line('arg: arg0,')
|
||||
elif iform.num_value_operands > 1:
|
||||
args = ('arg{}'.format(i) for i in range(iform.num_value_operands))
|
||||
fmt.line('args: [{}],'.format(', '.join(args)))
|
||||
|
||||
|
||||
def gen_inst_builder(inst, fmt):
|
||||
# type: (Instruction, srcgen.Formatter) -> None
|
||||
"""
|
||||
Emit a method for generating the instruction `inst`.
|
||||
|
||||
The method will create and insert an instruction, then return the result
|
||||
values, or the instruction reference itself for instructions that don't
|
||||
have results.
|
||||
"""
|
||||
|
||||
# Construct method arguments.
|
||||
if inst.format.has_value_list:
|
||||
args = ['mut self']
|
||||
else:
|
||||
args = ['self']
|
||||
|
||||
# The controlling type variable will be inferred from the input values if
|
||||
# possible. Otherwise, it is the first method argument.
|
||||
if inst.is_polymorphic and not inst.use_typevar_operand:
|
||||
args.append('{}: Type'.format(inst.ctrl_typevar.name))
|
||||
|
||||
tmpl_types = list() # type: List[str]
|
||||
into_args = list() # type: List[str]
|
||||
for op in inst.ins:
|
||||
if isinstance(op.kind, ImmediateKind):
|
||||
t = 'T{}{}'.format(1 + len(tmpl_types), op.kind.name)
|
||||
tmpl_types.append('{}: Into<{}>'.format(t, op.kind.rust_type))
|
||||
into_args.append(op.name)
|
||||
else:
|
||||
t = op.kind.rust_type
|
||||
args.append('{}: {}'.format(op.name, t))
|
||||
|
||||
# Return the inst reference for result-less instructions.
|
||||
if len(inst.value_results) == 0:
|
||||
rtype = 'Inst'
|
||||
elif len(inst.value_results) == 1:
|
||||
rtype = 'Value'
|
||||
else:
|
||||
rvals = ', '.join(len(inst.value_results) * ['Value'])
|
||||
rtype = '({})'.format(rvals)
|
||||
|
||||
if len(tmpl_types) > 0:
|
||||
tmpl = '<{}>'.format(', '.join(tmpl_types))
|
||||
else:
|
||||
tmpl = ''
|
||||
proto = '{}{}({}) -> {}'.format(
|
||||
inst.snake_name(), tmpl, ', '.join(args), rtype)
|
||||
|
||||
fmt.doc_comment('`{}`\n\n{}'.format(inst, inst.blurb()))
|
||||
fmt.line('#[allow(non_snake_case)]')
|
||||
with fmt.indented('fn {} {{'.format(proto), '}'):
|
||||
# Convert all of the `Into<>` arguments.
|
||||
for arg in into_args:
|
||||
fmt.line('let {} = {}.into();'.format(arg, arg))
|
||||
|
||||
# Arguments for instruction constructor.
|
||||
args = ['Opcode::' + inst.camel_name]
|
||||
|
||||
if inst.is_polymorphic and not inst.use_typevar_operand:
|
||||
# This was an explicit method argument.
|
||||
args.append(inst.ctrl_typevar.name)
|
||||
elif len(inst.value_results) == 0 or not inst.is_polymorphic:
|
||||
# No controlling type variable needed.
|
||||
args.append('types::VOID')
|
||||
else:
|
||||
assert inst.is_polymorphic and inst.use_typevar_operand
|
||||
# Infer the controlling type variable from the input operands.
|
||||
opnum = inst.value_opnums[inst.format.typevar_operand]
|
||||
fmt.line(
|
||||
'let ctrl_typevar = self.data_flow_graph().value_type({});'
|
||||
.format(inst.ins[opnum].name))
|
||||
# The format constructor will resolve the result types from the
|
||||
# type var.
|
||||
args.append('ctrl_typevar')
|
||||
|
||||
# Now add all of the immediate operands to the constructor arguments.
|
||||
for opnum in inst.imm_opnums:
|
||||
args.append(inst.ins[opnum].name)
|
||||
|
||||
# Finally, the value operands.
|
||||
if inst.format.has_value_list:
|
||||
# We need to build a value list with all the arguments.
|
||||
fmt.line('let mut vlist = ValueList::default();')
|
||||
args.append('vlist')
|
||||
with fmt.indented('{', '}'):
|
||||
fmt.line(
|
||||
'let pool = '
|
||||
'&mut self.data_flow_graph_mut().value_lists;')
|
||||
for op in inst.ins:
|
||||
if op.is_value():
|
||||
fmt.line('vlist.push({}, pool);'.format(op.name))
|
||||
elif op.is_varargs():
|
||||
fmt.line(
|
||||
'vlist.extend({}.iter().cloned(), pool);'
|
||||
.format(op.name))
|
||||
else:
|
||||
# With no value list, we're guaranteed to just have a set of fixed
|
||||
# value operands.
|
||||
for opnum in inst.value_opnums:
|
||||
args.append(inst.ins[opnum].name)
|
||||
|
||||
# Call to the format constructor,
|
||||
fcall = 'self.{}({})'.format(inst.format.name, ', '.join(args))
|
||||
|
||||
if len(inst.value_results) == 0:
|
||||
fmt.line(fcall + '.0')
|
||||
return
|
||||
|
||||
fmt.line('let (inst, dfg) = {};'.format(fcall))
|
||||
|
||||
if len(inst.value_results) == 1:
|
||||
fmt.line('dfg.first_result(inst)')
|
||||
return
|
||||
|
||||
fmt.format(
|
||||
'let results = &dfg.inst_results(inst)[0..{}];',
|
||||
len(inst.value_results))
|
||||
fmt.format('({})', ', '.join(
|
||||
'results[{}]'.format(i) for i in range(len(inst.value_results))))
|
||||
|
||||
|
||||
def gen_builder(insts, fmt):
|
||||
# type: (Sequence[Instruction], srcgen.Formatter) -> None
|
||||
"""
|
||||
Generate a Builder trait with methods for all instructions.
|
||||
"""
|
||||
fmt.doc_comment("""
|
||||
Convenience methods for building instructions.
|
||||
|
||||
The `InstrBuilder` trait has one method per instruction opcode for
|
||||
conveniently constructing the instruction with minimum arguments.
|
||||
Polymorphic instructions infer their result types from the input
|
||||
arguments when possible. In some cases, an explicit `ctrl_typevar`
|
||||
argument is required.
|
||||
|
||||
The opcode methods return the new instruction's result values, or
|
||||
the `Inst` itself for instructions that don't have any results.
|
||||
|
||||
There is also a method per instruction format. These methods all
|
||||
return an `Inst`.
|
||||
""")
|
||||
with fmt.indented(
|
||||
"pub trait InstBuilder<'f>: InstBuilderBase<'f> {", '}'):
|
||||
for inst in insts:
|
||||
gen_inst_builder(inst, fmt)
|
||||
for f in InstructionFormat.all_formats:
|
||||
gen_format_constructor(f, fmt)
|
||||
|
||||
|
||||
def generate(isas, out_dir):
|
||||
# type: (Sequence[TargetISA], str) -> None
|
||||
groups = collect_instr_groups(isas)
|
||||
|
||||
# opcodes.rs
|
||||
fmt = srcgen.Formatter()
|
||||
gen_formats(fmt)
|
||||
gen_instruction_data_impl(fmt)
|
||||
instrs = gen_opcodes(groups, fmt)
|
||||
gen_type_constraints(fmt, instrs)
|
||||
fmt.update_file('opcodes.rs', out_dir)
|
||||
|
||||
# builder.rs
|
||||
fmt = srcgen.Formatter()
|
||||
gen_builder(instrs, fmt)
|
||||
fmt.update_file('builder.rs', out_dir)
|
||||
@@ -1,427 +0,0 @@
|
||||
"""
|
||||
Generate legalizer transformations.
|
||||
|
||||
The transformations defined in the `cretonne.legalize` module are all of the
|
||||
macro-expansion form where the input pattern is a single instruction. We
|
||||
generate a Rust function for each `XFormGroup` which takes a `Cursor` pointing
|
||||
at the instruction to be legalized. The expanded destination pattern replaces
|
||||
the input instruction.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from srcgen import Formatter
|
||||
from collections import defaultdict
|
||||
from base import instructions
|
||||
from cdsl.ast import Var
|
||||
from cdsl.ti import ti_rtl, TypeEnv, get_type_env, TypesEqual,\
|
||||
InTypeset, WiderOrEq
|
||||
from unique_table import UniqueTable
|
||||
from gen_instr import gen_typesets_table
|
||||
from cdsl.typevar import TypeVar
|
||||
|
||||
try:
|
||||
from typing import Sequence, List, Dict, Set, DefaultDict # noqa
|
||||
from cdsl.isa import TargetISA # noqa
|
||||
from cdsl.ast import Def # noqa
|
||||
from cdsl.xform import XForm, XFormGroup # noqa
|
||||
from cdsl.typevar import TypeSet # noqa
|
||||
from cdsl.ti import TypeConstraint # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def get_runtime_typechecks(xform):
|
||||
# type: (XForm) -> List[TypeConstraint]
|
||||
"""
|
||||
Given a XForm build a list of runtime type checks neccessary to determine
|
||||
if it applies. We have 2 types of runtime checks:
|
||||
1) typevar tv belongs to typeset T - needed for free tvs whose
|
||||
typeset is constrainted by their use in the dst pattern
|
||||
|
||||
2) tv1 == tv2 where tv1 and tv2 are derived TVs - caused by unification
|
||||
of non-bijective functions
|
||||
"""
|
||||
check_l = [] # type: List[TypeConstraint]
|
||||
|
||||
# 1) Perform ti only on the source RTL. Accumulate any free tvs that have a
|
||||
# different inferred type in src, compared to the type inferred for both
|
||||
# src and dst.
|
||||
symtab = {} # type: Dict[Var, Var]
|
||||
src_copy = xform.src.copy(symtab)
|
||||
src_typenv = get_type_env(ti_rtl(src_copy, TypeEnv()))
|
||||
|
||||
for v in xform.ti.vars:
|
||||
if not v.has_free_typevar():
|
||||
continue
|
||||
|
||||
# In rust the local variable containing a free TV associated with var v
|
||||
# has name typeof_v. We rely on the python TVs having the same name.
|
||||
assert "typeof_{}".format(v) == xform.ti[v].name
|
||||
|
||||
if v not in symtab:
|
||||
# We can have singleton vars defined only on dst. Ignore them
|
||||
assert v.get_typevar().singleton_type() is not None
|
||||
continue
|
||||
|
||||
src_ts = src_typenv[symtab[v]].get_typeset()
|
||||
xform_ts = xform.ti[v].get_typeset()
|
||||
|
||||
assert xform_ts.issubset(src_ts)
|
||||
if src_ts != xform_ts:
|
||||
check_l.append(InTypeset(xform.ti[v], xform_ts))
|
||||
|
||||
# 2,3) Add any constraints that appear in xform.ti
|
||||
check_l.extend(xform.ti.constraints)
|
||||
|
||||
return check_l
|
||||
|
||||
|
||||
def emit_runtime_typecheck(check, fmt, type_sets):
|
||||
# type: (TypeConstraint, Formatter, UniqueTable) -> None
|
||||
"""
|
||||
Emit rust code for the given check.
|
||||
|
||||
The emitted code is a statement redefining the `predicate` variable like
|
||||
this:
|
||||
|
||||
let predicate = predicate && ...
|
||||
"""
|
||||
def build_derived_expr(tv):
|
||||
# type: (TypeVar) -> str
|
||||
"""
|
||||
Build an expression of type Option<Type> corresponding to a concrete
|
||||
type transformed by the sequence of derivation functions in tv.
|
||||
|
||||
We are using Option<Type>, as some constraints may cause an
|
||||
over/underflow on patterns that do not match them. We want to capture
|
||||
this without panicking at runtime.
|
||||
"""
|
||||
if not tv.is_derived:
|
||||
assert tv.name.startswith('typeof_')
|
||||
return "Some({})".format(tv.name)
|
||||
|
||||
base_exp = build_derived_expr(tv.base)
|
||||
if (tv.derived_func == TypeVar.LANEOF):
|
||||
return "{}.map(|t: Type| -> t.lane_type())".format(base_exp)
|
||||
elif (tv.derived_func == TypeVar.ASBOOL):
|
||||
return "{}.map(|t: Type| -> t.as_bool())".format(base_exp)
|
||||
elif (tv.derived_func == TypeVar.HALFWIDTH):
|
||||
return "{}.and_then(|t: Type| -> t.half_width())".format(base_exp)
|
||||
elif (tv.derived_func == TypeVar.DOUBLEWIDTH):
|
||||
return "{}.and_then(|t: Type| -> t.double_width())"\
|
||||
.format(base_exp)
|
||||
elif (tv.derived_func == TypeVar.HALFVECTOR):
|
||||
return "{}.and_then(|t: Type| -> t.half_vector())".format(base_exp)
|
||||
elif (tv.derived_func == TypeVar.DOUBLEVECTOR):
|
||||
return "{}.and_then(|t: Type| -> t.by(2))".format(base_exp)
|
||||
else:
|
||||
assert False, "Unknown derived function {}".format(tv.derived_func)
|
||||
|
||||
if (isinstance(check, InTypeset)):
|
||||
assert not check.tv.is_derived
|
||||
tv = check.tv.name
|
||||
if check.ts not in type_sets.index:
|
||||
type_sets.add(check.ts)
|
||||
ts = type_sets.index[check.ts]
|
||||
fmt.comment("{} must belong to {}".format(tv, check.ts))
|
||||
fmt.format(
|
||||
'let predicate = predicate && TYPE_SETS[{}].contains({});',
|
||||
ts, tv)
|
||||
elif (isinstance(check, TypesEqual)):
|
||||
with fmt.indented(
|
||||
'let predicate = predicate && match ({}, {}) {{'
|
||||
.format(build_derived_expr(check.tv1),
|
||||
build_derived_expr(check.tv2)), '};'):
|
||||
fmt.line('(Some(a), Some(b)) => a == b,')
|
||||
fmt.comment('On overflow, constraint doesn\'t appply')
|
||||
fmt.line('_ => false,')
|
||||
elif (isinstance(check, WiderOrEq)):
|
||||
with fmt.indented(
|
||||
'let predicate = predicate && match ({}, {}) {{'
|
||||
.format(build_derived_expr(check.tv1),
|
||||
build_derived_expr(check.tv2)), '};'):
|
||||
fmt.line('(Some(a), Some(b)) => a.wider_or_equal(b),')
|
||||
fmt.comment('On overflow, constraint doesn\'t appply')
|
||||
fmt.line('_ => false,')
|
||||
else:
|
||||
assert False, "Unknown check {}".format(check)
|
||||
|
||||
|
||||
def unwrap_inst(iref, node, fmt):
|
||||
# type: (str, Def, Formatter) -> bool
|
||||
"""
|
||||
Given a `Def` node, emit code that extracts all the instruction fields from
|
||||
`dfg[iref]`.
|
||||
|
||||
Create local variables named after the `Var` instances in `node`.
|
||||
|
||||
Also create a local variable named `predicate` with the value of the
|
||||
evaluated instruction predicate, or `true` if the node has no predicate.
|
||||
|
||||
:param iref: Name of the `Inst` reference to unwrap.
|
||||
:param node: `Def` node providing variable names.
|
||||
:returns: True if the instruction arguments were not detached, expecting a
|
||||
replacement instruction to overwrite the original.
|
||||
"""
|
||||
fmt.comment('Unwrap {}'.format(node))
|
||||
expr = node.expr
|
||||
iform = expr.inst.format
|
||||
nvops = iform.num_value_operands
|
||||
|
||||
# The tuple of locals we're extracting is `expr.args`.
|
||||
with fmt.indented(
|
||||
'let ({}, predicate) = if let ir::InstructionData::{} {{'
|
||||
.format(', '.join(map(str, expr.args)), iform.name), '};'):
|
||||
# Fields are encoded directly.
|
||||
for f in iform.imm_fields:
|
||||
fmt.line('{},'.format(f.member))
|
||||
if nvops == 1:
|
||||
fmt.line('arg,')
|
||||
elif iform.has_value_list or nvops > 1:
|
||||
fmt.line('ref args,')
|
||||
fmt.line('..')
|
||||
fmt.outdented_line('} = dfg[inst] {')
|
||||
if iform.has_value_list:
|
||||
fmt.line('let args = args.as_slice(&dfg.value_lists);')
|
||||
elif nvops == 1:
|
||||
fmt.line('let args = [arg];')
|
||||
# Generate the values for the tuple.
|
||||
with fmt.indented('(', ')'):
|
||||
for opnum, op in enumerate(expr.inst.ins):
|
||||
if op.is_immediate():
|
||||
n = expr.inst.imm_opnums.index(opnum)
|
||||
fmt.format('{},', iform.imm_fields[n].member)
|
||||
elif op.is_value():
|
||||
n = expr.inst.value_opnums.index(opnum)
|
||||
fmt.format('dfg.resolve_aliases(args[{}]),', n)
|
||||
# Evaluate the instruction predicate, if any.
|
||||
instp = expr.inst_predicate()
|
||||
fmt.line(instp.rust_predicate(0) if instp else 'true')
|
||||
fmt.outdented_line('} else {')
|
||||
fmt.line('unreachable!("bad instruction format")')
|
||||
|
||||
# Get the types of any variables where it is needed.
|
||||
for opnum in expr.inst.value_opnums:
|
||||
v = expr.args[opnum]
|
||||
if isinstance(v, Var) and v.has_free_typevar():
|
||||
fmt.line('let typeof_{0} = dfg.value_type({0});'.format(v))
|
||||
|
||||
# If the node has results, detach the values.
|
||||
# Place the values in locals.
|
||||
replace_inst = False
|
||||
if len(node.defs) > 0:
|
||||
if node.defs == node.defs[0].dst_def.defs:
|
||||
# Special case: The instruction replacing node defines the exact
|
||||
# same values.
|
||||
fmt.comment(
|
||||
'Results handled by {}.'
|
||||
.format(node.defs[0].dst_def))
|
||||
replace_inst = True
|
||||
else:
|
||||
# Boring case: Detach the result values, capture them in locals.
|
||||
for d in node.defs:
|
||||
fmt.line('let {};'.format(d))
|
||||
with fmt.indented('{', '}'):
|
||||
fmt.line('let r = dfg.inst_results(inst);')
|
||||
for i in range(len(node.defs)):
|
||||
fmt.line('{} = r[{}];'.format(node.defs[i], i))
|
||||
for d in node.defs:
|
||||
if d.has_free_typevar():
|
||||
fmt.line(
|
||||
'let typeof_{0} = dfg.value_type({0});'
|
||||
.format(d))
|
||||
|
||||
return replace_inst
|
||||
|
||||
|
||||
def wrap_tup(seq):
|
||||
# type: (Sequence[object]) -> str
|
||||
tup = tuple(map(str, seq))
|
||||
if len(tup) == 1:
|
||||
return tup[0]
|
||||
else:
|
||||
return '({})'.format(', '.join(tup))
|
||||
|
||||
|
||||
def is_value_split(node):
|
||||
# type: (Def) -> bool
|
||||
"""
|
||||
Determine if `node` represents one of the value splitting instructions:
|
||||
`isplit` or `vsplit. These instructions are lowered specially by the
|
||||
`legalize::split` module.
|
||||
"""
|
||||
if len(node.defs) != 2:
|
||||
return False
|
||||
return node.expr.inst in (instructions.isplit, instructions.vsplit)
|
||||
|
||||
|
||||
def emit_dst_inst(node, fmt):
|
||||
# type: (Def, Formatter) -> None
|
||||
replaced_inst = None # type: str
|
||||
|
||||
if is_value_split(node):
|
||||
# Split instructions are not emitted with the builder, but by calling
|
||||
# special functions in the `legalizer::split` module. These functions
|
||||
# will eliminate concat-split patterns.
|
||||
fmt.line(
|
||||
'let {} = split::{}(dfg, cfg, pos, {});'
|
||||
.format(
|
||||
wrap_tup(node.defs),
|
||||
node.expr.inst.snake_name(),
|
||||
node.expr.args[0]))
|
||||
else:
|
||||
if len(node.defs) == 0:
|
||||
# This node doesn't define any values, so just insert the new
|
||||
# instruction.
|
||||
builder = 'dfg.ins(pos)'
|
||||
else:
|
||||
src_def0 = node.defs[0].src_def
|
||||
if src_def0 and node.defs == src_def0.defs:
|
||||
# The replacement instruction defines the exact same values as
|
||||
# the source pattern. Unwrapping would have left the results
|
||||
# intact.
|
||||
# Replace the whole instruction.
|
||||
builder = 'let {} = dfg.replace(inst)'.format(
|
||||
wrap_tup(node.defs))
|
||||
replaced_inst = 'inst'
|
||||
else:
|
||||
# Insert a new instruction.
|
||||
builder = 'let {} = dfg.ins(pos)'.format(wrap_tup(node.defs))
|
||||
# We may want to reuse some of the detached output values.
|
||||
if len(node.defs) == 1 and node.defs[0].is_output():
|
||||
# Reuse the single source result value.
|
||||
builder += '.with_result({})'.format(node.defs[0])
|
||||
elif any(d.is_output() for d in node.defs):
|
||||
# We have some output values to be reused.
|
||||
array = ', '.join(
|
||||
('Some({})'.format(d) if d.is_output()
|
||||
else 'None')
|
||||
for d in node.defs)
|
||||
builder += '.with_results([{}])'.format(array)
|
||||
|
||||
fmt.line('{}.{};'.format(builder, node.expr.rust_builder(node.defs)))
|
||||
|
||||
# If we just replaced an instruction, we need to bump the cursor so
|
||||
# following instructions are inserted *after* the replaced instruction.
|
||||
if replaced_inst:
|
||||
with fmt.indented(
|
||||
'if pos.current_inst() == Some({}) {{'
|
||||
.format(replaced_inst), '}'):
|
||||
fmt.line('pos.next_inst();')
|
||||
|
||||
|
||||
def gen_xform(xform, fmt, type_sets):
|
||||
# type: (XForm, Formatter, UniqueTable) -> None
|
||||
"""
|
||||
Emit code for `xform`, assuming that the opcode of xform's root instruction
|
||||
has already been matched.
|
||||
|
||||
`inst: Inst` is the variable to be replaced. It is pointed to by `pos:
|
||||
Cursor`.
|
||||
`dfg: DataFlowGraph` is available and mutable.
|
||||
"""
|
||||
# Unwrap the source instruction, create local variables for the input
|
||||
# variables.
|
||||
replace_inst = unwrap_inst('inst', xform.src.rtl[0], fmt)
|
||||
|
||||
# Emit any runtime checks.
|
||||
# These will rebind `predicate` emitted by unwrap_inst().
|
||||
for check in get_runtime_typechecks(xform):
|
||||
emit_runtime_typecheck(check, fmt, type_sets)
|
||||
|
||||
# Guard the actual expansion by `predicate`.
|
||||
with fmt.indented('if predicate {', '}'):
|
||||
# If we're going to delete `inst`, we need to detach its results first
|
||||
# so they can be reattached during pattern expansion.
|
||||
if not replace_inst:
|
||||
fmt.line('dfg.clear_results(inst);')
|
||||
|
||||
# Emit the destination pattern.
|
||||
for dst in xform.dst.rtl:
|
||||
emit_dst_inst(dst, fmt)
|
||||
|
||||
# Delete the original instruction if we didn't have an opportunity to
|
||||
# replace it.
|
||||
if not replace_inst:
|
||||
fmt.line('assert_eq!(pos.remove_inst(), inst);')
|
||||
fmt.line('return true;')
|
||||
|
||||
|
||||
def gen_xform_group(xgrp, fmt, type_sets):
|
||||
# type: (XFormGroup, Formatter, UniqueTable) -> None
|
||||
fmt.doc_comment("Legalize the instruction pointed to by `pos`.")
|
||||
fmt.line('#[allow(unused_variables,unused_assignments)]')
|
||||
with fmt.indented(
|
||||
'pub fn {}(dfg: &mut ir::DataFlowGraph, '
|
||||
'cfg: &mut ::flowgraph::ControlFlowGraph, '
|
||||
'pos: &mut ir::Cursor) -> '
|
||||
'bool {{'.format(xgrp.name), '}'):
|
||||
fmt.line('use ir::{InstBuilder, CursorBase};')
|
||||
|
||||
# Gen the instruction to be legalized. The cursor we're passed must be
|
||||
# pointing at an instruction.
|
||||
fmt.line('let inst = pos.current_inst().expect("need instruction");')
|
||||
|
||||
# Group the xforms by opcode so we can generate a big switch.
|
||||
# Preserve ordering.
|
||||
xforms = defaultdict(list) # type: DefaultDict[str, List[XForm]]
|
||||
for xform in xgrp.xforms:
|
||||
inst = xform.src.rtl[0].expr.inst
|
||||
xforms[inst.camel_name].append(xform)
|
||||
|
||||
with fmt.indented('match dfg[inst].opcode() {', '}'):
|
||||
for camel_name in sorted(xforms.keys()):
|
||||
with fmt.indented(
|
||||
'ir::Opcode::{} => {{'.format(camel_name), '}'):
|
||||
for xform in xforms[camel_name]:
|
||||
gen_xform(xform, fmt, type_sets)
|
||||
# We'll assume there are uncovered opcodes.
|
||||
fmt.line('_ => {},')
|
||||
|
||||
# If we fall through, nothing was expanded. Call the chain if any.
|
||||
if xgrp.chain:
|
||||
fmt.format('{}(dfg, cfg, pos)', xgrp.chain.rust_name())
|
||||
else:
|
||||
fmt.line('false')
|
||||
|
||||
|
||||
def gen_isa(isa, fmt, shared_groups):
|
||||
# type: (TargetISA, Formatter, Set[XFormGroup]) -> None
|
||||
"""
|
||||
Generate legalization functions for `isa` and add any shared `XFormGroup`s
|
||||
encountered to `shared_groups`.
|
||||
|
||||
Generate `TYPE_SETS` and `LEGALIZE_ACTION` tables.
|
||||
"""
|
||||
type_sets = UniqueTable()
|
||||
for xgrp in isa.legalize_codes.keys():
|
||||
if xgrp.isa is None:
|
||||
shared_groups.add(xgrp)
|
||||
else:
|
||||
assert xgrp.isa == isa
|
||||
gen_xform_group(xgrp, fmt, type_sets)
|
||||
|
||||
gen_typesets_table(fmt, type_sets)
|
||||
|
||||
with fmt.indented(
|
||||
'pub static LEGALIZE_ACTIONS: [isa::Legalize; {}] = ['
|
||||
.format(len(isa.legalize_codes)), '];'):
|
||||
for xgrp in isa.legalize_codes.keys():
|
||||
fmt.format('{},', xgrp.rust_name())
|
||||
|
||||
|
||||
def generate(isas, out_dir):
|
||||
# type: (Sequence[TargetISA], str) -> None
|
||||
shared_groups = set() # type: Set[XFormGroup]
|
||||
|
||||
for isa in isas:
|
||||
fmt = Formatter()
|
||||
gen_isa(isa, fmt, shared_groups)
|
||||
fmt.update_file('legalize-{}.rs'.format(isa.name), out_dir)
|
||||
|
||||
# Shared xform groups.
|
||||
fmt = Formatter()
|
||||
type_sets = UniqueTable()
|
||||
for xgrp in sorted(shared_groups, key=lambda g: g.name):
|
||||
gen_xform_group(xgrp, fmt, type_sets)
|
||||
gen_typesets_table(fmt, type_sets)
|
||||
fmt.update_file('legalizer.rs', out_dir)
|
||||
@@ -1,106 +0,0 @@
|
||||
"""
|
||||
Generate register bank descriptions for each ISA.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
import srcgen
|
||||
|
||||
try:
|
||||
from typing import Sequence, List # noqa
|
||||
from cdsl.isa import TargetISA # noqa
|
||||
from cdsl.registers import RegBank, RegClass # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def gen_regbank(regbank, fmt):
|
||||
# type: (RegBank, srcgen.Formatter) -> None
|
||||
"""
|
||||
Emit a static data definition for regbank.
|
||||
"""
|
||||
with fmt.indented('RegBank {', '},'):
|
||||
fmt.format('name: "{}",', regbank.name)
|
||||
fmt.format('first_unit: {},', regbank.first_unit)
|
||||
fmt.format('units: {},', regbank.units)
|
||||
fmt.format(
|
||||
'names: &[{}],',
|
||||
', '.join('"{}"'.format(n) for n in regbank.names))
|
||||
fmt.format('prefix: "{}",', regbank.prefix)
|
||||
fmt.format('first_toprc: {},', regbank.toprcs[0].index)
|
||||
fmt.format('num_toprcs: {},', len(regbank.toprcs))
|
||||
|
||||
|
||||
def gen_regbank_units(regbank, fmt):
|
||||
# type: (RegBank, srcgen.Formatter) -> None
|
||||
"""
|
||||
Emit constants for all the register units in `regbank`.
|
||||
"""
|
||||
for unit in range(regbank.units):
|
||||
v = unit + regbank.first_unit
|
||||
if unit < len(regbank.names):
|
||||
fmt.format("{} = {},", regbank.names[unit], v)
|
||||
else:
|
||||
fmt.format("{}{} = {},", regbank.prefix, unit, v)
|
||||
|
||||
|
||||
def gen_regclass(rc, fmt):
|
||||
# type: (RegClass, srcgen.Formatter) -> None
|
||||
"""
|
||||
Emit a static data definition for a register class.
|
||||
"""
|
||||
with fmt.indented('RegClassData {', '},'):
|
||||
fmt.format('name: "{}",', rc.name)
|
||||
fmt.format('index: {},', rc.index)
|
||||
fmt.format('width: {},', rc.width)
|
||||
fmt.format('bank: {},', rc.bank.index)
|
||||
fmt.format('toprc: {},', rc.toprc.index)
|
||||
fmt.format('first: {},', rc.bank.first_unit + rc.start)
|
||||
fmt.format('subclasses: 0x{:x},', rc.subclass_mask())
|
||||
mask = ', '.join('0x{:08x}'.format(x) for x in rc.mask())
|
||||
fmt.format('mask: [{}],', mask)
|
||||
|
||||
|
||||
def gen_isa(isa, fmt):
|
||||
# type: (TargetISA, srcgen.Formatter) -> None
|
||||
"""
|
||||
Generate register tables for isa.
|
||||
"""
|
||||
if not isa.regbanks:
|
||||
print('cargo:warning={} has no register banks'.format(isa.name))
|
||||
|
||||
with fmt.indented('pub static INFO: RegInfo = RegInfo {', '};'):
|
||||
# Bank descriptors.
|
||||
with fmt.indented('banks: &[', '],'):
|
||||
for regbank in isa.regbanks:
|
||||
gen_regbank(regbank, fmt)
|
||||
fmt.line('classes: &CLASSES,')
|
||||
|
||||
# Register class descriptors.
|
||||
with fmt.indented(
|
||||
'const CLASSES: [RegClassData; {}] = ['
|
||||
.format(len(isa.regclasses)), '];'):
|
||||
for idx, rc in enumerate(isa.regclasses):
|
||||
assert idx == rc.index
|
||||
gen_regclass(rc, fmt)
|
||||
|
||||
# Emit constants referencing the register classes.
|
||||
for rc in isa.regclasses:
|
||||
fmt.line('#[allow(dead_code)]')
|
||||
fmt.line(
|
||||
'pub const {}: RegClass = &CLASSES[{}];'
|
||||
.format(rc.name, rc.index))
|
||||
|
||||
# Emit constants for all the register units.
|
||||
fmt.line('#[allow(dead_code, non_camel_case_types)]')
|
||||
fmt.line('#[derive(Clone, Copy)]')
|
||||
with fmt.indented('pub enum RU {', '}'):
|
||||
for regbank in isa.regbanks:
|
||||
gen_regbank_units(regbank, fmt)
|
||||
|
||||
|
||||
def generate(isas, out_dir):
|
||||
# type: (Sequence[TargetISA], str) -> None
|
||||
for isa in isas:
|
||||
fmt = srcgen.Formatter()
|
||||
gen_isa(isa, fmt)
|
||||
fmt.update_file('registers-{}.rs'.format(isa.name), out_dir)
|
||||
@@ -1,311 +0,0 @@
|
||||
"""
|
||||
Generate sources with settings.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
import srcgen
|
||||
from unique_table import UniqueSeqTable
|
||||
import constant_hash
|
||||
from cdsl import camel_case
|
||||
from cdsl.settings import BoolSetting, NumSetting, EnumSetting
|
||||
from base import settings
|
||||
|
||||
try:
|
||||
from typing import Sequence, Set, Tuple, List, Union, TYPE_CHECKING # noqa
|
||||
if TYPE_CHECKING:
|
||||
from cdsl.isa import TargetISA # noqa
|
||||
from cdsl.settings import Setting, Preset, SettingGroup # noqa
|
||||
from cdsl.predicates import Predicate, PredContext # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def gen_enum_types(sgrp, fmt):
|
||||
# type: (SettingGroup, srcgen.Formatter) -> None
|
||||
"""
|
||||
Emit enum types for any enum settings.
|
||||
"""
|
||||
for setting in sgrp.settings:
|
||||
if not isinstance(setting, EnumSetting):
|
||||
continue
|
||||
ty = camel_case(setting.name)
|
||||
fmt.doc_comment('Values for {}.'.format(setting))
|
||||
fmt.line('#[derive(Debug, PartialEq, Eq)]')
|
||||
with fmt.indented('pub enum {} {{'.format(ty), '}'):
|
||||
for v in setting.values:
|
||||
fmt.doc_comment('`{}`.'.format(v))
|
||||
fmt.line(camel_case(v) + ',')
|
||||
|
||||
|
||||
def gen_getter(setting, sgrp, fmt):
|
||||
# type: (Setting, SettingGroup, srcgen.Formatter) -> None
|
||||
"""
|
||||
Emit a getter function for `setting`.
|
||||
"""
|
||||
fmt.doc_comment(setting.__doc__)
|
||||
|
||||
if isinstance(setting, BoolSetting):
|
||||
proto = 'pub fn {}(&self) -> bool'.format(setting.name)
|
||||
with fmt.indented(proto + ' {', '}'):
|
||||
fmt.line(
|
||||
'self.numbered_predicate({})'
|
||||
.format(sgrp.predicate_number[setting]))
|
||||
elif isinstance(setting, NumSetting):
|
||||
proto = 'pub fn {}(&self) -> u8'.format(setting.name)
|
||||
with fmt.indented(proto + ' {', '}'):
|
||||
fmt.line('self.bytes[{}]'.format(setting.byte_offset))
|
||||
elif isinstance(setting, EnumSetting):
|
||||
ty = camel_case(setting.name)
|
||||
proto = 'pub fn {}(&self) -> {}'.format(setting.name, ty)
|
||||
with fmt.indented(proto + ' {', '}'):
|
||||
with fmt.indented(
|
||||
'match self.bytes[{}] {{'
|
||||
.format(setting.byte_offset), '}'):
|
||||
for i, v in enumerate(setting.values):
|
||||
fmt.line('{} => {}::{},'.format(i, ty, camel_case(v)))
|
||||
fmt.line('_ => panic!("Invalid enum value")')
|
||||
else:
|
||||
raise AssertionError("Unknown setting kind")
|
||||
|
||||
|
||||
def gen_pred_getter(name, pred, sgrp, fmt):
|
||||
# type: (str, Predicate, SettingGroup, srcgen.Formatter) -> None
|
||||
"""
|
||||
Emit a getter for a named pre-computed predicate.
|
||||
"""
|
||||
fmt.doc_comment('Computed predicate `{}`.'.format(pred.rust_predicate(0)))
|
||||
proto = 'pub fn {}(&self) -> bool'.format(name)
|
||||
with fmt.indented(proto + ' {', '}'):
|
||||
fmt.line(
|
||||
'self.numbered_predicate({})'
|
||||
.format(sgrp.predicate_number[pred]))
|
||||
|
||||
|
||||
def gen_getters(sgrp, fmt):
|
||||
# type: (SettingGroup, srcgen.Formatter) -> None
|
||||
"""
|
||||
Emit getter functions for all the settings in fmt.
|
||||
"""
|
||||
fmt.doc_comment("User-defined settings.")
|
||||
with fmt.indented('impl Flags {', '}'):
|
||||
fmt.doc_comment('Get a view of the boolean predicates.')
|
||||
with fmt.indented(
|
||||
'pub fn predicate_view(&self) -> ::settings::PredicateView {',
|
||||
'}'):
|
||||
fmt.format(
|
||||
'::settings::PredicateView::new(&self.bytes[{}..])',
|
||||
sgrp.boolean_offset)
|
||||
if sgrp.settings:
|
||||
fmt.doc_comment('Dynamic numbered predicate getter.')
|
||||
with fmt.indented(
|
||||
'fn numbered_predicate(&self, p: usize) -> bool {', '}'):
|
||||
fmt.line(
|
||||
'self.bytes[{} + p / 8] & (1 << (p % 8)) != 0'
|
||||
.format(sgrp.boolean_offset))
|
||||
for setting in sgrp.settings:
|
||||
gen_getter(setting, sgrp, fmt)
|
||||
for name, pred in sgrp.named_predicates.items():
|
||||
gen_pred_getter(name, pred, sgrp, fmt)
|
||||
|
||||
|
||||
def gen_descriptors(sgrp, fmt):
|
||||
# type: (SettingGroup, srcgen.Formatter) -> None
|
||||
"""
|
||||
Generate the DESCRIPTORS, ENUMERATORS, and PRESETS tables.
|
||||
"""
|
||||
|
||||
enums = UniqueSeqTable()
|
||||
|
||||
with fmt.indented(
|
||||
'static DESCRIPTORS: [detail::Descriptor; {}] = ['
|
||||
.format(len(sgrp.settings) + len(sgrp.presets)),
|
||||
'];'):
|
||||
for idx, setting in enumerate(sgrp.settings):
|
||||
setting.descriptor_index = idx
|
||||
with fmt.indented('detail::Descriptor {', '},'):
|
||||
fmt.line('name: "{}",'.format(setting.name))
|
||||
fmt.line('offset: {},'.format(setting.byte_offset))
|
||||
if isinstance(setting, BoolSetting):
|
||||
fmt.line(
|
||||
'detail: detail::Detail::Bool {{ bit: {} }},'
|
||||
.format(setting.bit_offset))
|
||||
elif isinstance(setting, NumSetting):
|
||||
fmt.line('detail: detail::Detail::Num,')
|
||||
elif isinstance(setting, EnumSetting):
|
||||
offs = enums.add(setting.values)
|
||||
fmt.line(
|
||||
'detail: detail::Detail::Enum ' +
|
||||
'{{ last: {}, enumerators: {} }},'
|
||||
.format(len(setting.values)-1, offs))
|
||||
else:
|
||||
raise AssertionError("Unknown setting kind")
|
||||
|
||||
for idx, preset in enumerate(sgrp.presets):
|
||||
preset.descriptor_index = len(sgrp.settings) + idx
|
||||
with fmt.indented('detail::Descriptor {', '},'):
|
||||
fmt.line('name: "{}",'.format(preset.name))
|
||||
fmt.line('offset: {},'.format(idx * sgrp.settings_size))
|
||||
fmt.line('detail: detail::Detail::Preset,')
|
||||
|
||||
with fmt.indented(
|
||||
'static ENUMERATORS: [&str; {}] = ['
|
||||
.format(len(enums.table)),
|
||||
'];'):
|
||||
for txt in enums.table:
|
||||
fmt.line('"{}",'.format(txt))
|
||||
|
||||
def hash_setting(s):
|
||||
# type: (Union[Setting, Preset]) -> int
|
||||
return constant_hash.simple_hash(s.name)
|
||||
|
||||
hash_elems = [] # type: List[Union[Setting, Preset]]
|
||||
hash_elems.extend(sgrp.settings)
|
||||
hash_elems.extend(sgrp.presets)
|
||||
hash_table = constant_hash.compute_quadratic(hash_elems, hash_setting)
|
||||
with fmt.indented(
|
||||
'static HASH_TABLE: [u16; {}] = ['
|
||||
.format(len(hash_table)),
|
||||
'];'):
|
||||
for h in hash_table:
|
||||
if h is None:
|
||||
fmt.line('0xffff,')
|
||||
else:
|
||||
fmt.line('{},'.format(h.descriptor_index))
|
||||
|
||||
with fmt.indented(
|
||||
'static PRESETS: [(u8, u8); {}] = ['
|
||||
.format(len(sgrp.presets) * sgrp.settings_size),
|
||||
'];'):
|
||||
for preset in sgrp.presets:
|
||||
fmt.comment(preset.name)
|
||||
for mask, value in preset.layout():
|
||||
fmt.format('(0b{:08b}, 0b{:08b}),', mask, value)
|
||||
|
||||
|
||||
def gen_template(sgrp, fmt):
|
||||
# type: (SettingGroup, srcgen.Formatter) -> None
|
||||
"""
|
||||
Emit a Template constant.
|
||||
"""
|
||||
v = [0] * sgrp.settings_size
|
||||
for setting in sgrp.settings:
|
||||
v[setting.byte_offset] |= setting.default_byte()
|
||||
|
||||
with fmt.indented(
|
||||
'static TEMPLATE: detail::Template = detail::Template {', '};'):
|
||||
fmt.line('name: "{}",'.format(sgrp.name))
|
||||
fmt.line('descriptors: &DESCRIPTORS,')
|
||||
fmt.line('enumerators: &ENUMERATORS,')
|
||||
fmt.line('hash_table: &HASH_TABLE,')
|
||||
vs = ', '.join('{:#04x}'.format(x) for x in v)
|
||||
fmt.line('defaults: &[ {} ],'.format(vs))
|
||||
fmt.line('presets: &PRESETS,')
|
||||
|
||||
fmt.doc_comment(
|
||||
'Create a `settings::Builder` for the {} settings group.'
|
||||
.format(sgrp.name))
|
||||
with fmt.indented('pub fn builder() -> Builder {', '}'):
|
||||
fmt.line('Builder::new(&TEMPLATE)')
|
||||
|
||||
|
||||
def gen_display(sgrp, fmt):
|
||||
# type: (SettingGroup, srcgen.Formatter) -> None
|
||||
"""
|
||||
Generate the Display impl for Flags.
|
||||
"""
|
||||
with fmt.indented('impl fmt::Display for Flags {', '}'):
|
||||
with fmt.indented(
|
||||
'fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {',
|
||||
'}'):
|
||||
fmt.line('writeln!(f, "[{}]")?;'.format(sgrp.name))
|
||||
with fmt.indented('for d in &DESCRIPTORS {', '}'):
|
||||
fmt.line('write!(f, "{} = ", d.name)?;')
|
||||
fmt.line(
|
||||
'TEMPLATE.format_toml_value(d.detail,' +
|
||||
'self.bytes[d.offset as usize], f)?;')
|
||||
fmt.line('writeln!(f, "")?;')
|
||||
fmt.line('Ok(())')
|
||||
|
||||
|
||||
def gen_constructor(sgrp, parent, fmt):
|
||||
# type: (SettingGroup, PredContext, srcgen.Formatter) -> None
|
||||
"""
|
||||
Generate a Flags constructor.
|
||||
"""
|
||||
|
||||
with fmt.indented('impl Flags {', '}'):
|
||||
args = 'builder: &Builder'
|
||||
if sgrp.parent:
|
||||
p = sgrp.parent
|
||||
args = '{}: &{}::Flags, {}'.format(p.name, p.qual_mod, args)
|
||||
fmt.doc_comment('Create flags {} settings group.'.format(sgrp.name))
|
||||
fmt.line('#[allow(unused_variables)]')
|
||||
with fmt.indented(
|
||||
'pub fn new({}) -> Flags {{'.format(args), '}'):
|
||||
fmt.line('let bvec = builder.state_for("{}");'.format(sgrp.name))
|
||||
fmt.line('let mut bytes = [0; {}];'.format(sgrp.byte_size()))
|
||||
fmt.line('assert_eq!(bvec.len(), {});'.format(sgrp.settings_size))
|
||||
with fmt.indented(
|
||||
'for (i, b) in bvec.iter().enumerate() {', '}'):
|
||||
fmt.line('bytes[i] = *b;')
|
||||
|
||||
# Stop here without predicates.
|
||||
if len(sgrp.predicate_number) == sgrp.boolean_settings:
|
||||
fmt.line('Flags { bytes: bytes }')
|
||||
return
|
||||
|
||||
# Now compute the predicates.
|
||||
fmt.line(
|
||||
'let mut {} = Flags {{ bytes: bytes }};'
|
||||
.format(sgrp.name))
|
||||
|
||||
for pred, number in sgrp.predicate_number.items():
|
||||
# Don't compute our own settings.
|
||||
if number < sgrp.boolean_settings:
|
||||
continue
|
||||
fmt.comment('Precompute #{}.'.format(number))
|
||||
with fmt.indented(
|
||||
'if {} {{'.format(pred.rust_predicate(0)),
|
||||
'}'):
|
||||
fmt.line(
|
||||
'{}.bytes[{}] |= 1 << {};'
|
||||
.format(
|
||||
sgrp.name,
|
||||
sgrp.boolean_offset + number // 8,
|
||||
number % 8))
|
||||
|
||||
fmt.line(sgrp.name)
|
||||
|
||||
|
||||
def gen_group(sgrp, fmt):
|
||||
# type: (SettingGroup, srcgen.Formatter) -> None
|
||||
"""
|
||||
Generate a Flags struct representing `sgrp`.
|
||||
"""
|
||||
fmt.line('#[derive(Clone)]')
|
||||
fmt.doc_comment('Flags group `{}`.'.format(sgrp.name))
|
||||
with fmt.indented('pub struct Flags {', '}'):
|
||||
fmt.line('bytes: [u8; {}],'.format(sgrp.byte_size()))
|
||||
|
||||
gen_constructor(sgrp, None, fmt)
|
||||
gen_enum_types(sgrp, fmt)
|
||||
gen_getters(sgrp, fmt)
|
||||
gen_descriptors(sgrp, fmt)
|
||||
gen_template(sgrp, fmt)
|
||||
gen_display(sgrp, fmt)
|
||||
|
||||
|
||||
def generate(isas, out_dir):
|
||||
# type: (Sequence[TargetISA], str) -> None
|
||||
# Generate shared settings.
|
||||
fmt = srcgen.Formatter()
|
||||
settings.group.qual_mod = 'settings'
|
||||
gen_group(settings.group, fmt)
|
||||
fmt.update_file('settings.rs', out_dir)
|
||||
|
||||
# Generate ISA-specific settings.
|
||||
for isa in isas:
|
||||
isa.settings.qual_mod = 'isa::{}::settings'.format(
|
||||
isa.settings.name)
|
||||
fmt = srcgen.Formatter()
|
||||
gen_group(isa.settings, fmt)
|
||||
fmt.update_file('settings-{}.rs'.format(isa.name), out_dir)
|
||||
@@ -1,61 +0,0 @@
|
||||
"""
|
||||
Generate sources with type info.
|
||||
|
||||
This generates a `types.rs` file which is included in
|
||||
`lib/cretonne/ir/types.rs`. The file provides constant definitions for the most
|
||||
commonly used types, including all of the scalar types.
|
||||
|
||||
This ensures that Python and Rust use the same type numbering.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
import srcgen
|
||||
from cdsl.types import ValueType
|
||||
import base.types # noqa
|
||||
|
||||
try:
|
||||
from typing import Iterable # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def emit_type(ty, fmt):
|
||||
# type: (ValueType, srcgen.Formatter) -> None
|
||||
"""
|
||||
Emit a constant definition of a single value type.
|
||||
"""
|
||||
name = ty.name.upper()
|
||||
fmt.doc_comment(ty.__doc__)
|
||||
fmt.line(
|
||||
'pub const {}: Type = Type({:#x});'
|
||||
.format(name, ty.number))
|
||||
|
||||
|
||||
def emit_vectors(bits, fmt):
|
||||
# type: (int, srcgen.Formatter) -> None
|
||||
"""
|
||||
Emit definition for all vector types with `bits` total size.
|
||||
"""
|
||||
size = bits // 8
|
||||
for ty in ValueType.all_scalars:
|
||||
mb = ty.membytes
|
||||
if mb == 0 or mb >= size:
|
||||
continue
|
||||
emit_type(ty.by(size // mb), fmt)
|
||||
|
||||
|
||||
def emit_types(fmt):
|
||||
# type: (srcgen.Formatter) -> None
|
||||
for ty in ValueType.all_scalars:
|
||||
emit_type(ty, fmt)
|
||||
# Emit vector definitions for common SIMD sizes.
|
||||
emit_vectors(64, fmt)
|
||||
emit_vectors(128, fmt)
|
||||
emit_vectors(256, fmt)
|
||||
emit_vectors(512, fmt)
|
||||
|
||||
|
||||
def generate(out_dir):
|
||||
# type: (str) -> None
|
||||
fmt = srcgen.Formatter()
|
||||
emit_types(fmt)
|
||||
fmt.update_file('types.rs', out_dir)
|
||||
@@ -1,24 +0,0 @@
|
||||
"""
|
||||
Cretonne target ISA definitions
|
||||
-------------------------------
|
||||
|
||||
The :py:mod:`isa` package contains sub-packages for each target instruction set
|
||||
architecture supported by Cretonne.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.isa import TargetISA # noqa
|
||||
from . import riscv, intel, 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:`cretonne.TargetISA` instance.
|
||||
"""
|
||||
return [riscv.ISA, intel.ISA, arm32.ISA, arm64.ISA]
|
||||
@@ -1,14 +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
|
||||
|
||||
# Re-export the primary target ISA definition.
|
||||
ISA = defs.ISA.finish()
|
||||
@@ -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])
|
||||
|
||||
# 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)
|
||||
@@ -1,37 +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')
|
||||
|
||||
GPR = RegClass(IntRegs)
|
||||
S = RegClass(FloatRegs, count=32)
|
||||
D = RegClass(FloatRegs, width=2)
|
||||
Q = RegClass(FloatRegs, width=4)
|
||||
|
||||
RegClass.extract_names(globals())
|
||||
@@ -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())
|
||||
@@ -1,13 +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
|
||||
|
||||
# Re-export the primary target ISA definition.
|
||||
ISA = defs.ISA.finish()
|
||||
@@ -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])
|
||||
A64 = CPUMode('A64', ISA)
|
||||
|
||||
# TODO: Refine these
|
||||
A64.legalize_type(narrow)
|
||||
@@ -1,24 +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')
|
||||
|
||||
GPR = RegClass(IntRegs)
|
||||
FPR = RegClass(FloatRegs)
|
||||
|
||||
RegClass.extract_names(globals())
|
||||
@@ -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())
|
||||
@@ -1,23 +0,0 @@
|
||||
"""
|
||||
Intel Target Architecture
|
||||
-------------------------
|
||||
|
||||
This target ISA generates code for Intel CPUs with two separate CPU modes:
|
||||
|
||||
`I32`
|
||||
IA-32 architecture, also known as 'x86'. Generates code for the Intel 386
|
||||
and later processors in 32-bit mode.
|
||||
`I64`
|
||||
Intel 64 architecture, also known as 'x86-64, 'x64', and 'amd64'. Intel and
|
||||
AMD CPUs running in 64-bit mode.
|
||||
|
||||
Floating point is supported only on CPUs with support for SSE2 or later. There
|
||||
is no x87 floating point support.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from . import defs
|
||||
from . import encodings, settings, registers # noqa
|
||||
|
||||
# Re-export the primary target ISA definition.
|
||||
ISA = defs.ISA.finish()
|
||||
@@ -1,15 +0,0 @@
|
||||
"""
|
||||
Intel definitions.
|
||||
|
||||
Commonly used definitions.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.isa import TargetISA, CPUMode
|
||||
import base.instructions
|
||||
from . import instructions as x86
|
||||
|
||||
ISA = TargetISA('intel', [base.instructions.GROUP, x86.GROUP])
|
||||
|
||||
# CPU modes for 32-bit and 64-bit operation.
|
||||
I64 = CPUMode('I64', ISA)
|
||||
I32 = CPUMode('I32', ISA)
|
||||
@@ -1,314 +0,0 @@
|
||||
"""
|
||||
Intel Encodings.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.predicates import IsUnsignedInt
|
||||
from base import instructions as base
|
||||
from base.formats import UnaryImm
|
||||
from .defs import I32, I64
|
||||
from . import recipes as r
|
||||
from . import settings as cfg
|
||||
from . import instructions as x86
|
||||
from .legalize import intel_expand
|
||||
from base.legalize import narrow, expand
|
||||
|
||||
try:
|
||||
from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from cdsl.instructions import MaybeBoundInst # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
I32.legalize_type(
|
||||
default=narrow,
|
||||
i32=intel_expand,
|
||||
f32=expand,
|
||||
f64=expand)
|
||||
|
||||
I64.legalize_type(
|
||||
default=narrow,
|
||||
i32=intel_expand,
|
||||
i64=intel_expand,
|
||||
f32=expand,
|
||||
f64=expand)
|
||||
|
||||
|
||||
#
|
||||
# Helper functions for generating encodings.
|
||||
#
|
||||
|
||||
def enc_i32_i64(inst, recipe, *args, **kwargs):
|
||||
# type: (MaybeBoundInst, r.TailRecipe, *int, **int) -> None
|
||||
"""
|
||||
Add encodings for `inst.i32` to I32.
|
||||
Add encodings for `inst.i32` to I64 with and without REX.
|
||||
Add encodings for `inst.i64` to I64 with a REX.W prefix.
|
||||
"""
|
||||
I32.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.
|
||||
I64.enc(inst.i32, *recipe.rex(*args, **kwargs))
|
||||
I64.enc(inst.i32, *recipe(*args, **kwargs))
|
||||
|
||||
I64.enc(inst.i64, *recipe.rex(*args, w=1, **kwargs))
|
||||
|
||||
|
||||
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 I32.
|
||||
Add encodings for `inst.i32` to I64 with and without REX.
|
||||
Add encodings for `inst.i64` to I64 with a REX prefix, using the `w_bit`
|
||||
argument to determine wheter or not to set the REX.W bit.
|
||||
"""
|
||||
I32.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.
|
||||
I64.enc(inst.i32.any, *recipe.rex(*args, **kwargs))
|
||||
I64.enc(inst.i32.any, *recipe(*args, **kwargs))
|
||||
|
||||
if w_bit:
|
||||
I64.enc(inst.i64.any, *recipe.rex(*args, w=1, **kwargs))
|
||||
else:
|
||||
I64.enc(inst.i64.any, *recipe.rex(*args, **kwargs))
|
||||
I64.enc(inst.i64.any, *recipe(*args, **kwargs))
|
||||
|
||||
|
||||
def enc_flt(inst, recipe, *args, **kwargs):
|
||||
# type: (MaybeBoundInst, r.TailRecipe, *int, **int) -> None
|
||||
"""
|
||||
Add encodings for floating point instruction `inst` to both I32 and I64.
|
||||
"""
|
||||
I32.enc(inst, *recipe(*args, **kwargs))
|
||||
I64.enc(inst, *recipe.rex(*args, **kwargs))
|
||||
I64.enc(inst, *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)
|
||||
|
||||
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(base.copy, r.umr, 0x89)
|
||||
enc_i32_i64(base.regmove, 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.rib, 0x83, rrr=rrr)
|
||||
enc_i32_i64(inst, r.rid, 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.
|
||||
I32.enc(base.iconst.i32, *r.puid(0xb8))
|
||||
|
||||
I64.enc(base.iconst.i32, *r.puid.rex(0xb8))
|
||||
I64.enc(base.iconst.i32, *r.puid(0xb8))
|
||||
# The 32-bit immediate movl also zero-extends to 64 bits.
|
||||
I64.enc(base.iconst.i64, *r.puid.rex(0xb8),
|
||||
instp=IsUnsignedInt(UnaryImm.imm, 32))
|
||||
I64.enc(base.iconst.i64, *r.puid(0xb8),
|
||||
instp=IsUnsignedInt(UnaryImm.imm, 32))
|
||||
# Sign-extended 32-bit immediate.
|
||||
I64.enc(base.iconst.i64, *r.uid.rex(0xc7, rrr=0, w=1))
|
||||
# Finally, the 0xb8 opcode takes an 8-byte immediate with a REX.W prefix.
|
||||
I64.enc(base.iconst.i64, *r.puiq.rex(0xb8, w=1))
|
||||
|
||||
# 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)]:
|
||||
I32.enc(inst.i32.any, *r.rc(0xd3, rrr=rrr))
|
||||
I64.enc(inst.i64.any, *r.rc.rex(0xd3, rrr=rrr, w=1))
|
||||
I64.enc(inst.i32.any, *r.rc.rex(0xd3, rrr=rrr))
|
||||
I64.enc(inst.i32.any, *r.rc(0xd3, rrr=rrr))
|
||||
|
||||
# Population count.
|
||||
I32.enc(base.popcnt.i32, *r.urm(0xf3, 0x0f, 0xb8), isap=cfg.use_popcnt)
|
||||
I64.enc(base.popcnt.i64, *r.urm.rex(0xf3, 0x0f, 0xb8, w=1),
|
||||
isap=cfg.use_popcnt)
|
||||
I64.enc(base.popcnt.i32, *r.urm.rex(0xf3, 0x0f, 0xb8), isap=cfg.use_popcnt)
|
||||
I64.enc(base.popcnt.i32, *r.urm(0xf3, 0x0f, 0xb8), isap=cfg.use_popcnt)
|
||||
|
||||
# Count leading zero bits.
|
||||
I32.enc(base.clz.i32, *r.urm(0xf3, 0x0f, 0xbd), isap=cfg.use_lzcnt)
|
||||
I64.enc(base.clz.i64, *r.urm.rex(0xf3, 0x0f, 0xbd, w=1),
|
||||
isap=cfg.use_lzcnt)
|
||||
I64.enc(base.clz.i32, *r.urm.rex(0xf3, 0x0f, 0xbd), isap=cfg.use_lzcnt)
|
||||
I64.enc(base.clz.i32, *r.urm(0xf3, 0x0f, 0xbd), isap=cfg.use_lzcnt)
|
||||
|
||||
# Count trailing zero bits.
|
||||
I32.enc(base.ctz.i32, *r.urm(0xf3, 0x0f, 0xbc), isap=cfg.use_bmi1)
|
||||
I64.enc(base.ctz.i64, *r.urm.rex(0xf3, 0x0f, 0xbc, w=1),
|
||||
isap=cfg.use_bmi1)
|
||||
I64.enc(base.ctz.i32, *r.urm.rex(0xf3, 0x0f, 0xbc), isap=cfg.use_bmi1)
|
||||
I64.enc(base.ctz.i32, *r.urm(0xf3, 0x0f, 0xbc), isap=cfg.use_bmi1)
|
||||
|
||||
#
|
||||
# Loads and stores.
|
||||
#
|
||||
enc_i32_i64_ld_st(base.store, True, r.st, 0x89)
|
||||
enc_i32_i64_ld_st(base.store, True, r.stDisp8, 0x89)
|
||||
enc_i32_i64_ld_st(base.store, True, r.stDisp32, 0x89)
|
||||
|
||||
I64.enc(base.istore32.i64.any, *r.st.rex(0x89))
|
||||
I64.enc(base.istore32.i64.any, *r.stDisp8.rex(0x89))
|
||||
I64.enc(base.istore32.i64.any, *r.stDisp32.rex(0x89))
|
||||
|
||||
enc_i32_i64_ld_st(base.istore16, False, r.st, 0x66, 0x89)
|
||||
enc_i32_i64_ld_st(base.istore16, False, r.stDisp8, 0x66, 0x89)
|
||||
enc_i32_i64_ld_st(base.istore16, False, r.stDisp32, 0x66, 0x89)
|
||||
|
||||
# Byte stores are more complicated because the registers they can address
|
||||
# depends of the presence of a REX prefix
|
||||
I32.enc(base.istore8.i32.any, *r.st_abcd(0x88))
|
||||
I64.enc(base.istore8.i32.any, *r.st_abcd(0x88))
|
||||
I64.enc(base.istore8.i64.any, *r.st.rex(0x88))
|
||||
I32.enc(base.istore8.i32.any, *r.stDisp8_abcd(0x88))
|
||||
I64.enc(base.istore8.i32.any, *r.stDisp8_abcd(0x88))
|
||||
I64.enc(base.istore8.i64.any, *r.stDisp8.rex(0x88))
|
||||
I32.enc(base.istore8.i32.any, *r.stDisp32_abcd(0x88))
|
||||
I64.enc(base.istore8.i32.any, *r.stDisp32_abcd(0x88))
|
||||
I64.enc(base.istore8.i64.any, *r.stDisp32.rex(0x88))
|
||||
|
||||
enc_i32_i64_ld_st(base.load, True, r.ld, 0x8b)
|
||||
enc_i32_i64_ld_st(base.load, True, r.ldDisp8, 0x8b)
|
||||
enc_i32_i64_ld_st(base.load, True, r.ldDisp32, 0x8b)
|
||||
|
||||
I64.enc(base.uload32.i64, *r.ld.rex(0x8b))
|
||||
I64.enc(base.uload32.i64, *r.ldDisp8.rex(0x8b))
|
||||
I64.enc(base.uload32.i64, *r.ldDisp32.rex(0x8b))
|
||||
|
||||
I64.enc(base.sload32.i64, *r.ld.rex(0x63, w=1))
|
||||
I64.enc(base.sload32.i64, *r.ldDisp8.rex(0x63, w=1))
|
||||
I64.enc(base.sload32.i64, *r.ldDisp32.rex(0x63, w=1))
|
||||
|
||||
enc_i32_i64_ld_st(base.uload16, True, r.ld, 0x0f, 0xb7)
|
||||
enc_i32_i64_ld_st(base.uload16, True, r.ldDisp8, 0x0f, 0xb7)
|
||||
enc_i32_i64_ld_st(base.uload16, True, r.ldDisp32, 0x0f, 0xb7)
|
||||
|
||||
enc_i32_i64_ld_st(base.sload16, True, r.ld, 0x0f, 0xbf)
|
||||
enc_i32_i64_ld_st(base.sload16, True, r.ldDisp8, 0x0f, 0xbf)
|
||||
enc_i32_i64_ld_st(base.sload16, True, r.ldDisp32, 0x0f, 0xbf)
|
||||
|
||||
enc_i32_i64_ld_st(base.uload8, True, r.ld, 0x0f, 0xb6)
|
||||
enc_i32_i64_ld_st(base.uload8, True, r.ldDisp8, 0x0f, 0xb6)
|
||||
enc_i32_i64_ld_st(base.uload8, True, r.ldDisp32, 0x0f, 0xb6)
|
||||
|
||||
enc_i32_i64_ld_st(base.sload8, True, r.ld, 0x0f, 0xbe)
|
||||
enc_i32_i64_ld_st(base.sload8, True, r.ldDisp8, 0x0f, 0xbe)
|
||||
enc_i32_i64_ld_st(base.sload8, True, r.ldDisp32, 0x0f, 0xbe)
|
||||
|
||||
#
|
||||
# Call/return
|
||||
#
|
||||
I32.enc(base.call, *r.call_id(0xe8))
|
||||
I32.enc(base.call_indirect.i32, *r.call_r(0xff, rrr=2))
|
||||
I32.enc(base.x_return, *r.ret(0xc3))
|
||||
I64.enc(base.x_return, *r.ret(0xc3))
|
||||
|
||||
#
|
||||
# Branches
|
||||
#
|
||||
I32.enc(base.jump, *r.jmpb(0xeb))
|
||||
I32.enc(base.jump, *r.jmpd(0xe9))
|
||||
I64.enc(base.jump, *r.jmpb(0xeb))
|
||||
I64.enc(base.jump, *r.jmpd(0xe9))
|
||||
|
||||
enc_i32_i64(base.brz, r.tjccb, 0x74)
|
||||
enc_i32_i64(base.brnz, r.tjccb, 0x75)
|
||||
|
||||
#
|
||||
# Trap as ud2
|
||||
#
|
||||
I32.enc(base.trap, *r.noop(0x0f, 0x0b))
|
||||
I64.enc(base.trap, *r.noop(0x0f, 0x0b))
|
||||
|
||||
#
|
||||
# Comparisons
|
||||
#
|
||||
enc_i32_i64(base.icmp, r.icscc, 0x39)
|
||||
|
||||
#
|
||||
# Convert bool to int.
|
||||
#
|
||||
# This assumes that b1 is represented as an 8-bit low register with the value 0
|
||||
# or 1.
|
||||
I32.enc(base.bint.i32.b1, *r.urm_abcd(0x0f, 0xb6))
|
||||
I64.enc(base.bint.i64.b1, *r.urm.rex(0x0f, 0xb6, w=1))
|
||||
I64.enc(base.bint.i64.b1, *r.urm_abcd(0x0f, 0xb6)) # zext to i64 implicit.
|
||||
I64.enc(base.bint.i32.b1, *r.urm.rex(0x0f, 0xb6))
|
||||
I64.enc(base.bint.i32.b1, *r.urm_abcd(0x0f, 0xb6))
|
||||
|
||||
# Numerical conversions.
|
||||
|
||||
# Converting i64 to i32 is a no-op in 64-bit mode.
|
||||
I64.enc(base.ireduce.i32.i64, r.null, 0)
|
||||
I64.enc(base.sextend.i64.i32, *r.urm.rex(0x63, w=1))
|
||||
# A 32-bit register copy clears the high 32 bits.
|
||||
I64.enc(base.uextend.i64.i32, *r.umr.rex(0x89))
|
||||
I64.enc(base.uextend.i64.i32, *r.umr(0x89))
|
||||
|
||||
|
||||
#
|
||||
# Floating point
|
||||
#
|
||||
|
||||
# movd
|
||||
enc_flt(base.bitcast.f32.i32, r.frurm, 0x66, 0x0f, 0x6e)
|
||||
enc_flt(base.bitcast.i32.f32, r.rfumr, 0x66, 0x0f, 0x7e)
|
||||
|
||||
# movq
|
||||
I64.enc(base.bitcast.f64.i64, *r.frurm.rex(0x66, 0x0f, 0x6e, w=1))
|
||||
I64.enc(base.bitcast.i64.f64, *r.rfumr.rex(0x66, 0x0f, 0x7e, w=1))
|
||||
|
||||
# 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_flt(base.fpromote.f64.f32, r.furm, 0xf3, 0x0f, 0x5a)
|
||||
|
||||
# cvtsd2ss
|
||||
enc_flt(base.fdemote.f32.f64, r.furm, 0xf2, 0x0f, 0x5a)
|
||||
|
||||
|
||||
# Binary arithmetic ops.
|
||||
for inst, opc in [
|
||||
(base.fadd, 0x58),
|
||||
(base.fsub, 0x5c),
|
||||
(base.fmul, 0x59),
|
||||
(base.fdiv, 0x5e)]:
|
||||
enc_flt(inst.f32, r.frm, 0xf3, 0x0f, opc)
|
||||
enc_flt(inst.f64, r.frm, 0xf2, 0x0f, opc)
|
||||
|
||||
# Binary bitwise ops.
|
||||
for inst, opc in [
|
||||
(base.band, 0x54),
|
||||
(base.band_not, 0x55),
|
||||
(base.bor, 0x56),
|
||||
(base.bxor, 0x57)]:
|
||||
enc_flt(inst.f32, r.frm, 0x0f, opc)
|
||||
enc_flt(inst.f64, r.frm, 0x0f, opc)
|
||||
@@ -1,49 +0,0 @@
|
||||
"""
|
||||
Supplementary instruction definitions for Intel.
|
||||
|
||||
This module defines additional instructions that are useful only to the Intel
|
||||
target ISA.
|
||||
"""
|
||||
|
||||
from cdsl.operands import Operand
|
||||
from cdsl.typevar import TypeVar
|
||||
from cdsl.instructions import Instruction, InstructionGroup
|
||||
|
||||
|
||||
GROUP = InstructionGroup("x86", "Intel-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)
|
||||
|
||||
GROUP.close()
|
||||
@@ -1,58 +0,0 @@
|
||||
"""
|
||||
Custom legalization patterns for Intel.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.ast import Var
|
||||
from cdsl.xform import Rtl, XFormGroup
|
||||
from base.immediates import imm64
|
||||
from base.types import i32, i64
|
||||
from base import legalize as shared
|
||||
from base import instructions as insts
|
||||
from . import instructions as x86
|
||||
from .defs import ISA
|
||||
|
||||
intel_expand = XFormGroup(
|
||||
'intel_expand',
|
||||
"""
|
||||
Legalize instructions by expansion.
|
||||
|
||||
Use Intel-specific instructions if needed.
|
||||
""",
|
||||
isa=ISA, chain=shared.expand)
|
||||
|
||||
a = Var('a')
|
||||
dead = Var('dead')
|
||||
x = Var('x')
|
||||
xhi = Var('xhi')
|
||||
y = Var('y')
|
||||
|
||||
#
|
||||
# Division and remainder.
|
||||
#
|
||||
intel_expand.legalize(
|
||||
a << insts.udiv(x, y),
|
||||
Rtl(
|
||||
xhi << insts.iconst(imm64(0)),
|
||||
(a, dead) << x86.udivmodx(x, xhi, y)
|
||||
))
|
||||
|
||||
intel_expand.legalize(
|
||||
a << insts.urem(x, y),
|
||||
Rtl(
|
||||
xhi << insts.iconst(imm64(0)),
|
||||
(dead, a) << x86.udivmodx(x, xhi, y)
|
||||
))
|
||||
|
||||
for ty in [i32, i64]:
|
||||
intel_expand.legalize(
|
||||
a << insts.sdiv.bind(ty)(x, y),
|
||||
Rtl(
|
||||
xhi << insts.sshr_imm(x, imm64(ty.lane_bits() - 1)),
|
||||
(a, dead) << x86.sdivmodx(x, xhi, y)
|
||||
))
|
||||
intel_expand.legalize(
|
||||
a << insts.srem.bind(ty)(x, y),
|
||||
Rtl(
|
||||
xhi << insts.sshr_imm(x, imm64(ty.lane_bits() - 1)),
|
||||
(dead, a) << x86.sdivmodx(x, xhi, y)
|
||||
))
|
||||
@@ -1,557 +0,0 @@
|
||||
"""
|
||||
Intel Encoding recipes.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.isa import EncRecipe
|
||||
from cdsl.predicates import IsSignedInt, IsEqual
|
||||
from base.formats import Unary, UnaryImm, Binary, BinaryImm, MultiAry
|
||||
from base.formats import Nullary, Call, IndirectCall, Store, Load
|
||||
from base.formats import IntCompare
|
||||
from base.formats import RegMove, Ternary, Jump, Branch
|
||||
from .registers import GPR, ABCD, FPR
|
||||
|
||||
try:
|
||||
from typing import Tuple, Dict # noqa
|
||||
from cdsl.instructions import InstructionFormat # noqa
|
||||
from cdsl.isa import ConstraintSeq, BranchRange, PredNode # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
# Opcode representation.
|
||||
#
|
||||
# Cretonne requires each recipe to have a single encoding size in bytes, and
|
||||
# Intel 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 <op>.
|
||||
# 8-9: pp, mandatory prefix:
|
||||
# 00 none (Op*)
|
||||
# 01 66 (Mp*)
|
||||
# 10 F3 (Mp*)
|
||||
# 11 F2 (Mp*)
|
||||
# 10-11: mm, opcode map:
|
||||
# 00 <op> (Op1/Mp1)
|
||||
# 01 0F <op> (Op2/Mp2)
|
||||
# 10 0F 38 <op> (Op3/Mp3)
|
||||
# 11 0F 3A <op> (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())
|
||||
|
||||
|
||||
class TailRecipe:
|
||||
"""
|
||||
Generate encoding recipes on demand.
|
||||
|
||||
Intel 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 `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 `intel/binemit.rs` module.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name, # type: str
|
||||
format, # type: InstructionFormat
|
||||
size, # type: int
|
||||
ins, # type: ConstraintSeq
|
||||
outs, # type: ConstraintSeq
|
||||
branch_range=None, # type: BranchRange
|
||||
instp=None, # type: PredNode
|
||||
isap=None, # type: PredNode
|
||||
emit=None # type: str
|
||||
):
|
||||
# type: (...) -> None
|
||||
self.name = name
|
||||
self.format = format
|
||||
self.size = size
|
||||
self.ins = ins
|
||||
self.outs = outs
|
||||
self.branch_range = branch_range
|
||||
self.instp = instp
|
||||
self.isap = isap
|
||||
self.emit = emit
|
||||
|
||||
# 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`.
|
||||
"""
|
||||
rrr = kwargs.get('rrr', 0)
|
||||
w = kwargs.get('w', 0)
|
||||
name, bits = decode_ops(ops, rrr, w)
|
||||
if name not in self.recipes:
|
||||
self.recipes[name] = EncRecipe(
|
||||
name + self.name,
|
||||
self.format,
|
||||
len(ops) + self.size,
|
||||
ins=self.ins,
|
||||
outs=self.outs,
|
||||
branch_range=self.branch_range,
|
||||
instp=self.instp,
|
||||
isap=self.isap,
|
||||
emit=replace_put_op(self.emit, name))
|
||||
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.
|
||||
"""
|
||||
rrr = kwargs.get('rrr', 0)
|
||||
w = kwargs.get('w', 0)
|
||||
name, bits = decode_ops(ops, rrr, w)
|
||||
name = 'Rex' + name
|
||||
if name not in self.recipes:
|
||||
self.recipes[name] = EncRecipe(
|
||||
name + self.name,
|
||||
self.format,
|
||||
1 + len(ops) + self.size,
|
||||
ins=self.ins,
|
||||
outs=self.outs,
|
||||
branch_range=self.branch_range,
|
||||
instp=self.instp,
|
||||
isap=self.isap,
|
||||
emit=replace_put_op(self.emit, name))
|
||||
return (self.recipes[name], bits)
|
||||
|
||||
|
||||
# A null unary instruction that takes a GPR register. Can be used for identity
|
||||
# copies and no-op conversions.
|
||||
null = EncRecipe('null', Unary, size=0, ins=GPR, outs=0, emit='')
|
||||
|
||||
# XX opcode, no ModR/M.
|
||||
noop = TailRecipe(
|
||||
'noop', Nullary, size=0, ins=(), outs=(),
|
||||
emit='PUT_OP(bits, BASE_REX, sink);')
|
||||
|
||||
# XX /r
|
||||
rr = TailRecipe(
|
||||
'rr', Binary, 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, 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. RM form.
|
||||
frm = TailRecipe(
|
||||
'frr', Binary, 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, but for a unary operator with separate input/output register, like
|
||||
# copies. MR form.
|
||||
umr = TailRecipe(
|
||||
'umr', Unary, size=1, ins=GPR, outs=GPR,
|
||||
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, size=1, ins=FPR, outs=GPR,
|
||||
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.
|
||||
urm = TailRecipe(
|
||||
'urm', Unary, 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 input limited to ABCD.
|
||||
urm_abcd = TailRecipe(
|
||||
'urm_abcd', Unary, size=1, ins=ABCD, outs=GPR,
|
||||
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, size=1, ins=FPR, outs=FPR,
|
||||
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, size=1, ins=GPR, outs=FPR,
|
||||
emit='''
|
||||
PUT_OP(bits, rex2(in_reg0, out_reg0), sink);
|
||||
modrm_rr(in_reg0, out_reg0, sink);
|
||||
''')
|
||||
|
||||
# XX /r, for regmove instructions.
|
||||
rmov = TailRecipe(
|
||||
'ur', RegMove, size=1, ins=GPR, outs=(),
|
||||
emit='''
|
||||
PUT_OP(bits, rex2(dst, src), sink);
|
||||
modrm_rr(dst, src, sink);
|
||||
''')
|
||||
|
||||
# XX /n with one arg in %rcx, for shifts.
|
||||
rc = TailRecipe(
|
||||
'rc', Binary, 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, size=1,
|
||||
ins=(GPR.rax, GPR.rdx, GPR), outs=(GPR.rax, GPR.rdx),
|
||||
emit='''
|
||||
PUT_OP(bits, rex1(in_reg2), sink);
|
||||
modrm_r_bits(in_reg2, bits, sink);
|
||||
''')
|
||||
|
||||
# XX /n ib with 8-bit immediate sign-extended.
|
||||
rib = TailRecipe(
|
||||
'rib', BinaryImm, 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.
|
||||
rid = TailRecipe(
|
||||
'rid', BinaryImm, 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.
|
||||
uid = TailRecipe(
|
||||
'uid', UnaryImm, 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.
|
||||
puid = TailRecipe(
|
||||
'uid', UnaryImm, 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 iq unary with 64-bit immediate.
|
||||
puiq = TailRecipe(
|
||||
'uiq', UnaryImm, 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);
|
||||
''')
|
||||
|
||||
#
|
||||
# Store recipes.
|
||||
#
|
||||
|
||||
# XX /r register-indirect store with no offset.
|
||||
st = TailRecipe(
|
||||
'st', Store, size=1, ins=(GPR, GPR), outs=(),
|
||||
instp=IsEqual(Store.offset, 0),
|
||||
emit='''
|
||||
PUT_OP(bits, rex2(in_reg0, in_reg1), sink);
|
||||
modrm_rm(in_reg1, in_reg0, sink);
|
||||
''')
|
||||
|
||||
# XX /r register-indirect store with no offset.
|
||||
# Only ABCD allowed for stored value. This is for byte stores.
|
||||
st_abcd = TailRecipe(
|
||||
'st_abcd', Store, size=1, ins=(ABCD, GPR), outs=(),
|
||||
instp=IsEqual(Store.offset, 0),
|
||||
emit='''
|
||||
PUT_OP(bits, rex2(in_reg0, in_reg1), sink);
|
||||
modrm_rm(in_reg1, in_reg0, sink);
|
||||
''')
|
||||
|
||||
# XX /r register-indirect store with 8-bit offset.
|
||||
stDisp8 = TailRecipe(
|
||||
'stDisp8', Store, size=2, ins=(GPR, GPR), outs=(),
|
||||
instp=IsSignedInt(Store.offset, 8),
|
||||
emit='''
|
||||
PUT_OP(bits, rex2(in_reg0, in_reg1), sink);
|
||||
modrm_disp8(in_reg1, in_reg0, sink);
|
||||
let offset: i32 = offset.into();
|
||||
sink.put1(offset as u8);
|
||||
''')
|
||||
stDisp8_abcd = TailRecipe(
|
||||
'stDisp8_abcd', Store, size=2, ins=(ABCD, GPR), outs=(),
|
||||
instp=IsSignedInt(Store.offset, 8),
|
||||
emit='''
|
||||
PUT_OP(bits, rex2(in_reg0, in_reg1), sink);
|
||||
modrm_disp8(in_reg1, in_reg0, sink);
|
||||
let offset: i32 = offset.into();
|
||||
sink.put1(offset as u8);
|
||||
''')
|
||||
|
||||
# XX /r register-indirect store with 32-bit offset.
|
||||
stDisp32 = TailRecipe(
|
||||
'stDisp32', Store, size=5, ins=(GPR, GPR), outs=(),
|
||||
emit='''
|
||||
PUT_OP(bits, rex2(in_reg0, in_reg1), sink);
|
||||
modrm_disp32(in_reg1, in_reg0, sink);
|
||||
let offset: i32 = offset.into();
|
||||
sink.put4(offset as u32);
|
||||
''')
|
||||
stDisp32_abcd = TailRecipe(
|
||||
'stDisp32_abcd', Store, size=5, ins=(ABCD, GPR), outs=(),
|
||||
emit='''
|
||||
PUT_OP(bits, rex2(in_reg0, in_reg1), sink);
|
||||
modrm_disp32(in_reg1, in_reg0, sink);
|
||||
let offset: i32 = offset.into();
|
||||
sink.put4(offset as u32);
|
||||
''')
|
||||
|
||||
#
|
||||
# Load recipes
|
||||
#
|
||||
|
||||
# XX /r load with no offset.
|
||||
ld = TailRecipe(
|
||||
'ld', Load, size=1, ins=(GPR), outs=(GPR),
|
||||
instp=IsEqual(Load.offset, 0),
|
||||
emit='''
|
||||
PUT_OP(bits, rex2(out_reg0, in_reg0), sink);
|
||||
modrm_rm(in_reg0, out_reg0, sink);
|
||||
''')
|
||||
|
||||
# XX /r load with 8-bit offset.
|
||||
ldDisp8 = TailRecipe(
|
||||
'ldDisp8', Load, size=2, ins=(GPR), outs=(GPR),
|
||||
instp=IsSignedInt(Load.offset, 8),
|
||||
emit='''
|
||||
PUT_OP(bits, rex2(out_reg0, in_reg0), sink);
|
||||
modrm_disp8(in_reg0, out_reg0, sink);
|
||||
let offset: i32 = offset.into();
|
||||
sink.put1(offset as u8);
|
||||
''')
|
||||
|
||||
# XX /r load with 32-bit offset.
|
||||
ldDisp32 = TailRecipe(
|
||||
'ldDisp32', Load, size=5, ins=(GPR), outs=(GPR),
|
||||
instp=IsSignedInt(Load.offset, 32),
|
||||
emit='''
|
||||
PUT_OP(bits, rex2(out_reg0, in_reg0), sink);
|
||||
modrm_disp32(in_reg0, out_reg0, sink);
|
||||
let offset: i32 = offset.into();
|
||||
sink.put4(offset as u32);
|
||||
''')
|
||||
|
||||
#
|
||||
# Call/return
|
||||
#
|
||||
call_id = TailRecipe(
|
||||
'call_id', Call, size=4, ins=(), outs=(),
|
||||
emit='''
|
||||
PUT_OP(bits, BASE_REX, sink);
|
||||
sink.reloc_func(RelocKind::PCRel4.into(), func_ref);
|
||||
sink.put4(0);
|
||||
''')
|
||||
|
||||
call_r = TailRecipe(
|
||||
'call_r', IndirectCall, size=1, ins=GPR, outs=(),
|
||||
emit='''
|
||||
PUT_OP(bits, rex1(in_reg0), sink);
|
||||
modrm_r_bits(in_reg0, bits, sink);
|
||||
''')
|
||||
|
||||
ret = TailRecipe(
|
||||
'ret', MultiAry, size=0, ins=(), outs=(),
|
||||
emit='''
|
||||
PUT_OP(bits, BASE_REX, sink);
|
||||
''')
|
||||
|
||||
#
|
||||
# Branches
|
||||
#
|
||||
jmpb = TailRecipe(
|
||||
'jmpb', Jump, size=1, ins=(), outs=(),
|
||||
branch_range=(2, 8),
|
||||
emit='''
|
||||
PUT_OP(bits, BASE_REX, sink);
|
||||
disp1(destination, func, sink);
|
||||
''')
|
||||
|
||||
jmpd = TailRecipe(
|
||||
'jmpd', Jump, size=4, ins=(), outs=(),
|
||||
branch_range=(5, 32),
|
||||
emit='''
|
||||
PUT_OP(bits, BASE_REX, sink);
|
||||
disp4(destination, func, 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 Cretonne 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(
|
||||
'tjcc', Branch, size=1 + 2, ins=GPR, outs=(),
|
||||
branch_range=(2, 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);
|
||||
''')
|
||||
|
||||
# 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.
|
||||
icscc = TailRecipe(
|
||||
'cscc', IntCompare, 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 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);
|
||||
''')
|
||||
@@ -1,45 +0,0 @@
|
||||
"""
|
||||
Intel register banks.
|
||||
|
||||
While the floating-point registers are straight-forward, the general purpose
|
||||
register bank has a few quirks on Intel architectures. 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
|
||||
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')
|
||||
|
||||
GPR = RegClass(IntRegs)
|
||||
ABCD = GPR[0:4]
|
||||
FPR = RegClass(FloatRegs)
|
||||
|
||||
RegClass.extract_names(globals())
|
||||
@@ -1,47 +0,0 @@
|
||||
"""
|
||||
Intel settings.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from cdsl.settings import SettingGroup, BoolSetting, Preset
|
||||
from cdsl.predicates import And
|
||||
import base.settings as shared
|
||||
from .defs import ISA
|
||||
|
||||
ISA.settings = SettingGroup('intel', parent=shared.group)
|
||||
|
||||
# The has_* settings here correspond to CPUID bits.
|
||||
|
||||
# CPUID.01H:EDX
|
||||
has_sse2 = BoolSetting("SSE2: CPUID.01H:EDX.SSE2[bit 26]")
|
||||
|
||||
# CPUID.01H:ECX
|
||||
has_sse3 = BoolSetting("SSE3: CPUID.01H:ECX.SSE3[bit 0]")
|
||||
has_ssse3 = BoolSetting("SSSE3: CPUID.01H:ECX.SSSE3[bit 9]")
|
||||
has_sse41 = BoolSetting("SSE4.1: CPUID.01H:ECX.SSE4_1[bit 19]")
|
||||
has_sse42 = BoolSetting("SSE4.2: CPUID.01H:ECX.SSE4_2[bit 20]")
|
||||
has_popcnt = BoolSetting("POPCNT: CPUID.01H:ECX.POPCNT[bit 23]")
|
||||
has_avx = BoolSetting("AVX: CPUID.01H:ECX.AVX[bit 28]")
|
||||
|
||||
# CPUID.(EAX=07H, ECX=0H):EBX
|
||||
has_bmi1 = BoolSetting("BMI1: CPUID.(EAX=07H, ECX=0H):EBX.BMI1[bit 3]")
|
||||
has_bmi2 = BoolSetting("BMI2: CPUID.(EAX=07H, ECX=0H):EBX.BMI2[bit 8]")
|
||||
|
||||
# CPUID.EAX=80000001H:ECX
|
||||
has_lzcnt = BoolSetting("LZCNT: CPUID.EAX=80000001H:ECX.LZCNT[bit 5]")
|
||||
|
||||
|
||||
# The use_* settings here are used to determine if a feature can be used.
|
||||
|
||||
use_sse41 = And(has_sse41)
|
||||
use_sse42 = And(has_sse42, use_sse41)
|
||||
use_popcnt = And(has_popcnt, has_sse42)
|
||||
use_bmi1 = And(has_bmi1)
|
||||
use_lzcnt = And(has_lzcnt)
|
||||
|
||||
# Presets corresponding to Intel CPUs.
|
||||
|
||||
nehalem = Preset(
|
||||
has_sse2, has_sse3, has_ssse3, has_sse41, has_sse42, has_popcnt)
|
||||
haswell = Preset(nehalem, has_bmi1, has_lzcnt)
|
||||
|
||||
ISA.settings.close(globals())
|
||||
@@ -1,32 +0,0 @@
|
||||
"""
|
||||
RISC-V Target
|
||||
-------------
|
||||
|
||||
`RISC-V <http://riscv.org/>`_ is an open instruction set architecture
|
||||
originally developed at UC Berkeley. It is a RISC-style ISA with either a
|
||||
32-bit (RV32I) or 64-bit (RV32I) base instruction set and a number of optional
|
||||
extensions:
|
||||
|
||||
RV32M / RV64M
|
||||
Integer multiplication and division.
|
||||
|
||||
RV32A / RV64A
|
||||
Atomics.
|
||||
|
||||
RV32F / RV64F
|
||||
Single-precision IEEE floating point.
|
||||
|
||||
RV32D / RV64D
|
||||
Double-precision IEEE floating point.
|
||||
|
||||
RV32G / RV64G
|
||||
General purpose instruction sets. This represents the union of the I, M, A,
|
||||
F, and D instruction sets listed above.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from . import defs
|
||||
from . import encodings, settings, registers # noqa
|
||||
|
||||
# Re-export the primary target ISA definition.
|
||||
ISA = defs.ISA.finish()
|
||||
@@ -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])
|
||||
|
||||
# CPU modes for 32-bit and 64-bit operation.
|
||||
RV32 = CPUMode('RV32', ISA)
|
||||
RV64 = CPUMode('RV64', ISA)
|
||||
@@ -1,155 +0,0 @@
|
||||
"""
|
||||
RISC-V Encodings.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from base import instructions as base
|
||||
from base.immediates import intcc
|
||||
from .defs import RV32, RV64
|
||||
from .recipes import OPIMM, OPIMM32, OP, OP32, LUI, BRANCH, JALR, JAL
|
||||
from .recipes import LOAD, STORE
|
||||
from .recipes import R, Rshamt, Ricmp, I, Iz, Iicmp, Iret, Icall, Icopy
|
||||
from .recipes import U, UJ, UJcall, SB, SBzero, GPsp, GPfi, Irmov
|
||||
from .settings import use_m
|
||||
from cdsl.ast import Var
|
||||
from base.legalize import narrow, expand
|
||||
|
||||
RV32.legalize_type(
|
||||
default=narrow,
|
||||
i32=expand,
|
||||
f32=expand,
|
||||
f64=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, I, OPIMM(f3))
|
||||
RV64.enc(inst_imm.i64, I, 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, I, 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 cton 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))
|
||||
@@ -1,219 +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, IndirectCall, 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, 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, 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, size=4, ins=(GPR, GPR), outs=GPR,
|
||||
emit='put_r(bits, in_reg0, in_reg1, out_reg0, sink);')
|
||||
|
||||
I = EncRecipe(
|
||||
'I', BinaryImm, 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, 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, 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, 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 an indirect call.
|
||||
Icall = EncRecipe(
|
||||
'Icall', IndirectCall, size=4, ins=GPR, outs=(),
|
||||
emit='''
|
||||
// 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, 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, 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, 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, size=4, ins=(), outs=(), branch_range=(0, 21),
|
||||
emit='''
|
||||
let dest = func.offsets[destination] as i64;
|
||||
let disp = dest - sink.offset() as i64;
|
||||
put_uj(bits, disp, 0, sink);
|
||||
''')
|
||||
|
||||
UJcall = EncRecipe(
|
||||
'UJcall', Call, size=4, ins=(), outs=(),
|
||||
emit='''
|
||||
sink.reloc_func(RelocKind::Call.into(), func_ref);
|
||||
// rd=%x1 is the standard link register.
|
||||
put_uj(bits, 0, 1, sink);
|
||||
''')
|
||||
|
||||
# SB-type branch instructions.
|
||||
SB = EncRecipe(
|
||||
'SB', BranchIcmp, size=4,
|
||||
ins=(GPR, GPR), outs=(),
|
||||
branch_range=(0, 13),
|
||||
emit='''
|
||||
let dest = func.offsets[destination] as i64;
|
||||
let disp = dest - sink.offset() as i64;
|
||||
put_sb(bits, disp, in_reg0, in_reg1, sink);
|
||||
''')
|
||||
|
||||
# SB-type branch instruction with rs2 fixed to zero.
|
||||
SBzero = EncRecipe(
|
||||
'SBzero', Branch, size=4,
|
||||
ins=(GPR), outs=(),
|
||||
branch_range=(0, 13),
|
||||
emit='''
|
||||
let dest = func.offsets[destination] as i64;
|
||||
let disp = dest - sink.offset() as i64;
|
||||
put_sb(bits, disp, in_reg0, 0, sink);
|
||||
''')
|
||||
|
||||
# Spill of a GPR.
|
||||
GPsp = EncRecipe(
|
||||
'GPsp', Unary, size=4,
|
||||
ins=GPR, outs=Stack(GPR),
|
||||
emit='unimplemented!();')
|
||||
|
||||
# Fill of a GPR.
|
||||
GPfi = EncRecipe(
|
||||
'GPfi', Unary, size=4,
|
||||
ins=Stack(GPR), outs=GPR,
|
||||
emit='unimplemented!();')
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -1,4 +0,0 @@
|
||||
[mypy]
|
||||
disallow_untyped_defs = True
|
||||
warn_unused_ignores = True
|
||||
warn_return_any = True
|
||||
@@ -1,49 +0,0 @@
|
||||
"""Definitions for the semantics segment of the Cretonne language."""
|
||||
from cdsl.ti import TypeEnv, ti_rtl, get_type_env
|
||||
|
||||
try:
|
||||
from typing import List, Dict, Tuple # noqa
|
||||
from cdsl.ast import Var # 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:
|
||||
1) For all XForms x \in xforms, there is a Var substitution form src to
|
||||
x.src
|
||||
2) For any possible concrete typing of src there is exactly 1 XForm x
|
||||
in xforms that applies.
|
||||
"""
|
||||
# 0) The source rtl is always a single instruction
|
||||
assert len(src.rtl) == 1
|
||||
|
||||
# 1) For all XForms x, x.src is structurally equivalent to src
|
||||
for x in xforms:
|
||||
assert src.substitution(x.src, {}) is not None,\
|
||||
"XForm {} doesn't describe instruction {}.".format(x, src)
|
||||
|
||||
# 2) Any possible typing for the instruction 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:
|
||||
# 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, inst, matching_xforms)
|
||||
@@ -1,140 +0,0 @@
|
||||
"""
|
||||
Tools to elaborate a given Rtl with concrete types into its semantically
|
||||
equivalent primitive version. Its elaborated primitive version contains only
|
||||
primitive cretonne 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, VarMap # 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
|
||||
|
||||
if x.ti.permits({subst[v]: tv for (v, tv) in typing.items()}):
|
||||
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: VarMap
|
||||
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)
|
||||
@@ -1,89 +0,0 @@
|
||||
"""
|
||||
Cretonne 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
|
||||
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)")
|
||||
|
||||
real = Operand('real', Real, doc="A real cretonne value")
|
||||
fromReal = Operand('fromReal', Real.to_bitvec(),
|
||||
doc="A real cretonne value converted to a BV")
|
||||
|
||||
prim_to_bv = Instruction(
|
||||
'prim_to_bv', r"""
|
||||
Convert an SSA Value to a flat bitvector
|
||||
""",
|
||||
ins=(real), outs=(fromReal))
|
||||
|
||||
# Note that when converting from BV->real values, we use a constraint and not a
|
||||
# derived function. This reflects that fact that to_bitvec() is not a
|
||||
# bijection.
|
||||
prim_from_bv = Instruction(
|
||||
'prim_from_bv', r"""
|
||||
Convert a flat bitvector to a real SSA Value.
|
||||
""",
|
||||
ins=(fromReal), outs=(real))
|
||||
|
||||
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
|
||||
cmp_res = Operand('cmp_res', BV1, doc="Single bit boolean")
|
||||
bvult = Instruction(
|
||||
'bvult', r"""Unsigned bitvector comparison""",
|
||||
ins=(x, y), outs=cmp_res)
|
||||
|
||||
# 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()
|
||||
@@ -1,221 +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 VarMap # 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, VarMap, VarMap) -> List[ExprRef]
|
||||
"""
|
||||
Given:
|
||||
- concrete source Rtl r1
|
||||
- concrete dest Rtl r2
|
||||
- VarMap inp_m mapping r1's non-bitvector inputs to r2
|
||||
- VarMap 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.
|
||||
|
||||
# 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()}
|
||||
dst_m = {v: Var(v.name + ".b", v.get_typevar()) for v in r2.vars()}
|
||||
r1 = r1.copy(src_m)
|
||||
r2 = r2.copy(dst_m)
|
||||
|
||||
# Convert inp_m, out_m in terms of variables with the .a/.b suffixes
|
||||
inp_m = {src_m[k]: dst_m[v] for (k, v) in inp_m.items()}
|
||||
out_m = {src_m[k]: dst_m[v] for (k, v) in out_m.items()}
|
||||
|
||||
# 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 Cretone inputs of r1 and r2
|
||||
args_eq_exp = [] # type: List[ExprRef]
|
||||
|
||||
for v in r1.free_vars():
|
||||
args_eq_exp.append(mk_eq(m1[v], m2[inp_m[v]]))
|
||||
|
||||
# Build an expression for the equality of real Cretone outputs of r1 and r2
|
||||
results_eq_exp = [] # type: List[ExprRef]
|
||||
for (v1, v2) in out_m.items():
|
||||
results_eq_exp.append(mk_eq(m1[v1], m2[v2]))
|
||||
|
||||
# Put the whole query toghether
|
||||
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()}
|
||||
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 = {}
|
||||
out_m = {}
|
||||
|
||||
for v in x.src.vars():
|
||||
if v.is_input():
|
||||
inp_m[src_m[v]] = dst_m[v]
|
||||
elif v.is_output():
|
||||
out_m[src_m[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
|
||||
@@ -1,387 +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
|
||||
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
|
||||
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')
|
||||
bvx = Var('bvx')
|
||||
bvy = Var('bvy')
|
||||
bva = Var('bva')
|
||||
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),
|
||||
bvc_out << bvult.bv32(bva, bvx),
|
||||
a << prim_from_bv.i32(bva),
|
||||
c_out << prim_from_bv.b1(bvc_out)
|
||||
)
|
||||
exp.cleanup_concrete_rtl()
|
||||
|
||||
assert concrete_rtls_eq(sem, exp)
|
||||
@@ -1,179 +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
|
||||
|
||||
try:
|
||||
from typing import Any, List # 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 _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. This is useful for strings defined with doc
|
||||
strings:
|
||||
>>> parse_multiline('\\n hello\\n world\\n')
|
||||
[None, 'hello', 'world']
|
||||
"""
|
||||
lines = s.splitlines()
|
||||
indents = list(i for i in (_indent(l) for l in lines) if i)
|
||||
indent = min(indents) if indents else 0
|
||||
return list(l[indent:] if len(l) > indent else None for l in lines)
|
||||
@@ -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: ...
|
||||
@@ -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: ...
|
||||
@@ -1,12 +0,0 @@
|
||||
from typing import Any
|
||||
|
||||
class Z3Exception(Exception):
|
||||
def __init__(self, a: Any) -> None:
|
||||
self.value = a
|
||||
...
|
||||
|
||||
class ContextObj:
|
||||
...
|
||||
|
||||
class Ast:
|
||||
...
|
||||
@@ -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
|
||||
@@ -1,196 +0,0 @@
|
||||
import doctest
|
||||
import gen_legalizer
|
||||
from unittest import TestCase
|
||||
from srcgen import Formatter
|
||||
from gen_legalizer import get_runtime_typechecks, emit_runtime_typecheck
|
||||
from base.instructions import vselect, vsplit, isplit, iconcat, vconcat, \
|
||||
iconst, b1, icmp, copy, sextend, uextend, ireduce, fdemote, fpromote # noqa
|
||||
from base.legalize import narrow, expand # noqa
|
||||
from base.immediates import intcc # noqa
|
||||
from cdsl.typevar import TypeVar, TypeSet
|
||||
from cdsl.ast import Var, Def # noqa
|
||||
from cdsl.xform import Rtl, XForm # noqa
|
||||
from cdsl.ti import ti_rtl, subst, TypeEnv, get_type_env # noqa
|
||||
from unique_table import UniqueTable
|
||||
from functools import reduce
|
||||
|
||||
try:
|
||||
from typing import Callable, TYPE_CHECKING, Iterable, Any # noqa
|
||||
if TYPE_CHECKING:
|
||||
CheckProducer = Callable[[UniqueTable], str]
|
||||
except ImportError:
|
||||
TYPE_CHECKING = False
|
||||
|
||||
|
||||
def load_tests(loader, tests, ignore):
|
||||
# type: (Any, Any, Any) -> Any
|
||||
tests.addTests(doctest.DocTestSuite(gen_legalizer))
|
||||
return tests
|
||||
|
||||
|
||||
def format_check(typesets, s, *args):
|
||||
# type: (...) -> str
|
||||
def transform(x):
|
||||
# type: (Any) -> str
|
||||
if isinstance(x, TypeSet):
|
||||
return str(typesets.index[x])
|
||||
elif isinstance(x, TypeVar):
|
||||
assert not x.is_derived
|
||||
return x.name
|
||||
else:
|
||||
return str(x)
|
||||
|
||||
dummy_s = s # type: str
|
||||
args = tuple(map(lambda x: transform(x), args))
|
||||
return dummy_s.format(*args)
|
||||
|
||||
|
||||
def typeset_check(v, ts):
|
||||
# type: (Var, TypeSet) -> CheckProducer
|
||||
return lambda typesets: format_check(
|
||||
typesets,
|
||||
'let predicate = predicate && TYPE_SETS[{}].contains(typeof_{});\n',
|
||||
ts, v)
|
||||
|
||||
|
||||
def equiv_check(tv1, tv2):
|
||||
# type: (str, str) -> CheckProducer
|
||||
return lambda typesets: format_check(
|
||||
typesets,
|
||||
'let predicate = predicate && match ({}, {}) {{\n'
|
||||
' (Some(a), Some(b)) => a == b,\n'
|
||||
' _ => false,\n'
|
||||
'}};\n', tv1, tv2)
|
||||
|
||||
|
||||
def wider_check(tv1, tv2):
|
||||
# type: (str, str) -> CheckProducer
|
||||
return lambda typesets: format_check(
|
||||
typesets,
|
||||
'let predicate = predicate && match ({}, {}) {{\n'
|
||||
' (Some(a), Some(b)) => a.wider_or_equal(b),\n'
|
||||
' _ => false,\n'
|
||||
'}};\n', tv1, tv2)
|
||||
|
||||
|
||||
def sequence(*args):
|
||||
# type: (...) -> CheckProducer
|
||||
dummy = args # type: Iterable[CheckProducer]
|
||||
|
||||
def sequenceF(typesets):
|
||||
# type: (UniqueTable) -> str
|
||||
def strconcat(acc, el):
|
||||
# type: (str, CheckProducer) -> str
|
||||
return acc + el(typesets)
|
||||
|
||||
return reduce(strconcat, dummy, "")
|
||||
return sequenceF
|
||||
|
||||
|
||||
class TestRuntimeChecks(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
# type: () -> None
|
||||
self.v0 = Var("v0")
|
||||
self.v1 = Var("v1")
|
||||
self.v2 = Var("v2")
|
||||
self.v3 = Var("v3")
|
||||
self.v4 = Var("v4")
|
||||
self.v5 = Var("v5")
|
||||
self.v6 = Var("v6")
|
||||
self.v7 = Var("v7")
|
||||
self.v8 = Var("v8")
|
||||
self.v9 = Var("v9")
|
||||
self.imm0 = Var("imm0")
|
||||
self.IxN_nonscalar = TypeVar("IxN_nonscalar", "", ints=True,
|
||||
scalars=False, simd=True)
|
||||
self.TxN = TypeVar("TxN", "", ints=True, bools=True, floats=True,
|
||||
scalars=False, simd=True)
|
||||
self.b1 = TypeVar.singleton(b1)
|
||||
|
||||
def check_yo_check(self, xform, expected_f):
|
||||
# type: (XForm, CheckProducer) -> None
|
||||
fmt = Formatter()
|
||||
type_sets = UniqueTable()
|
||||
for check in get_runtime_typechecks(xform):
|
||||
emit_runtime_typecheck(check, fmt, type_sets)
|
||||
|
||||
# Remove comments
|
||||
got = "".join([l for l in fmt.lines if not l.strip().startswith("//")])
|
||||
expected = expected_f(type_sets)
|
||||
self.assertEqual(got, expected)
|
||||
|
||||
def test_width_check(self):
|
||||
# type: () -> None
|
||||
x = XForm(Rtl(self.v0 << copy(self.v1)),
|
||||
Rtl((self.v2, self.v3) << isplit(self.v1),
|
||||
self.v0 << iconcat(self.v2, self.v3)))
|
||||
|
||||
WideInt = TypeSet(lanes=(1, 256), ints=(16, 64))
|
||||
self.check_yo_check(x, typeset_check(self.v1, WideInt))
|
||||
|
||||
def test_lanes_check(self):
|
||||
# type: () -> None
|
||||
x = XForm(Rtl(self.v0 << copy(self.v1)),
|
||||
Rtl((self.v2, self.v3) << vsplit(self.v1),
|
||||
self.v0 << vconcat(self.v2, self.v3)))
|
||||
|
||||
WideVec = TypeSet(lanes=(2, 256), ints=(8, 64), floats=(32, 64),
|
||||
bools=(1, 64))
|
||||
self.check_yo_check(x, typeset_check(self.v1, WideVec))
|
||||
|
||||
def test_vselect_imm(self):
|
||||
# type: () -> None
|
||||
ts = TypeSet(lanes=(2, 256), ints=True, floats=True, bools=(8, 64))
|
||||
r = Rtl(
|
||||
self.v0 << iconst(self.imm0),
|
||||
self.v1 << icmp(intcc.eq, self.v2, self.v0),
|
||||
self.v5 << vselect(self.v1, self.v3, self.v4),
|
||||
)
|
||||
x = XForm(r, r)
|
||||
tv2_exp = 'Some({}).map(|t: Type| -> t.as_bool())'\
|
||||
.format(self.v2.get_typevar().name)
|
||||
tv3_exp = 'Some({}).map(|t: Type| -> t.as_bool())'\
|
||||
.format(self.v3.get_typevar().name)
|
||||
|
||||
self.check_yo_check(
|
||||
x, sequence(typeset_check(self.v3, ts),
|
||||
equiv_check(tv2_exp, tv3_exp)))
|
||||
|
||||
def test_reduce_extend(self):
|
||||
# type: () -> None
|
||||
r = Rtl(
|
||||
self.v1 << uextend(self.v0),
|
||||
self.v2 << ireduce(self.v1),
|
||||
self.v3 << sextend(self.v2),
|
||||
)
|
||||
x = XForm(r, r)
|
||||
|
||||
tv0_exp = 'Some({})'.format(self.v0.get_typevar().name)
|
||||
tv1_exp = 'Some({})'.format(self.v1.get_typevar().name)
|
||||
tv2_exp = 'Some({})'.format(self.v2.get_typevar().name)
|
||||
tv3_exp = 'Some({})'.format(self.v3.get_typevar().name)
|
||||
|
||||
self.check_yo_check(
|
||||
x, sequence(wider_check(tv1_exp, tv0_exp),
|
||||
wider_check(tv1_exp, tv2_exp),
|
||||
wider_check(tv3_exp, tv2_exp)))
|
||||
|
||||
def test_demote_promote(self):
|
||||
# type: () -> None
|
||||
r = Rtl(
|
||||
self.v1 << fpromote(self.v0),
|
||||
self.v2 << fdemote(self.v1),
|
||||
self.v3 << fpromote(self.v2),
|
||||
)
|
||||
x = XForm(r, r)
|
||||
|
||||
tv0_exp = 'Some({})'.format(self.v0.get_typevar().name)
|
||||
tv1_exp = 'Some({})'.format(self.v1.get_typevar().name)
|
||||
tv2_exp = 'Some({})'.format(self.v2.get_typevar().name)
|
||||
tv3_exp = 'Some({})'.format(self.v3.get_typevar().name)
|
||||
|
||||
self.check_yo_check(
|
||||
x, sequence(wider_check(tv1_exp, tv0_exp),
|
||||
wider_check(tv1_exp, tv2_exp),
|
||||
wider_check(tv3_exp, tv2_exp)))
|
||||
@@ -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
|
||||
@@ -1,77 +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
|
||||
seq = tuple(seq)
|
||||
if seq in self.index:
|
||||
return self.index[seq]
|
||||
|
||||
idx = len(self.table)
|
||||
self.table.extend(seq)
|
||||
|
||||
# Add seq and all sub-sequences to `index`.
|
||||
for length in range(1, len(seq) + 1):
|
||||
for offset in range(len(seq) - length + 1):
|
||||
self.index[seq[offset:offset+length]] = idx + offset
|
||||
|
||||
return idx
|
||||
@@ -1,213 +0,0 @@
|
||||
//! Common helper code for ABI lowering.
|
||||
//!
|
||||
//! This module provides functions and data structures that are useful for implementing the
|
||||
//! `TargetIsa::legalize_signature()` method.
|
||||
|
||||
use ir::{ArgumentLoc, ArgumentType, ArgumentExtension, Type};
|
||||
use std::cmp::Ordering;
|
||||
|
||||
/// Legalization action to perform on a single argument or return value when converting a
|
||||
/// signature.
|
||||
///
|
||||
/// An argument may go through a sequence of legalization steps before it reaches the final
|
||||
/// `Assign` action.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum ArgAction {
|
||||
/// Assign the argument to the given location.
|
||||
Assign(ArgumentLoc),
|
||||
|
||||
/// Convert the argument, then call again.
|
||||
///
|
||||
/// This action can split an integer type into two smaller integer arguments, or it can split a
|
||||
/// SIMD vector into halves.
|
||||
Convert(ValueConversion),
|
||||
}
|
||||
|
||||
impl From<ArgumentLoc> for ArgAction {
|
||||
fn from(x: ArgumentLoc) -> ArgAction {
|
||||
ArgAction::Assign(x)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ValueConversion> for ArgAction {
|
||||
fn from(x: ValueConversion) -> ArgAction {
|
||||
ArgAction::Convert(x)
|
||||
}
|
||||
}
|
||||
|
||||
/// Legalization action to be applied to a value that is being passed to or from a legalized ABI.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum ValueConversion {
|
||||
/// Split an integer types into low and high parts, using `isplit`.
|
||||
IntSplit,
|
||||
|
||||
/// Split a vector type into halves with identical lane types, using `vsplit`.
|
||||
VectorSplit,
|
||||
|
||||
/// Bit-cast to an integer type of the same size.
|
||||
IntBits,
|
||||
|
||||
/// Sign-extend integer value to the required type.
|
||||
Sext(Type),
|
||||
|
||||
/// Unsigned zero-extend value to the required type.
|
||||
Uext(Type),
|
||||
}
|
||||
|
||||
impl ValueConversion {
|
||||
/// Apply this conversion to a type, return the converted type.
|
||||
pub fn apply(self, ty: Type) -> Type {
|
||||
match self {
|
||||
ValueConversion::IntSplit => ty.half_width().expect("Integer type too small to split"),
|
||||
ValueConversion::VectorSplit => ty.half_vector().expect("Not a vector"),
|
||||
ValueConversion::IntBits => Type::int(ty.bits()).expect("Bad integer size"),
|
||||
ValueConversion::Sext(nty) => nty,
|
||||
ValueConversion::Uext(nty) => nty,
|
||||
}
|
||||
}
|
||||
|
||||
/// Is this a split conversion that results in two arguments?
|
||||
pub fn is_split(self) -> bool {
|
||||
match self {
|
||||
ValueConversion::IntSplit => true,
|
||||
ValueConversion::VectorSplit => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Common trait for assigning arguments to registers or stack locations.
|
||||
///
|
||||
/// This will be implemented by individual ISAs.
|
||||
pub trait ArgAssigner {
|
||||
/// Pick an assignment action for function argument (or return value) `arg`.
|
||||
fn assign(&mut self, arg: &ArgumentType) -> ArgAction;
|
||||
}
|
||||
|
||||
/// Legalize the arguments in `args` using the given argument assigner.
|
||||
///
|
||||
/// This function can be used for both arguments and return values.
|
||||
pub fn legalize_args<AA: ArgAssigner>(args: &mut Vec<ArgumentType>, aa: &mut AA) {
|
||||
// Iterate over the arguments.
|
||||
// We may need to mutate the vector in place, so don't use a normal iterator, and clone the
|
||||
// argument to avoid holding a reference.
|
||||
let mut argno = 0;
|
||||
while let Some(arg) = args.get(argno).cloned() {
|
||||
// Leave the pre-assigned arguments alone.
|
||||
// We'll assume that they don't interfere with our assignments.
|
||||
if arg.location.is_assigned() {
|
||||
argno += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
match aa.assign(&arg) {
|
||||
// Assign argument to a location and move on to the next one.
|
||||
ArgAction::Assign(loc) => {
|
||||
args[argno].location = loc;
|
||||
argno += 1;
|
||||
}
|
||||
// Split this argument into two smaller ones. Then revisit both.
|
||||
ArgAction::Convert(conv) => {
|
||||
let new_arg = ArgumentType {
|
||||
value_type: conv.apply(arg.value_type),
|
||||
..arg
|
||||
};
|
||||
args[argno].value_type = new_arg.value_type;
|
||||
if conv.is_split() {
|
||||
args.insert(argno + 1, new_arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine the right action to take when passing a `have` value type to a call signature where
|
||||
/// the next argument is `arg` which has a different value type.
|
||||
///
|
||||
/// The signature legalization process in `legalize_args` above can replace a single argument value
|
||||
/// with multiple arguments of smaller types. It can also change the type of an integer argument to
|
||||
/// a larger integer type, requiring the smaller value to be sign- or zero-extended.
|
||||
///
|
||||
/// The legalizer needs to repair the values at all ABI boundaries:
|
||||
///
|
||||
/// - Incoming function arguments to the entry EBB.
|
||||
/// - Function arguments passed to a call.
|
||||
/// - Return values from a call.
|
||||
/// - Return values passed to a return instruction.
|
||||
///
|
||||
/// The `legalize_abi_value` function helps the legalizer with the process. When the legalizer
|
||||
/// needs to pass a pre-legalized `have` argument, but the ABI argument `arg` has a different value
|
||||
/// type, `legalize_abi_value(have, arg)` tells the legalizer how to create the needed value type
|
||||
/// for the argument.
|
||||
///
|
||||
/// It may be necessary to call `legalize_abi_value` more than once for a given argument before the
|
||||
/// desired argument type appears. This will happen when a vector or integer type needs to be split
|
||||
/// more than once, for example.
|
||||
pub fn legalize_abi_value(have: Type, arg: &ArgumentType) -> ValueConversion {
|
||||
let have_bits = have.bits();
|
||||
let arg_bits = arg.value_type.bits();
|
||||
|
||||
match have_bits.cmp(&arg_bits) {
|
||||
// We have fewer bits than the ABI argument.
|
||||
Ordering::Less => {
|
||||
assert!(have.is_int() && arg.value_type.is_int(),
|
||||
"Can only extend integer values");
|
||||
match arg.extension {
|
||||
ArgumentExtension::Uext => ValueConversion::Uext(arg.value_type),
|
||||
ArgumentExtension::Sext => ValueConversion::Sext(arg.value_type),
|
||||
_ => panic!("No argument extension specified"),
|
||||
}
|
||||
}
|
||||
// We have the same number of bits as the argument.
|
||||
Ordering::Equal => {
|
||||
// This must be an integer vector that is split and then extended.
|
||||
assert!(arg.value_type.is_int());
|
||||
assert!(!have.is_scalar());
|
||||
ValueConversion::VectorSplit
|
||||
}
|
||||
// We have more bits than the argument.
|
||||
Ordering::Greater => {
|
||||
if have.is_scalar() {
|
||||
if have.is_float() {
|
||||
// Convert a float to int so it can be split the next time.
|
||||
// ARM would do this to pass an `f64` in two registers.
|
||||
ValueConversion::IntBits
|
||||
} else {
|
||||
ValueConversion::IntSplit
|
||||
}
|
||||
} else {
|
||||
ValueConversion::VectorSplit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ir::types;
|
||||
use ir::ArgumentType;
|
||||
|
||||
#[test]
|
||||
fn legalize() {
|
||||
let mut arg = ArgumentType::new(types::I32);
|
||||
|
||||
assert_eq!(legalize_abi_value(types::I64X2, &arg),
|
||||
ValueConversion::VectorSplit);
|
||||
assert_eq!(legalize_abi_value(types::I64, &arg),
|
||||
ValueConversion::IntSplit);
|
||||
|
||||
// Vector of integers is broken down, then sign-extended.
|
||||
arg.extension = ArgumentExtension::Sext;
|
||||
assert_eq!(legalize_abi_value(types::I16X4, &arg),
|
||||
ValueConversion::VectorSplit);
|
||||
assert_eq!(legalize_abi_value(types::I16.by(2).unwrap(), &arg),
|
||||
ValueConversion::VectorSplit);
|
||||
assert_eq!(legalize_abi_value(types::I16, &arg),
|
||||
ValueConversion::Sext(types::I32));
|
||||
|
||||
// 64-bit float is split as an integer.
|
||||
assert_eq!(legalize_abi_value(types::F64, &arg),
|
||||
ValueConversion::IntBits);
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
//! Code sink that writes binary machine code into contiguous memory.
|
||||
//!
|
||||
//! The `CodeSink` trait is the most general way of extracting binary machine code from Cretonne,
|
||||
//! and it is implemented by things like the `test binemit` file test driver to generate
|
||||
//! hexadecimal machine code. The `CodeSink` has some undesirable performance properties because of
|
||||
//! the dual abstraction: `TargetIsa` is a trait object implemented by each supported ISA, so it
|
||||
//! can't have any generic functions that could be specialized for each `CodeSink` implementation.
|
||||
//! This results in many virtual function callbacks (one per `put*` call) when
|
||||
//! `TargetIsa::emit_inst()` is used.
|
||||
//!
|
||||
//! The `MemoryCodeSink` type fixes the performance problem because it is a type known to
|
||||
//! `TargetIsa` so it can specialize its machine code generation for the type. The trade-off is
|
||||
//! that a `MemoryCodeSink` will always write binary machine code to raw memory. It forwards any
|
||||
//! relocations to a `RelocSink` trait object. Relocations are less frequent than the
|
||||
//! `CodeSink::put*` methods, so the performance impact of the virtual callbacks is less severe.
|
||||
|
||||
use ir::{Ebb, FuncRef, JumpTable};
|
||||
use super::{CodeSink, CodeOffset, Reloc};
|
||||
use std::ptr::write_unaligned;
|
||||
|
||||
/// A `CodeSink` that writes binary machine code directly into memory.
|
||||
///
|
||||
/// A `MemoryCodeSink` object should be used when emitting a Cretonne IL function into executable
|
||||
/// memory. It writes machine code directly to a raw pointer without any bounds checking, so make
|
||||
/// sure to allocate enough memory for the whole function. The number of bytes required is returned
|
||||
/// by the `Context::compile()` function.
|
||||
///
|
||||
/// Any relocations in the function are forwarded to the `RelocSink` trait object.
|
||||
///
|
||||
/// Note that `MemoryCodeSink` writes multi-byte values in the native byte order of the host. This
|
||||
/// is not the right thing to do for cross compilation.
|
||||
pub struct MemoryCodeSink<'a> {
|
||||
data: *mut u8,
|
||||
offset: isize,
|
||||
relocs: &'a mut RelocSink,
|
||||
}
|
||||
|
||||
impl<'a> MemoryCodeSink<'a> {
|
||||
/// Create a new memory code sink that writes a function to the memory pointed to by `data`.
|
||||
pub fn new(data: *mut u8, relocs: &mut RelocSink) -> MemoryCodeSink {
|
||||
MemoryCodeSink {
|
||||
data,
|
||||
offset: 0,
|
||||
relocs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for receiving relocations for code that is emitted directly into memory.
|
||||
pub trait RelocSink {
|
||||
/// Add a relocation referencing an EBB at the current offset.
|
||||
fn reloc_ebb(&mut self, CodeOffset, Reloc, Ebb);
|
||||
|
||||
/// Add a relocation referencing an external function at the current offset.
|
||||
fn reloc_func(&mut self, CodeOffset, Reloc, FuncRef);
|
||||
|
||||
/// Add a relocation referencing a jump table.
|
||||
fn reloc_jt(&mut self, CodeOffset, Reloc, JumpTable);
|
||||
}
|
||||
|
||||
impl<'a> CodeSink for MemoryCodeSink<'a> {
|
||||
fn offset(&self) -> CodeOffset {
|
||||
self.offset as CodeOffset
|
||||
}
|
||||
|
||||
fn put1(&mut self, x: u8) {
|
||||
unsafe {
|
||||
write_unaligned(self.data.offset(self.offset), x);
|
||||
}
|
||||
self.offset += 1;
|
||||
}
|
||||
|
||||
fn put2(&mut self, x: u16) {
|
||||
unsafe {
|
||||
write_unaligned(self.data.offset(self.offset) as *mut u16, x);
|
||||
}
|
||||
self.offset += 2;
|
||||
}
|
||||
|
||||
fn put4(&mut self, x: u32) {
|
||||
unsafe {
|
||||
write_unaligned(self.data.offset(self.offset) as *mut u32, x);
|
||||
}
|
||||
self.offset += 4;
|
||||
}
|
||||
|
||||
fn put8(&mut self, x: u64) {
|
||||
unsafe {
|
||||
write_unaligned(self.data.offset(self.offset) as *mut u64, x);
|
||||
}
|
||||
self.offset += 8;
|
||||
}
|
||||
|
||||
fn reloc_ebb(&mut self, rel: Reloc, ebb: Ebb) {
|
||||
let ofs = self.offset();
|
||||
self.relocs.reloc_ebb(ofs, rel, ebb);
|
||||
}
|
||||
|
||||
fn reloc_func(&mut self, rel: Reloc, func: FuncRef) {
|
||||
let ofs = self.offset();
|
||||
self.relocs.reloc_func(ofs, rel, func);
|
||||
}
|
||||
|
||||
fn reloc_jt(&mut self, rel: Reloc, jt: JumpTable) {
|
||||
let ofs = self.offset();
|
||||
self.relocs.reloc_jt(ofs, rel, jt);
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
//! Binary machine code emission.
|
||||
//!
|
||||
//! The `binemit` module contains code for translating Cretonne's intermediate representation into
|
||||
//! binary machine code.
|
||||
|
||||
mod relaxation;
|
||||
mod memorysink;
|
||||
|
||||
pub use self::relaxation::relax_branches;
|
||||
pub use self::memorysink::{MemoryCodeSink, RelocSink};
|
||||
|
||||
use ir::{Ebb, FuncRef, JumpTable, Function, Inst};
|
||||
use regalloc::RegDiversions;
|
||||
|
||||
/// Offset in bytes from the beginning of the function.
|
||||
///
|
||||
/// Cretonne can be used as a cross compiler, so we don't want to use a type like `usize` which
|
||||
/// depends on the *host* platform, not the *target* platform.
|
||||
pub type CodeOffset = u32;
|
||||
|
||||
/// Relocation kinds depend on the current ISA.
|
||||
pub struct Reloc(pub u16);
|
||||
|
||||
/// Abstract interface for adding bytes to the code segment.
|
||||
///
|
||||
/// A `CodeSink` will receive all of the machine code for a function. It also accepts relocations
|
||||
/// which are locations in the code section that need to be fixed up when linking.
|
||||
pub trait CodeSink {
|
||||
/// Get the current position.
|
||||
fn offset(&self) -> CodeOffset;
|
||||
|
||||
/// Add 1 byte to the code section.
|
||||
fn put1(&mut self, u8);
|
||||
|
||||
/// Add 2 bytes to the code section.
|
||||
fn put2(&mut self, u16);
|
||||
|
||||
/// Add 4 bytes to the code section.
|
||||
fn put4(&mut self, u32);
|
||||
|
||||
/// Add 8 bytes to the code section.
|
||||
fn put8(&mut self, u64);
|
||||
|
||||
/// Add a relocation referencing an EBB at the current offset.
|
||||
fn reloc_ebb(&mut self, Reloc, Ebb);
|
||||
|
||||
/// Add a relocation referencing an external function at the current offset.
|
||||
fn reloc_func(&mut self, Reloc, FuncRef);
|
||||
|
||||
/// Add a relocation referencing a jump table.
|
||||
fn reloc_jt(&mut self, Reloc, JumpTable);
|
||||
}
|
||||
|
||||
/// Report a bad encoding error.
|
||||
#[inline(never)]
|
||||
pub fn bad_encoding(func: &Function, inst: Inst) -> ! {
|
||||
panic!("Bad encoding {} for {}",
|
||||
func.encodings[inst],
|
||||
func.dfg.display_inst(inst, None));
|
||||
}
|
||||
|
||||
/// Emit a function to `sink`, given an instruction emitter function.
|
||||
///
|
||||
/// This function is called from the `TargetIsa::emit_function()` implementations with the
|
||||
/// appropriate instruction emitter.
|
||||
pub fn emit_function<CS, EI>(func: &Function, emit_inst: EI, sink: &mut CS)
|
||||
where CS: CodeSink,
|
||||
EI: Fn(&Function, Inst, &mut RegDiversions, &mut CS)
|
||||
{
|
||||
let mut divert = RegDiversions::new();
|
||||
for ebb in func.layout.ebbs() {
|
||||
divert.clear();
|
||||
assert_eq!(func.offsets[ebb], sink.offset());
|
||||
for inst in func.layout.ebb_insts(ebb) {
|
||||
emit_inst(func, inst, &mut divert, sink);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
//! Branch relaxation and offset computation.
|
||||
//!
|
||||
//! # EBB header offsets
|
||||
//!
|
||||
//! Before we can generate binary machine code for branch instructions, we need to know the final
|
||||
//! offsets of all the EBB headers in the function. This information is encoded in the
|
||||
//! `func.offsets` table.
|
||||
//!
|
||||
//! # Branch relaxation
|
||||
//!
|
||||
//! Branch relaxation is the process of ensuring that all branches in the function have enough
|
||||
//! range to encode their destination. It is common to have multiple branch encodings in an ISA.
|
||||
//! For example, Intel branches can have either an 8-bit or a 32-bit displacement.
|
||||
//!
|
||||
//! On RISC architectures, it can happen that conditional branches have a shorter range than
|
||||
//! unconditional branches:
|
||||
//!
|
||||
//! ```cton
|
||||
//! brz v1, ebb17
|
||||
//! ```
|
||||
//!
|
||||
//! can be transformed into:
|
||||
//!
|
||||
//! ```cton
|
||||
//! brnz v1, ebb23
|
||||
//! jump ebb17
|
||||
//! ebb23:
|
||||
//! ```
|
||||
|
||||
use binemit::CodeOffset;
|
||||
use cursor::{Cursor, FuncCursor};
|
||||
use ir::{Function, InstructionData, Opcode};
|
||||
use isa::{TargetIsa, EncInfo};
|
||||
use iterators::IteratorExtras;
|
||||
use result::CtonError;
|
||||
|
||||
/// Relax branches and compute the final layout of EBB headers in `func`.
|
||||
///
|
||||
/// Fill in the `func.offsets` table so the function is ready for binary emission.
|
||||
pub fn relax_branches(func: &mut Function, isa: &TargetIsa) -> Result<CodeOffset, CtonError> {
|
||||
let encinfo = isa.encoding_info();
|
||||
|
||||
// Clear all offsets so we can recognize EBBs that haven't been visited yet.
|
||||
func.offsets.clear();
|
||||
func.offsets.resize(func.dfg.num_ebbs());
|
||||
|
||||
// Start by inserting fall through instructions.
|
||||
fallthroughs(func);
|
||||
|
||||
let mut offset = 0;
|
||||
|
||||
// The relaxation algorithm iterates to convergence.
|
||||
let mut go_again = true;
|
||||
while go_again {
|
||||
go_again = false;
|
||||
offset = 0;
|
||||
|
||||
// Visit all instructions in layout order
|
||||
let mut cur = FuncCursor::new(func);
|
||||
while let Some(ebb) = cur.next_ebb() {
|
||||
// Record the offset for `ebb` and make sure we iterate until offsets are stable.
|
||||
if cur.func.offsets[ebb] != offset {
|
||||
assert!(cur.func.offsets[ebb] < offset,
|
||||
"Code shrinking during relaxation");
|
||||
cur.func.offsets[ebb] = offset;
|
||||
go_again = true;
|
||||
}
|
||||
|
||||
while let Some(inst) = cur.next_inst() {
|
||||
let enc = cur.func.encodings.get_or_default(inst);
|
||||
let size = encinfo.bytes(enc);
|
||||
|
||||
// See if this might be a branch that is out of range.
|
||||
if let Some(range) = encinfo.branch_range(enc) {
|
||||
if let Some(dest) = cur.func.dfg[inst].branch_destination() {
|
||||
let dest_offset = cur.func.offsets[dest];
|
||||
if !range.contains(offset, dest_offset) {
|
||||
// This is an out-of-range branch.
|
||||
// Relax it unless the destination offset has not been computed yet.
|
||||
if dest_offset != 0 || Some(dest) == cur.func.layout.entry_block() {
|
||||
offset += relax_branch(&mut cur, offset, dest_offset, &encinfo);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
offset += size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(offset)
|
||||
}
|
||||
|
||||
/// Convert `jump` instructions to `fallthrough` instructions where possible and verify that any
|
||||
/// existing `fallthrough` instructions are correct.
|
||||
fn fallthroughs(func: &mut Function) {
|
||||
for (ebb, succ) in func.layout.ebbs().adjacent_pairs() {
|
||||
let term = func.layout.last_inst(ebb).expect("EBB has no terminator.");
|
||||
if let InstructionData::Jump {
|
||||
ref mut opcode,
|
||||
destination,
|
||||
..
|
||||
} = func.dfg[term] {
|
||||
match *opcode {
|
||||
Opcode::Fallthrough => {
|
||||
// Somebody used a fall-through instruction before the branch relaxation pass.
|
||||
// Make sure it is correct, i.e. the destination is the layout successor.
|
||||
assert_eq!(destination, succ, "Illegal fall-through in {}", ebb)
|
||||
}
|
||||
Opcode::Jump => {
|
||||
// If this is a jump to the successor EBB, change it to a fall-through.
|
||||
if destination == succ {
|
||||
*opcode = Opcode::Fallthrough;
|
||||
func.encodings[term] = Default::default();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Relax the branch instruction at `pos` so it can cover the range `offset - dest_offset`.
|
||||
///
|
||||
/// Return the size of the replacement instructions up to and including the location where `pos` is
|
||||
/// left.
|
||||
fn relax_branch(cur: &mut FuncCursor,
|
||||
offset: CodeOffset,
|
||||
dest_offset: CodeOffset,
|
||||
encinfo: &EncInfo)
|
||||
-> CodeOffset {
|
||||
let inst = cur.current_inst().unwrap();
|
||||
dbg!("Relaxing [{}] {} for {:#x}-{:#x} range",
|
||||
encinfo.display(cur.func.encodings[inst]),
|
||||
cur.func.dfg.display_inst(inst, None),
|
||||
offset,
|
||||
dest_offset);
|
||||
unimplemented!();
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
//! Small Bitset
|
||||
//!
|
||||
//! This module defines a struct BitSet<T> encapsulating a bitset built over the type T.
|
||||
//! T is intended to be a primitive unsigned type. Currently it can be any type between u8 and u32
|
||||
//!
|
||||
//! If you would like to add support for larger bitsets in the future, you need to change the trait
|
||||
//! bound Into<u32> and the u32 in the implementation of max_bits()
|
||||
use std::mem::size_of;
|
||||
use std::ops::{Shl, BitOr, Sub, Add};
|
||||
use std::convert::{Into, From};
|
||||
|
||||
/// A small bitset built on a single primitive integer type
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct BitSet<T>(pub T);
|
||||
|
||||
impl<T> BitSet<T>
|
||||
where T: Into<u32> + From<u8> + BitOr<T, Output = T> + Shl<u8, Output = T> + Sub<T, Output=T> +
|
||||
Add<T, Output=T> + PartialEq + Copy
|
||||
{
|
||||
/// Maximum number of bits supported by this BitSet instance
|
||||
pub fn bits() -> usize {
|
||||
size_of::<T>() * 8
|
||||
}
|
||||
|
||||
/// Maximum number of bits supported by any bitset instance atm.
|
||||
pub fn max_bits() -> usize {
|
||||
size_of::<u32>() * 8
|
||||
}
|
||||
|
||||
/// Check if this BitSet contains the number num
|
||||
pub fn contains(&self, num: u8) -> bool {
|
||||
assert!((num as usize) < Self::bits());
|
||||
assert!((num as usize) < Self::max_bits());
|
||||
return self.0.into() & (1 << num) != 0;
|
||||
}
|
||||
|
||||
/// Return the smallest number contained in the bitset or None if empty
|
||||
pub fn min(&self) -> Option<u8> {
|
||||
if self.0.into() == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(self.0.into().trailing_zeros() as u8)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the largest number contained in the bitset or None if empty
|
||||
pub fn max(&self) -> Option<u8> {
|
||||
if self.0.into() == 0 {
|
||||
None
|
||||
} else {
|
||||
let leading_zeroes = self.0.into().leading_zeros() as usize;
|
||||
Some((Self::max_bits() - leading_zeroes - 1) as u8)
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a BitSet with the half-open range [lo,hi) filled in
|
||||
pub fn from_range(lo: u8, hi: u8) -> BitSet<T> {
|
||||
assert!(lo <= hi);
|
||||
assert!((hi as usize) <= Self::bits());
|
||||
let one : T = T::from(1);
|
||||
// I can't just do (one << hi) - one here as the shift may overflow
|
||||
let hi_rng = if hi >= 1 {
|
||||
(one << (hi-1)) + ((one << (hi-1)) - one)
|
||||
} else {
|
||||
T::from(0)
|
||||
};
|
||||
|
||||
let lo_rng = (one << lo) - one;
|
||||
|
||||
BitSet(hi_rng - lo_rng)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn contains() {
|
||||
let s = BitSet::<u8>(255);
|
||||
for i in 0..7 {
|
||||
assert!(s.contains(i));
|
||||
}
|
||||
|
||||
let s1 = BitSet::<u8>(0);
|
||||
for i in 0..7 {
|
||||
assert!(!s1.contains(i));
|
||||
}
|
||||
|
||||
let s2 = BitSet::<u8>(127);
|
||||
for i in 0..6 {
|
||||
assert!(s2.contains(i));
|
||||
}
|
||||
assert!(!s2.contains(7));
|
||||
|
||||
let s3 = BitSet::<u8>(2 | 4 | 64);
|
||||
assert!(!s3.contains(0) && !s3.contains(3) && !s3.contains(4) && !s3.contains(5) &&
|
||||
!s3.contains(7));
|
||||
assert!(s3.contains(1) && s3.contains(2) && s3.contains(6));
|
||||
|
||||
let s4 = BitSet::<u16>(4 | 8 | 256 | 1024);
|
||||
assert!(!s4.contains(0) && !s4.contains(1) && !s4.contains(4) && !s4.contains(5) &&
|
||||
!s4.contains(6) && !s4.contains(7) &&
|
||||
!s4.contains(9) && !s4.contains(11));
|
||||
assert!(s4.contains(2) && s4.contains(3) && s4.contains(8) && s4.contains(10));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minmax() {
|
||||
let s = BitSet::<u8>(255);
|
||||
assert_eq!(s.min(), Some(0));
|
||||
assert_eq!(s.max(), Some(7));
|
||||
assert!(s.min() == Some(0) && s.max() == Some(7));
|
||||
let s1 = BitSet::<u8>(0);
|
||||
assert!(s1.min() == None && s1.max() == None);
|
||||
let s2 = BitSet::<u8>(127);
|
||||
assert!(s2.min() == Some(0) && s2.max() == Some(6));
|
||||
let s3 = BitSet::<u8>(2 | 4 | 64);
|
||||
assert!(s3.min() == Some(1) && s3.max() == Some(6));
|
||||
let s4 = BitSet::<u16>(4 | 8 | 256 | 1024);
|
||||
assert!(s4.min() == Some(2) && s4.max() == Some(10));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_range() {
|
||||
let s = BitSet::<u8>::from_range(5, 5);
|
||||
assert!(s.0 == 0);
|
||||
|
||||
let s = BitSet::<u8>::from_range(0, 8);
|
||||
assert!(s.0 == 255);
|
||||
|
||||
let s = BitSet::<u16>::from_range(0, 8);
|
||||
assert!(s.0 == 255u16);
|
||||
|
||||
let s = BitSet::<u16>::from_range(0, 16);
|
||||
assert!(s.0 == 65535u16);
|
||||
|
||||
let s = BitSet::<u8>::from_range(5, 6);
|
||||
assert!(s.0 == 32u8);
|
||||
|
||||
let s = BitSet::<u8>::from_range(3, 7);
|
||||
assert!(s.0 == 8 | 16 | 32 | 64);
|
||||
|
||||
let s = BitSet::<u16>::from_range(5, 11);
|
||||
assert!(s.0 == 32 | 64 | 128 | 256 | 512 | 1024);
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
//! Generic B-Tree implementation.
|
||||
//!
|
||||
//! This module defines a `Btree<K, V>` type which provides similar functionality to
|
||||
//! `BtreeMap<K, V>`, but with some important differences in the implementation:
|
||||
//!
|
||||
//! 1. Memory is allocated from a `NodePool<K, V>` instead of the global heap.
|
||||
//! 2. The footprint of a BTree is only 4 bytes.
|
||||
//! 3. A BTree doesn't implement `Drop`, leaving it to the pool to manage memory.
|
||||
//!
|
||||
//! The node pool is intended to be used as a LIFO allocator. After building up a larger data
|
||||
//! structure with many list references, the whole thing can be discarded quickly by clearing the
|
||||
//! pool.
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
// A Node reference is a direct index to an element of the pool.
|
||||
type NodeRef = u32;
|
||||
|
||||
/// A B-tree data structure which nodes are allocated from a pool.
|
||||
pub struct BTree<K, V> {
|
||||
index: NodeRef,
|
||||
unused1: PhantomData<K>,
|
||||
unused2: PhantomData<V>,
|
||||
}
|
||||
|
||||
/// An enum representing a B-tree node.
|
||||
/// Keys and values are required to implement Default.
|
||||
enum Node<K, V> {
|
||||
Inner {
|
||||
size: u8,
|
||||
keys: [K; 7],
|
||||
nodes: [NodeRef; 8],
|
||||
},
|
||||
Leaf {
|
||||
size: u8,
|
||||
keys: [K; 7],
|
||||
values: [V; 7],
|
||||
},
|
||||
}
|
||||
|
||||
/// Memory pool for nodes.
|
||||
struct NodePool<K, V> {
|
||||
// The array containing the nodes.
|
||||
data: Vec<Node<K, V>>,
|
||||
|
||||
// A free list
|
||||
freelist: Vec<NodeRef>,
|
||||
}
|
||||
|
||||
impl<K: Default, V: Default> NodePool<K, V> {
|
||||
/// Create a new NodePool.
|
||||
pub fn new() -> NodePool<K, V> {
|
||||
NodePool {
|
||||
data: Vec::new(),
|
||||
freelist: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a B-tree node.
|
||||
pub fn get(&self, index: u32) -> Option<&Node<K, V>> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Default, V: Default> BTree<K, V> {
|
||||
/// Search for `key` and return a `Cursor` that either points at `key` or the position where it would be inserted.
|
||||
pub fn search(&mut self, key: K) -> Cursor<K, V> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Cursor<'a, K: 'a, V: 'a> {
|
||||
pool: &'a mut NodePool<K, V>,
|
||||
height: usize,
|
||||
path: [(NodeRef, u8); 16],
|
||||
}
|
||||
|
||||
impl<'a, K: Default, V: Default> Cursor<'a, K, V> {
|
||||
/// The key at the cursor position. Returns `None` when the cursor points off the end.
|
||||
pub fn key(&self) -> Option<K> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
/// The value at the cursor position. Returns `None` when the cursor points off the end.
|
||||
pub fn value(&self) -> Option<&V> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
/// Move to the next element.
|
||||
/// Returns `false` if that moves the cursor off the end.
|
||||
pub fn next(&mut self) -> bool {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
/// Move to the previous element.
|
||||
/// Returns `false` if this moves the cursor before the beginning.
|
||||
pub fn prev(&mut self) -> bool {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
/// Insert a `(key, value)` pair at the cursor position.
|
||||
/// It is an error to insert a key that would be out of order at this position.
|
||||
pub fn insert(&mut self, key: K, value: V) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
/// Remove the current element.
|
||||
pub fn remove(&mut self) {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
//! Runtime support for precomputed constant hash tables.
|
||||
//!
|
||||
//! The `lib/cretonne/meta/constant_hash.py` Python module can generate constant hash tables using
|
||||
//! open addressing and quadratic probing. The hash tables are arrays that are guaranteed to:
|
||||
//!
|
||||
//! - Have a power-of-two size.
|
||||
//! - Contain at least one empty slot.
|
||||
//!
|
||||
//! This module provides runtime support for lookups in these tables.
|
||||
|
||||
/// Trait that must be implemented by the entries in a constant hash table.
|
||||
pub trait Table<K: Copy + Eq> {
|
||||
/// Get the number of entries in this table which must be a power of two.
|
||||
fn len(&self) -> usize;
|
||||
|
||||
/// Get the key corresponding to the entry at `idx`, or `None` if the entry is empty.
|
||||
/// The `idx` must be in range.
|
||||
fn key(&self, idx: usize) -> Option<K>;
|
||||
}
|
||||
|
||||
|
||||
/// Look for `key` in `table`.
|
||||
///
|
||||
/// The provided `hash` value must have been computed from `key` using the same hash function that
|
||||
/// was used to construct the table.
|
||||
///
|
||||
/// Returns `Ok(idx)` with the table index containing the found entry, or `Err(idx)` with the empty
|
||||
/// sentinel entry if no entry could be found.
|
||||
pub fn probe<K: Copy + Eq, T: Table<K> + ?Sized>(table: &T,
|
||||
key: K,
|
||||
hash: usize)
|
||||
-> Result<usize, usize> {
|
||||
debug_assert!(table.len().is_power_of_two());
|
||||
let mask = table.len() - 1;
|
||||
|
||||
let mut idx = hash;
|
||||
let mut step = 0;
|
||||
|
||||
loop {
|
||||
idx &= mask;
|
||||
|
||||
match table.key(idx) {
|
||||
None => return Err(idx),
|
||||
Some(k) if k == key => return Ok(idx),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Quadratic probing.
|
||||
step += 1;
|
||||
// When `table.len()` is a power of two, it can be proven that `idx` will visit all
|
||||
// entries. This means that this loop will always terminate if the hash table has even
|
||||
// one unused entry.
|
||||
debug_assert!(step < table.len());
|
||||
idx += step;
|
||||
}
|
||||
}
|
||||
|
||||
/// A primitive hash function for matching opcodes.
|
||||
/// Must match `lib/cretonne/meta/constant_hash.py`.
|
||||
pub fn simple_hash(s: &str) -> usize {
|
||||
let mut h: u32 = 5381;
|
||||
for c in s.chars() {
|
||||
h = (h ^ c as u32).wrapping_add(h.rotate_right(6));
|
||||
}
|
||||
h as usize
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::simple_hash;
|
||||
|
||||
#[test]
|
||||
fn basic() {
|
||||
// c.f. `meta/constant_hash.py` tests.
|
||||
assert_eq!(simple_hash("Hello"), 0x2fa70c01);
|
||||
assert_eq!(simple_hash("world"), 0x5b0c31d5);
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
//! Cretonne compilation context and main entry point.
|
||||
//!
|
||||
//! When compiling many small functions, it is important to avoid repeatedly allocating and
|
||||
//! deallocating the data structures needed for compilation. The `Context` struct is used to hold
|
||||
//! on to memory allocations between function compilations.
|
||||
//!
|
||||
//! The context does not hold a `TargetIsa` instance which has to be provided as an argument
|
||||
//! instead. This is because an ISA instance is immutable and can be used by multiple compilation
|
||||
//! contexts concurrently. Typically, you would have one context per compilation thread and only a
|
||||
//! single ISA instance.
|
||||
|
||||
use binemit::{CodeOffset, relax_branches, MemoryCodeSink, RelocSink};
|
||||
use dominator_tree::DominatorTree;
|
||||
use flowgraph::ControlFlowGraph;
|
||||
use ir::Function;
|
||||
use loop_analysis::LoopAnalysis;
|
||||
use isa::TargetIsa;
|
||||
use legalize_function;
|
||||
use regalloc;
|
||||
use result::{CtonError, CtonResult};
|
||||
use verifier;
|
||||
use simple_gvn::do_simple_gvn;
|
||||
use licm::do_licm;
|
||||
|
||||
/// Persistent data structures and compilation pipeline.
|
||||
pub struct Context {
|
||||
/// The function we're compiling.
|
||||
pub func: Function,
|
||||
|
||||
/// The control flow graph of `func`.
|
||||
pub cfg: ControlFlowGraph,
|
||||
|
||||
/// Dominator tree for `func`.
|
||||
pub domtree: DominatorTree,
|
||||
|
||||
/// Register allocation context.
|
||||
pub regalloc: regalloc::Context,
|
||||
|
||||
/// Loop analysis of `func`.
|
||||
pub loop_analysis: LoopAnalysis,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Allocate a new compilation context.
|
||||
///
|
||||
/// The returned instance should be reused for compiling multiple functions in order to avoid
|
||||
/// needless allocator thrashing.
|
||||
pub fn new() -> Context {
|
||||
Context {
|
||||
func: Function::new(),
|
||||
cfg: ControlFlowGraph::new(),
|
||||
domtree: DominatorTree::new(),
|
||||
regalloc: regalloc::Context::new(),
|
||||
loop_analysis: LoopAnalysis::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Compile the function.
|
||||
///
|
||||
/// Run the function through all the passes necessary to generate code for the target ISA
|
||||
/// represented by `isa`. This does not include the final step of emitting machine code into a
|
||||
/// code sink.
|
||||
///
|
||||
/// Returns the size of the function's code.
|
||||
pub fn compile(&mut self, isa: &TargetIsa) -> Result<CodeOffset, CtonError> {
|
||||
self.flowgraph();
|
||||
self.verify_if(isa)?;
|
||||
|
||||
self.legalize(isa)?;
|
||||
self.regalloc(isa)?;
|
||||
self.prologue_epilogue(isa)?;
|
||||
self.relax_branches(isa)
|
||||
}
|
||||
|
||||
/// Emit machine code directly into raw memory.
|
||||
///
|
||||
/// Write all of the function's machine code to the memory at `mem`. The size of the machine
|
||||
/// code is returned by `compile` above.
|
||||
///
|
||||
/// The machine code is not relocated. Instead, any relocations are emitted into `relocs`.
|
||||
pub fn emit_to_memory(&self, mem: *mut u8, relocs: &mut RelocSink, isa: &TargetIsa) {
|
||||
isa.emit_function(&self.func, &mut MemoryCodeSink::new(mem, relocs));
|
||||
}
|
||||
|
||||
/// Run the verifier on the function.
|
||||
///
|
||||
/// Also check that the dominator tree and control flow graph are consistent with the function.
|
||||
///
|
||||
/// The `isa` argument is currently unused, but the verifier will soon be able to also
|
||||
/// check ISA-dependent constraints.
|
||||
pub fn verify(&self, isa: Option<&TargetIsa>) -> verifier::Result {
|
||||
verifier::verify_context(&self.func, &self.cfg, &self.domtree, isa)
|
||||
}
|
||||
|
||||
/// Run the verifier only if the `enable_verifier` setting is true.
|
||||
pub fn verify_if(&self, isa: &TargetIsa) -> CtonResult {
|
||||
if isa.flags().enable_verifier() {
|
||||
self.verify(Some(isa)).map_err(Into::into)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the legalizer for `isa` on the function.
|
||||
pub fn legalize(&mut self, isa: &TargetIsa) -> CtonResult {
|
||||
legalize_function(&mut self.func, &mut self.cfg, &self.domtree, isa);
|
||||
self.verify_if(isa)
|
||||
}
|
||||
|
||||
/// Recompute the control flow graph and dominator tree.
|
||||
pub fn flowgraph(&mut self) {
|
||||
self.cfg.compute(&self.func);
|
||||
self.domtree.compute(&self.func, &self.cfg);
|
||||
}
|
||||
|
||||
/// Perform simple GVN on the function.
|
||||
pub fn simple_gvn(&mut self) -> CtonResult {
|
||||
do_simple_gvn(&mut self.func, &mut self.cfg);
|
||||
// TODO: Factor things such that we can get a Flags and test
|
||||
// enable_verifier().
|
||||
self.verify(None).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Perform LICM on the function.
|
||||
pub fn licm(&mut self) -> CtonResult {
|
||||
do_licm(&mut self.func,
|
||||
&mut self.cfg,
|
||||
&mut self.domtree,
|
||||
&mut self.loop_analysis);
|
||||
self.verify(None).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Run the register allocator.
|
||||
pub fn regalloc(&mut self, isa: &TargetIsa) -> CtonResult {
|
||||
self.regalloc
|
||||
.run(isa, &mut self.func, &self.cfg, &self.domtree)
|
||||
}
|
||||
|
||||
/// Insert prologue and epilogues after computing the stack frame layout.
|
||||
pub fn prologue_epilogue(&mut self, isa: &TargetIsa) -> CtonResult {
|
||||
isa.prologue_epilogue(&mut self.func)?;
|
||||
self.verify_if(isa)
|
||||
}
|
||||
|
||||
/// Run the branch relaxation pass and return the final code size.
|
||||
pub fn relax_branches(&mut self, isa: &TargetIsa) -> Result<CodeOffset, CtonError> {
|
||||
let code_size = relax_branches(&mut self.func, isa)?;
|
||||
self.verify_if(isa)?;
|
||||
|
||||
Ok(code_size)
|
||||
}
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
//! Cursor library.
|
||||
//!
|
||||
//! This module defines cursor data types that can be used for inserting instructions.
|
||||
|
||||
use ir;
|
||||
use isa::TargetIsa;
|
||||
|
||||
// Re-export these types, anticipating their being moved here.
|
||||
pub use ir::layout::CursorBase as Cursor;
|
||||
pub use ir::layout::CursorPosition;
|
||||
pub use ir::layout::Cursor as LayoutCursor;
|
||||
|
||||
/// Function cursor.
|
||||
///
|
||||
/// A `FuncCursor` holds a mutable reference to a whole `ir::Function` while keeping a position
|
||||
/// too. The function can be re-borrowed by accessing the public `cur.func` member.
|
||||
///
|
||||
/// This cursor is for use before legalization. The inserted instructions are not given an
|
||||
/// encoding.
|
||||
pub struct FuncCursor<'f> {
|
||||
pos: CursorPosition,
|
||||
pub func: &'f mut ir::Function,
|
||||
}
|
||||
|
||||
impl<'f> FuncCursor<'f> {
|
||||
/// Create a new `FuncCursor` pointing nowhere.
|
||||
pub fn new(func: &'f mut ir::Function) -> FuncCursor<'f> {
|
||||
FuncCursor {
|
||||
pos: CursorPosition::Nowhere,
|
||||
func,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an instruction builder that inserts an instruction at the current position.
|
||||
pub fn ins(&mut self) -> ir::InsertBuilder<&mut FuncCursor<'f>> {
|
||||
ir::InsertBuilder::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'f> Cursor for FuncCursor<'f> {
|
||||
fn position(&self) -> CursorPosition {
|
||||
self.pos
|
||||
}
|
||||
|
||||
fn set_position(&mut self, pos: CursorPosition) {
|
||||
self.pos = pos
|
||||
}
|
||||
|
||||
fn layout(&self) -> &ir::Layout {
|
||||
&self.func.layout
|
||||
}
|
||||
|
||||
fn layout_mut(&mut self) -> &mut ir::Layout {
|
||||
&mut self.func.layout
|
||||
}
|
||||
}
|
||||
|
||||
impl<'c, 'f> ir::InstInserterBase<'c> for &'c mut FuncCursor<'f> {
|
||||
fn data_flow_graph(&self) -> &ir::DataFlowGraph {
|
||||
&self.func.dfg
|
||||
}
|
||||
|
||||
fn data_flow_graph_mut(&mut self) -> &mut ir::DataFlowGraph {
|
||||
&mut self.func.dfg
|
||||
}
|
||||
|
||||
fn insert_built_inst(self, inst: ir::Inst, _: ir::Type) -> &'c mut ir::DataFlowGraph {
|
||||
self.insert_inst(inst);
|
||||
&mut self.func.dfg
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Encoding cursor.
|
||||
///
|
||||
/// An `EncCursor` can be used to insert instructions that are immediately assigned an encoding.
|
||||
/// The cursor holds a mutable reference to the whole function which can be re-borrowed from the
|
||||
/// public `pos.func` member.
|
||||
pub struct EncCursor<'f> {
|
||||
pos: CursorPosition,
|
||||
built_inst: Option<ir::Inst>,
|
||||
pub func: &'f mut ir::Function,
|
||||
pub isa: &'f TargetIsa,
|
||||
}
|
||||
|
||||
impl<'f> EncCursor<'f> {
|
||||
/// Create a new `EncCursor` pointing nowhere.
|
||||
pub fn new(func: &'f mut ir::Function, isa: &'f TargetIsa) -> EncCursor<'f> {
|
||||
EncCursor {
|
||||
pos: CursorPosition::Nowhere,
|
||||
built_inst: None,
|
||||
func,
|
||||
isa,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an instruction builder that will insert an encoded instruction at the current
|
||||
/// position.
|
||||
///
|
||||
/// The builder will panic if it is used to insert an instruction that can't be encoded for
|
||||
/// `self.isa`.
|
||||
pub fn ins(&mut self) -> ir::InsertBuilder<&mut EncCursor<'f>> {
|
||||
ir::InsertBuilder::new(self)
|
||||
}
|
||||
|
||||
/// Get the last built instruction.
|
||||
///
|
||||
/// This returns the last instruction that was built using the `ins()` method on this cursor.
|
||||
/// Panics if no instruction was built.
|
||||
pub fn built_inst(&self) -> ir::Inst {
|
||||
self.built_inst.expect("No instruction was inserted")
|
||||
}
|
||||
|
||||
/// Return an object that can display `inst`.
|
||||
///
|
||||
/// This is a convenience wrapper for the DFG equivalent.
|
||||
pub fn display_inst(&self, inst: ir::Inst) -> ir::dfg::DisplayInst {
|
||||
self.func.dfg.display_inst(inst, self.isa)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'f> Cursor for EncCursor<'f> {
|
||||
fn position(&self) -> CursorPosition {
|
||||
self.pos
|
||||
}
|
||||
|
||||
fn set_position(&mut self, pos: CursorPosition) {
|
||||
self.pos = pos
|
||||
}
|
||||
|
||||
fn layout(&self) -> &ir::Layout {
|
||||
&self.func.layout
|
||||
}
|
||||
|
||||
fn layout_mut(&mut self) -> &mut ir::Layout {
|
||||
&mut self.func.layout
|
||||
}
|
||||
}
|
||||
|
||||
impl<'c, 'f> ir::InstInserterBase<'c> for &'c mut EncCursor<'f> {
|
||||
fn data_flow_graph(&self) -> &ir::DataFlowGraph {
|
||||
&self.func.dfg
|
||||
}
|
||||
|
||||
fn data_flow_graph_mut(&mut self) -> &mut ir::DataFlowGraph {
|
||||
&mut self.func.dfg
|
||||
}
|
||||
|
||||
fn insert_built_inst(self,
|
||||
inst: ir::Inst,
|
||||
ctrl_typevar: ir::Type)
|
||||
-> &'c mut ir::DataFlowGraph {
|
||||
// Insert the instruction and remember the reference.
|
||||
self.insert_inst(inst);
|
||||
self.built_inst = Some(inst);
|
||||
|
||||
// Assign an encoding.
|
||||
match self.isa
|
||||
.encode(&self.func.dfg, &self.func.dfg[inst], ctrl_typevar) {
|
||||
Ok(e) => *self.func.encodings.ensure(inst) = e,
|
||||
Err(_) => panic!("can't encode {}", self.display_inst(inst)),
|
||||
}
|
||||
|
||||
&mut self.func.dfg
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
//! Debug tracing macros.
|
||||
//!
|
||||
//! This module defines the `dbg!` macro which works like `println!` except it writes to the
|
||||
//! Cretonne tracing output file if enabled.
|
||||
//!
|
||||
//! Tracing can be enabled by setting the `CRETONNE_DBG` environment variable to something
|
||||
/// other than `0`.
|
||||
///
|
||||
/// The output will appear in files named `cretonne.dbg.*`, where the suffix is named after the
|
||||
/// thread doing the logging.
|
||||
|
||||
use std::ascii::AsciiExt;
|
||||
use std::cell::RefCell;
|
||||
use std::env;
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Write};
|
||||
use std::sync::atomic;
|
||||
use std::thread;
|
||||
|
||||
static STATE: atomic::AtomicIsize = atomic::ATOMIC_ISIZE_INIT;
|
||||
|
||||
/// Is debug tracing enabled?
|
||||
///
|
||||
/// Debug tracing can be enabled by setting the `CRETONNE_DBG` environment variable to something
|
||||
/// other than `0`.
|
||||
///
|
||||
/// This inline function turns into a constant `false` when debug assertions are disabled.
|
||||
#[inline]
|
||||
pub fn enabled() -> bool {
|
||||
if cfg!(debug_assertions) {
|
||||
match STATE.load(atomic::Ordering::Relaxed) {
|
||||
0 => initialize(),
|
||||
s => s > 0,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize `STATE` from the environment variable.
|
||||
fn initialize() -> bool {
|
||||
let enable = match env::var_os("CRETONNE_DBG") {
|
||||
Some(s) => s != OsStr::new("0"),
|
||||
None => false,
|
||||
};
|
||||
|
||||
if enable {
|
||||
STATE.store(1, atomic::Ordering::Relaxed);
|
||||
} else {
|
||||
STATE.store(-1, atomic::Ordering::Relaxed);
|
||||
}
|
||||
|
||||
enable
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static WRITER : RefCell<io::BufWriter<File>> = RefCell::new(open_file());
|
||||
}
|
||||
|
||||
/// Write a line with the given format arguments.
|
||||
///
|
||||
/// This is for use by the `dbg!` macro.
|
||||
pub fn writeln_with_format_args(args: fmt::Arguments) -> io::Result<()> {
|
||||
WRITER.with(|rc| writeln!(*rc.borrow_mut(), "{}", args))
|
||||
}
|
||||
|
||||
/// Open the tracing file for the current thread.
|
||||
fn open_file() -> io::BufWriter<File> {
|
||||
let file = match thread::current().name() {
|
||||
None => File::create("cretonne.dbg"),
|
||||
Some(name) => {
|
||||
let mut path = "cretonne.dbg.".to_owned();
|
||||
for ch in name.chars() {
|
||||
if ch.is_ascii() && ch.is_alphanumeric() {
|
||||
path.push(ch);
|
||||
}
|
||||
}
|
||||
File::create(path)
|
||||
}
|
||||
}
|
||||
.expect("Can't open tracing file");
|
||||
io::BufWriter::new(file)
|
||||
}
|
||||
|
||||
/// Write a line to the debug trace file if tracing is enabled.
|
||||
///
|
||||
/// Arguments are the same as for `printf!`.
|
||||
#[macro_export]
|
||||
macro_rules! dbg {
|
||||
($($arg:tt)+) => {
|
||||
if $crate::dbg::enabled() {
|
||||
// Drop the error result so we don't get compiler errors for ignoring it.
|
||||
// What are you going to do, log the error?
|
||||
$crate::dbg::writeln_with_format_args(format_args!($($arg)+)).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper for printing lists.
|
||||
pub struct DisplayList<'a, T>(pub &'a [T]) where T: 'a + fmt::Display;
|
||||
|
||||
impl<'a, T> fmt::Display for DisplayList<'a, T>
|
||||
where T: 'a + fmt::Display
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self.0.split_first() {
|
||||
None => write!(f, "[]"),
|
||||
Some((first, rest)) => {
|
||||
write!(f, "[{}", first)?;
|
||||
for x in rest {
|
||||
write!(f, ", {}", x)?;
|
||||
}
|
||||
write!(f, "]")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,570 +0,0 @@
|
||||
//! A Dominator Tree represented as mappings of Ebbs to their immediate dominator.
|
||||
|
||||
use entity_map::EntityMap;
|
||||
use flowgraph::{ControlFlowGraph, BasicBlock};
|
||||
use ir::{Ebb, Inst, Function, Layout, ProgramOrder, ExpandedProgramPoint};
|
||||
use packed_option::PackedOption;
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
// RPO numbers are not first assigned in a contiguous way but as multiples of STRIDE, to leave
|
||||
// room for modifications of the dominator tree.
|
||||
const STRIDE: u32 = 4;
|
||||
|
||||
// Dominator tree node. We keep one of these per EBB.
|
||||
#[derive(Clone, Default)]
|
||||
struct DomNode {
|
||||
// Number of this node in a reverse post-order traversal of the CFG, starting from 1.
|
||||
// This number is monotonic in the reverse postorder but not contiguous, since we leave
|
||||
// holes for later localized modifications of the dominator tree.
|
||||
// Unreachable nodes get number 0, all others are positive.
|
||||
rpo_number: u32,
|
||||
|
||||
// The immediate dominator of this EBB, represented as the branch or jump instruction at the
|
||||
// end of the dominating basic block.
|
||||
//
|
||||
// This is `None` for unreachable blocks and the entry block which doesn't have an immediate
|
||||
// dominator.
|
||||
idom: PackedOption<Inst>,
|
||||
}
|
||||
|
||||
/// The dominator tree for a single function.
|
||||
pub struct DominatorTree {
|
||||
nodes: EntityMap<Ebb, DomNode>,
|
||||
|
||||
// CFG post-order of all reachable EBBs.
|
||||
postorder: Vec<Ebb>,
|
||||
|
||||
// Scratch memory used by `compute_postorder()`.
|
||||
stack: Vec<Ebb>,
|
||||
}
|
||||
|
||||
/// Methods for querying the dominator tree.
|
||||
impl DominatorTree {
|
||||
/// Is `ebb` reachable from the entry block?
|
||||
pub fn is_reachable(&self, ebb: Ebb) -> bool {
|
||||
self.nodes[ebb].rpo_number != 0
|
||||
}
|
||||
|
||||
/// Get the CFG post-order of EBBs that was used to compute the dominator tree.
|
||||
///
|
||||
/// Note that this post-order is not updated automatically when the CFG is modified. It is
|
||||
/// computed from scratch and cached by `compute()`.
|
||||
pub fn cfg_postorder(&self) -> &[Ebb] {
|
||||
&self.postorder
|
||||
}
|
||||
|
||||
/// Returns the immediate dominator of `ebb`.
|
||||
///
|
||||
/// The immediate dominator of an extended basic block is a basic block which we represent by
|
||||
/// the branch or jump instruction at the end of the basic block. This does not have to be the
|
||||
/// terminator of its EBB.
|
||||
///
|
||||
/// A branch or jump is said to *dominate* `ebb` if all control flow paths from the function
|
||||
/// entry to `ebb` must go through the branch.
|
||||
///
|
||||
/// The *immediate dominator* is the dominator that is closest to `ebb`. All other dominators
|
||||
/// also dominate the immediate dominator.
|
||||
///
|
||||
/// This returns `None` if `ebb` is not reachable from the entry EBB, or if it is the entry EBB
|
||||
/// which has no dominators.
|
||||
pub fn idom(&self, ebb: Ebb) -> Option<Inst> {
|
||||
self.nodes[ebb].idom.into()
|
||||
}
|
||||
|
||||
/// Compare two EBBs relative to the reverse post-order.
|
||||
fn rpo_cmp_ebb(&self, a: Ebb, b: Ebb) -> Ordering {
|
||||
|
||||
self.nodes[a].rpo_number.cmp(&self.nodes[b].rpo_number)
|
||||
}
|
||||
|
||||
/// Compare two program points relative to a reverse post-order traversal of the control-flow
|
||||
/// graph.
|
||||
///
|
||||
/// Return `Ordering::Less` if `a` comes before `b` in the RPO.
|
||||
///
|
||||
/// If `a` and `b` belong to the same EBB, compare their relative position in the EBB.
|
||||
pub fn rpo_cmp<A, B>(&self, a: A, b: B, layout: &Layout) -> Ordering
|
||||
where A: Into<ExpandedProgramPoint>,
|
||||
B: Into<ExpandedProgramPoint>
|
||||
{
|
||||
let a = a.into();
|
||||
let b = b.into();
|
||||
self.rpo_cmp_ebb(layout.pp_ebb(a), layout.pp_ebb(b))
|
||||
.then(layout.cmp(a, b))
|
||||
}
|
||||
|
||||
/// Returns `true` if `a` dominates `b`.
|
||||
///
|
||||
/// This means that every control-flow path from the function entry to `b` must go through `a`.
|
||||
///
|
||||
/// Dominance is ill defined for unreachable blocks. This function can always determine
|
||||
/// dominance for instructions in the same EBB, but otherwise returns `false` if either block
|
||||
/// is unreachable.
|
||||
///
|
||||
/// An instruction is considered to dominate itself.
|
||||
pub fn dominates<A, B>(&self, a: A, b: B, layout: &Layout) -> bool
|
||||
where A: Into<ExpandedProgramPoint>,
|
||||
B: Into<ExpandedProgramPoint>
|
||||
{
|
||||
let a = a.into();
|
||||
let b = b.into();
|
||||
match a {
|
||||
ExpandedProgramPoint::Ebb(ebb_a) => {
|
||||
a == b || self.last_dominator(ebb_a, b, layout).is_some()
|
||||
}
|
||||
ExpandedProgramPoint::Inst(inst_a) => {
|
||||
let ebb_a = layout.inst_ebb(inst_a).expect("Instruction not in layout.");
|
||||
match self.last_dominator(ebb_a, b, layout) {
|
||||
Some(last) => layout.cmp(inst_a, last) != Ordering::Greater,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Find the last instruction in `a` that dominates `b`.
|
||||
/// If no instructions in `a` dominate `b`, return `None`.
|
||||
fn last_dominator<B>(&self, a: Ebb, b: B, layout: &Layout) -> Option<Inst>
|
||||
where B: Into<ExpandedProgramPoint>
|
||||
{
|
||||
let (mut ebb_b, mut inst_b) = match b.into() {
|
||||
ExpandedProgramPoint::Ebb(ebb) => (ebb, None),
|
||||
ExpandedProgramPoint::Inst(inst) => {
|
||||
(layout.inst_ebb(inst).expect("Instruction not in layout."), Some(inst))
|
||||
}
|
||||
};
|
||||
let rpo_a = self.nodes[a].rpo_number;
|
||||
|
||||
// Run a finger up the dominator tree from b until we see a.
|
||||
// Do nothing if b is unreachable.
|
||||
while rpo_a < self.nodes[ebb_b].rpo_number {
|
||||
let idom = self.idom(ebb_b).expect("Shouldn't meet unreachable here.");
|
||||
ebb_b = layout.inst_ebb(idom).expect("Dominator got removed.");
|
||||
inst_b = Some(idom);
|
||||
}
|
||||
if a == ebb_b { inst_b } else { None }
|
||||
}
|
||||
|
||||
/// Compute the common dominator of two basic blocks.
|
||||
///
|
||||
/// Both basic blocks are assumed to be reachable.
|
||||
pub fn common_dominator(&self,
|
||||
mut a: BasicBlock,
|
||||
mut b: BasicBlock,
|
||||
layout: &Layout)
|
||||
-> BasicBlock {
|
||||
loop {
|
||||
match self.rpo_cmp_ebb(a.0, b.0) {
|
||||
Ordering::Less => {
|
||||
// `a` comes before `b` in the RPO. Move `b` up.
|
||||
let idom = self.nodes[b.0].idom.expect("Unreachable basic block?");
|
||||
b = (layout.inst_ebb(idom).expect("Dangling idom instruction"), idom);
|
||||
}
|
||||
Ordering::Greater => {
|
||||
// `b` comes before `a` in the RPO. Move `a` up.
|
||||
let idom = self.nodes[a.0].idom.expect("Unreachable basic block?");
|
||||
a = (layout.inst_ebb(idom).expect("Dangling idom instruction"), idom);
|
||||
}
|
||||
Ordering::Equal => break,
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(a.0, b.0, "Unreachable block passed to common_dominator?");
|
||||
|
||||
// We're in the same EBB. The common dominator is the earlier instruction.
|
||||
if layout.cmp(a.1, b.1) == Ordering::Less {
|
||||
a
|
||||
} else {
|
||||
b
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DominatorTree {
|
||||
/// Allocate a new blank dominator tree. Use `compute` to compute the dominator tree for a
|
||||
/// function.
|
||||
pub fn new() -> DominatorTree {
|
||||
DominatorTree {
|
||||
nodes: EntityMap::new(),
|
||||
postorder: Vec::new(),
|
||||
stack: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocate and compute a dominator tree.
|
||||
pub fn with_function(func: &Function, cfg: &ControlFlowGraph) -> DominatorTree {
|
||||
let mut domtree = DominatorTree::new();
|
||||
domtree.compute(func, cfg);
|
||||
domtree
|
||||
}
|
||||
|
||||
/// Reset and compute a CFG post-order and dominator tree.
|
||||
pub fn compute(&mut self, func: &Function, cfg: &ControlFlowGraph) {
|
||||
self.compute_postorder(func, cfg);
|
||||
self.compute_domtree(func, cfg);
|
||||
}
|
||||
|
||||
/// Reset all internal data structures and compute a post-order for `cfg`.
|
||||
///
|
||||
/// This leaves `rpo_number == 1` for all reachable EBBs, 0 for unreachable ones.
|
||||
fn compute_postorder(&mut self, func: &Function, cfg: &ControlFlowGraph) {
|
||||
self.nodes.clear();
|
||||
self.nodes.resize(func.dfg.num_ebbs());
|
||||
self.postorder.clear();
|
||||
assert!(self.stack.is_empty());
|
||||
|
||||
// During this algorithm only, use `rpo_number` to hold the following state:
|
||||
//
|
||||
// 0: EBB never reached.
|
||||
// 2: EBB has been pushed once, so it shouldn't be pushed again.
|
||||
// 1: EBB has already been popped once, and should be added to the post-order next time.
|
||||
const SEEN: u32 = 2;
|
||||
const DONE: u32 = 1;
|
||||
|
||||
match func.layout.entry_block() {
|
||||
Some(ebb) => {
|
||||
self.nodes[ebb].rpo_number = SEEN;
|
||||
self.stack.push(ebb)
|
||||
}
|
||||
None => return,
|
||||
}
|
||||
|
||||
while let Some(ebb) = self.stack.pop() {
|
||||
match self.nodes[ebb].rpo_number {
|
||||
// This is the first time we visit `ebb`, forming a pre-order.
|
||||
SEEN => {
|
||||
// Mark it as done and re-queue it to be visited after its children.
|
||||
self.nodes[ebb].rpo_number = DONE;
|
||||
self.stack.push(ebb);
|
||||
for &succ in cfg.get_successors(ebb) {
|
||||
// Only push children that haven't been seen before.
|
||||
if self.nodes[succ].rpo_number == 0 {
|
||||
self.nodes[succ].rpo_number = SEEN;
|
||||
self.stack.push(succ);
|
||||
}
|
||||
}
|
||||
}
|
||||
// This is the second time we popped `ebb`, so all its children have been visited.
|
||||
// This is the post-order.
|
||||
DONE => self.postorder.push(ebb),
|
||||
_ => panic!("Inconsistent stack rpo_number"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a dominator tree from a control flow graph using Keith D. Cooper's
|
||||
/// "Simple, Fast Dominator Algorithm."
|
||||
fn compute_domtree(&mut self, func: &Function, cfg: &ControlFlowGraph) {
|
||||
// During this algorithm, `rpo_number` has the following values:
|
||||
//
|
||||
// 0: EBB is not reachable.
|
||||
// 1: EBB is reachable, but has not yet been visited during the first pass. This is set by
|
||||
// `compute_postorder`.
|
||||
// 2+: EBB is reachable and has an assigned RPO number.
|
||||
|
||||
// We'll be iterating over a reverse post-order of the CFG, skipping the entry block.
|
||||
let (entry_block, postorder) = match self.postorder.as_slice().split_last() {
|
||||
Some((&eb, rest)) => (eb, rest),
|
||||
None => return,
|
||||
};
|
||||
debug_assert_eq!(Some(entry_block), func.layout.entry_block());
|
||||
|
||||
// Do a first pass where we assign RPO numbers to all reachable nodes.
|
||||
self.nodes[entry_block].rpo_number = 2 * STRIDE;
|
||||
for (rpo_idx, &ebb) in postorder.iter().rev().enumerate() {
|
||||
// Update the current node and give it an RPO number.
|
||||
// The entry block got 2, the rest start at 3 by multiples of STRIDE to leave
|
||||
// room for future dominator tree modifications.
|
||||
//
|
||||
// Since `compute_idom` will only look at nodes with an assigned RPO number, the
|
||||
// function will never see an uninitialized predecessor.
|
||||
//
|
||||
// Due to the nature of the post-order traversal, every node we visit will have at
|
||||
// least one predecessor that has previously been visited during this RPO.
|
||||
self.nodes[ebb] = DomNode {
|
||||
idom: self.compute_idom(ebb, cfg, &func.layout).into(),
|
||||
rpo_number: (rpo_idx as u32 + 3) * STRIDE,
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we have RPO numbers for everything and initial immediate dominator estimates,
|
||||
// iterate until convergence.
|
||||
//
|
||||
// If the function is free of irreducible control flow, this will exit after one iteration.
|
||||
let mut changed = true;
|
||||
while changed {
|
||||
changed = false;
|
||||
for &ebb in postorder.iter().rev() {
|
||||
let idom = self.compute_idom(ebb, cfg, &func.layout).into();
|
||||
if self.nodes[ebb].idom != idom {
|
||||
self.nodes[ebb].idom = idom;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the immediate dominator for `ebb` using the current `idom` states for the reachable
|
||||
// nodes.
|
||||
fn compute_idom(&self, ebb: Ebb, cfg: &ControlFlowGraph, layout: &Layout) -> Inst {
|
||||
// Get an iterator with just the reachable, already visited predecessors to `ebb`.
|
||||
// Note that during the first pass, `rpo_number` is 1 for reachable blocks that haven't
|
||||
// been visited yet, 0 for unreachable blocks.
|
||||
let mut reachable_preds = cfg.get_predecessors(ebb)
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter(|&(pred, _)| self.nodes[pred].rpo_number > 1);
|
||||
|
||||
// The RPO must visit at least one predecessor before this node.
|
||||
let mut idom = reachable_preds
|
||||
.next()
|
||||
.expect("EBB node must have one reachable predecessor");
|
||||
|
||||
for pred in reachable_preds {
|
||||
idom = self.common_dominator(idom, pred, layout);
|
||||
}
|
||||
|
||||
idom.1
|
||||
}
|
||||
}
|
||||
|
||||
impl DominatorTree {
|
||||
/// When splitting an `Ebb` using `Layout::split_ebb`, you can use this method to update
|
||||
/// the dominator tree locally rather than recomputing it.
|
||||
///
|
||||
/// `old_ebb` is the `Ebb` before splitting, and `new_ebb` is the `Ebb` which now contains
|
||||
/// the second half of `old_ebb`. `split_jump_inst` is the terminator jump instruction of
|
||||
/// `old_ebb` that points to `new_ebb`.
|
||||
pub fn recompute_split_ebb(&mut self, old_ebb: Ebb, new_ebb: Ebb, split_jump_inst: Inst) {
|
||||
if !self.is_reachable(old_ebb) {
|
||||
// old_ebb is unreachable, it stays so and new_ebb is unreachable too
|
||||
*self.nodes.ensure(new_ebb) = Default::default();
|
||||
return;
|
||||
}
|
||||
// We use the RPO comparison on the postorder list so we invert the operands of the
|
||||
// comparison
|
||||
let old_ebb_postorder_index =
|
||||
self.postorder
|
||||
.as_slice()
|
||||
.binary_search_by(|probe| self.rpo_cmp_ebb(old_ebb, *probe))
|
||||
.expect("the old ebb is not declared to the dominator tree");
|
||||
let new_ebb_rpo = self.insert_after_rpo(old_ebb, old_ebb_postorder_index, new_ebb);
|
||||
*self.nodes.ensure(new_ebb) = DomNode {
|
||||
rpo_number: new_ebb_rpo,
|
||||
idom: Some(split_jump_inst).into(),
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// Insert new_ebb just after ebb in the RPO. This function checks
|
||||
// if there is a gap in rpo numbers; if yes it returns the number in the gap and if
|
||||
// not it renumbers.
|
||||
fn insert_after_rpo(&mut self, ebb: Ebb, ebb_postorder_index: usize, new_ebb: Ebb) -> u32 {
|
||||
let ebb_rpo_number = self.nodes[ebb].rpo_number;
|
||||
let inserted_rpo_number = ebb_rpo_number + 1;
|
||||
// If there is no gaps in RPo numbers to insert this new number, we iterate
|
||||
// forward in RPO numbers and backwards in the postorder list of EBBs, renumbering the Ebbs
|
||||
// until we find a gap
|
||||
for (¤t_ebb, current_rpo) in
|
||||
self.postorder[0..ebb_postorder_index]
|
||||
.iter()
|
||||
.rev()
|
||||
.zip(inserted_rpo_number + 1..) {
|
||||
if self.nodes[current_ebb].rpo_number < current_rpo {
|
||||
// There is no gap, we renumber
|
||||
self.nodes[current_ebb].rpo_number = current_rpo;
|
||||
} else {
|
||||
// There is a gap, we stop the renumbering and exit
|
||||
break;
|
||||
}
|
||||
}
|
||||
// TODO: insert in constant time?
|
||||
self.postorder.insert(ebb_postorder_index, new_ebb);
|
||||
inserted_rpo_number
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use cursor::{Cursor, FuncCursor};
|
||||
use flowgraph::ControlFlowGraph;
|
||||
use ir::{Function, InstBuilder, types};
|
||||
use super::*;
|
||||
use ir::types::*;
|
||||
use verifier::verify_context;
|
||||
|
||||
#[test]
|
||||
fn empty() {
|
||||
let func = Function::new();
|
||||
let cfg = ControlFlowGraph::with_function(&func);
|
||||
let dtree = DominatorTree::with_function(&func, &cfg);
|
||||
assert_eq!(0, dtree.nodes.keys().count());
|
||||
assert_eq!(dtree.cfg_postorder(), &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_zero_entry_block() {
|
||||
let mut func = Function::new();
|
||||
let ebb3 = func.dfg.make_ebb();
|
||||
let cond = func.dfg.append_ebb_arg(ebb3, types::I32);
|
||||
let ebb1 = func.dfg.make_ebb();
|
||||
let ebb2 = func.dfg.make_ebb();
|
||||
let ebb0 = func.dfg.make_ebb();
|
||||
|
||||
let mut cur = FuncCursor::new(&mut func);
|
||||
|
||||
cur.insert_ebb(ebb3);
|
||||
let jmp_ebb3_ebb1 = cur.ins().jump(ebb1, &[]);
|
||||
|
||||
cur.insert_ebb(ebb1);
|
||||
let br_ebb1_ebb0 = cur.ins().brnz(cond, ebb0, &[]);
|
||||
let jmp_ebb1_ebb2 = cur.ins().jump(ebb2, &[]);
|
||||
|
||||
cur.insert_ebb(ebb2);
|
||||
cur.ins().jump(ebb0, &[]);
|
||||
|
||||
cur.insert_ebb(ebb0);
|
||||
|
||||
let cfg = ControlFlowGraph::with_function(cur.func);
|
||||
let dt = DominatorTree::with_function(cur.func, &cfg);
|
||||
|
||||
assert_eq!(cur.func.layout.entry_block().unwrap(), ebb3);
|
||||
assert_eq!(dt.idom(ebb3), None);
|
||||
assert_eq!(dt.idom(ebb1).unwrap(), jmp_ebb3_ebb1);
|
||||
assert_eq!(dt.idom(ebb2).unwrap(), jmp_ebb1_ebb2);
|
||||
assert_eq!(dt.idom(ebb0).unwrap(), br_ebb1_ebb0);
|
||||
|
||||
assert!(dt.dominates(br_ebb1_ebb0, br_ebb1_ebb0, &cur.func.layout));
|
||||
assert!(!dt.dominates(br_ebb1_ebb0, jmp_ebb3_ebb1, &cur.func.layout));
|
||||
assert!(dt.dominates(jmp_ebb3_ebb1, br_ebb1_ebb0, &cur.func.layout));
|
||||
|
||||
assert_eq!(dt.rpo_cmp(ebb3, ebb3, &cur.func.layout), Ordering::Equal);
|
||||
assert_eq!(dt.rpo_cmp(ebb3, ebb1, &cur.func.layout), Ordering::Less);
|
||||
assert_eq!(dt.rpo_cmp(ebb3, jmp_ebb3_ebb1, &cur.func.layout),
|
||||
Ordering::Less);
|
||||
assert_eq!(dt.rpo_cmp(jmp_ebb3_ebb1, jmp_ebb1_ebb2, &cur.func.layout),
|
||||
Ordering::Less);
|
||||
|
||||
assert_eq!(dt.cfg_postorder(), &[ebb2, ebb0, ebb1, ebb3]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn backwards_layout() {
|
||||
let mut func = Function::new();
|
||||
let ebb0 = func.dfg.make_ebb();
|
||||
let ebb1 = func.dfg.make_ebb();
|
||||
let ebb2 = func.dfg.make_ebb();
|
||||
|
||||
let mut cur = FuncCursor::new(&mut func);
|
||||
|
||||
cur.insert_ebb(ebb0);
|
||||
let jmp02 = cur.ins().jump(ebb2, &[]);
|
||||
|
||||
cur.insert_ebb(ebb1);
|
||||
let trap = cur.ins().trap();
|
||||
|
||||
cur.insert_ebb(ebb2);
|
||||
let jmp21 = cur.ins().jump(ebb1, &[]);
|
||||
|
||||
let cfg = ControlFlowGraph::with_function(cur.func);
|
||||
let dt = DominatorTree::with_function(cur.func, &cfg);
|
||||
|
||||
assert_eq!(cur.func.layout.entry_block(), Some(ebb0));
|
||||
assert_eq!(dt.idom(ebb0), None);
|
||||
assert_eq!(dt.idom(ebb1), Some(jmp21));
|
||||
assert_eq!(dt.idom(ebb2), Some(jmp02));
|
||||
|
||||
assert!(dt.dominates(ebb0, ebb0, &cur.func.layout));
|
||||
assert!(dt.dominates(ebb0, jmp02, &cur.func.layout));
|
||||
assert!(dt.dominates(ebb0, ebb1, &cur.func.layout));
|
||||
assert!(dt.dominates(ebb0, trap, &cur.func.layout));
|
||||
assert!(dt.dominates(ebb0, ebb2, &cur.func.layout));
|
||||
assert!(dt.dominates(ebb0, jmp21, &cur.func.layout));
|
||||
|
||||
assert!(!dt.dominates(jmp02, ebb0, &cur.func.layout));
|
||||
assert!(dt.dominates(jmp02, jmp02, &cur.func.layout));
|
||||
assert!(dt.dominates(jmp02, ebb1, &cur.func.layout));
|
||||
assert!(dt.dominates(jmp02, trap, &cur.func.layout));
|
||||
assert!(dt.dominates(jmp02, ebb2, &cur.func.layout));
|
||||
assert!(dt.dominates(jmp02, jmp21, &cur.func.layout));
|
||||
|
||||
assert!(!dt.dominates(ebb1, ebb0, &cur.func.layout));
|
||||
assert!(!dt.dominates(ebb1, jmp02, &cur.func.layout));
|
||||
assert!(dt.dominates(ebb1, ebb1, &cur.func.layout));
|
||||
assert!(dt.dominates(ebb1, trap, &cur.func.layout));
|
||||
assert!(!dt.dominates(ebb1, ebb2, &cur.func.layout));
|
||||
assert!(!dt.dominates(ebb1, jmp21, &cur.func.layout));
|
||||
|
||||
assert!(!dt.dominates(trap, ebb0, &cur.func.layout));
|
||||
assert!(!dt.dominates(trap, jmp02, &cur.func.layout));
|
||||
assert!(!dt.dominates(trap, ebb1, &cur.func.layout));
|
||||
assert!(dt.dominates(trap, trap, &cur.func.layout));
|
||||
assert!(!dt.dominates(trap, ebb2, &cur.func.layout));
|
||||
assert!(!dt.dominates(trap, jmp21, &cur.func.layout));
|
||||
|
||||
assert!(!dt.dominates(ebb2, ebb0, &cur.func.layout));
|
||||
assert!(!dt.dominates(ebb2, jmp02, &cur.func.layout));
|
||||
assert!(dt.dominates(ebb2, ebb1, &cur.func.layout));
|
||||
assert!(dt.dominates(ebb2, trap, &cur.func.layout));
|
||||
assert!(dt.dominates(ebb2, ebb2, &cur.func.layout));
|
||||
assert!(dt.dominates(ebb2, jmp21, &cur.func.layout));
|
||||
|
||||
assert!(!dt.dominates(jmp21, ebb0, &cur.func.layout));
|
||||
assert!(!dt.dominates(jmp21, jmp02, &cur.func.layout));
|
||||
assert!(dt.dominates(jmp21, ebb1, &cur.func.layout));
|
||||
assert!(dt.dominates(jmp21, trap, &cur.func.layout));
|
||||
assert!(!dt.dominates(jmp21, ebb2, &cur.func.layout));
|
||||
assert!(dt.dominates(jmp21, jmp21, &cur.func.layout));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renumbering() {
|
||||
let mut func = Function::new();
|
||||
let ebb0 = func.dfg.make_ebb();
|
||||
let ebb100 = func.dfg.make_ebb();
|
||||
|
||||
let mut cur = FuncCursor::new(&mut func);
|
||||
|
||||
cur.insert_ebb(ebb0);
|
||||
let cond = cur.ins().iconst(I32, 0);
|
||||
let inst2 = cur.ins().brz(cond, ebb0, &[]);
|
||||
let inst3 = cur.ins().brz(cond, ebb0, &[]);
|
||||
let inst4 = cur.ins().brz(cond, ebb0, &[]);
|
||||
let inst5 = cur.ins().brz(cond, ebb0, &[]);
|
||||
cur.ins().jump(ebb100, &[]);
|
||||
cur.insert_ebb(ebb100);
|
||||
cur.ins().return_(&[]);
|
||||
|
||||
let mut cfg = ControlFlowGraph::with_function(cur.func);
|
||||
let mut dt = DominatorTree::with_function(cur.func, &cfg);
|
||||
|
||||
let ebb1 = cur.func.dfg.make_ebb();
|
||||
cur.func.layout.split_ebb(ebb1, inst2);
|
||||
cur.goto_bottom(ebb0);
|
||||
let middle_jump_inst = cur.ins().jump(ebb1, &[]);
|
||||
|
||||
dt.recompute_split_ebb(ebb0, ebb1, middle_jump_inst);
|
||||
|
||||
let ebb2 = cur.func.dfg.make_ebb();
|
||||
cur.func.layout.split_ebb(ebb2, inst3);
|
||||
cur.goto_bottom(ebb1);
|
||||
let middle_jump_inst = cur.ins().jump(ebb2, &[]);
|
||||
dt.recompute_split_ebb(ebb1, ebb2, middle_jump_inst);
|
||||
|
||||
let ebb3 = cur.func.dfg.make_ebb();
|
||||
cur.func.layout.split_ebb(ebb3, inst4);
|
||||
cur.goto_bottom(ebb2);
|
||||
let middle_jump_inst = cur.ins().jump(ebb3, &[]);
|
||||
dt.recompute_split_ebb(ebb2, ebb3, middle_jump_inst);
|
||||
|
||||
let ebb4 = cur.func.dfg.make_ebb();
|
||||
cur.func.layout.split_ebb(ebb4, inst5);
|
||||
cur.goto_bottom(ebb3);
|
||||
let middle_jump_inst = cur.ins().jump(ebb4, &[]);
|
||||
dt.recompute_split_ebb(ebb3, ebb4, middle_jump_inst);
|
||||
|
||||
cfg.compute(cur.func);
|
||||
verify_context(cur.func, &cfg, &dt, None).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -1,680 +0,0 @@
|
||||
//! Small lists of entity references.
|
||||
//!
|
||||
//! This module defines an `EntityList<T>` type which provides similar functionality to `Vec<T>`,
|
||||
//! but with some important differences in the implementation:
|
||||
//!
|
||||
//! 1. Memory is allocated from a `ListPool<T>` instead of the global heap.
|
||||
//! 2. The footprint of an entity list is 4 bytes, compared with the 24 bytes for `Vec<T>`.
|
||||
//! 3. An entity list doesn't implement `Drop`, leaving it to the pool to manage memory.
|
||||
//!
|
||||
//! The list pool is intended to be used as a LIFO allocator. After building up a larger data
|
||||
//! structure with many list references, the whole thing can be discarded quickly by clearing the
|
||||
//! pool.
|
||||
//!
|
||||
//! # Safety
|
||||
//!
|
||||
//! Entity lists are not as safe to use as `Vec<T>`, but they never jeopardize Rust's memory safety
|
||||
//! guarantees. These are the problems to be aware of:
|
||||
//!
|
||||
//! - If you lose track of an entity list, its memory won't be recycled until the pool is cleared.
|
||||
//! This can cause the pool to grow very large with leaked lists.
|
||||
//! - If entity lists are used after their pool is cleared, they may contain garbage data, and
|
||||
//! modifying them may corrupt other lists in the pool.
|
||||
//! - If an entity list is used with two different pool instances, both pools are likely to become
|
||||
//! corrupted.
|
||||
//!
|
||||
//! # Implementation
|
||||
//!
|
||||
//! The `EntityList` itself is designed to have the smallest possible footprint. This is important
|
||||
//! because it is used inside very compact data structures like `InstructionData`. The list
|
||||
//! contains only a 32-bit index into the pool's memory vector, pointing to the first element of
|
||||
//! the list.
|
||||
//!
|
||||
//! The pool is just a single `Vec<T>` containing all of the allocated lists. Each list is
|
||||
//! represented as three contiguous parts:
|
||||
//!
|
||||
//! 1. The number of elements in the list.
|
||||
//! 2. The list elements.
|
||||
//! 3. Excess capacity elements.
|
||||
//!
|
||||
//! The total size of the three parts is always a power of two, and the excess capacity is always
|
||||
//! as small as possible. This means that shrinking a list may cause the excess capacity to shrink
|
||||
//! if a smaller power-of-two size becomes available.
|
||||
//!
|
||||
//! Both growing and shrinking a list may cause it to be reallocated in the pool vector.
|
||||
//!
|
||||
//! The index stored in an `EntityList` points to part 2, the list elements. The value 0 is
|
||||
//! reserved for the empty list which isn't allocated in the vector.
|
||||
|
||||
use entity_ref::EntityRef;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::marker::PhantomData;
|
||||
use std::mem;
|
||||
|
||||
/// A small list of entity references allocated from a pool.
|
||||
///
|
||||
/// All of the list methods that take a pool reference must be given the same pool reference every
|
||||
/// time they are called. Otherwise data structures will be corrupted.
|
||||
///
|
||||
/// Entity lists can be cloned, but that operation should only be used as part of cloning the whole
|
||||
/// function they belong to. *Cloning an entity list does not allocate new memory for the clone*.
|
||||
/// It creates an alias of the same memory.
|
||||
///
|
||||
/// Entity lists can also be hashed and compared for equality, but those operations just panic if,
|
||||
/// they're ever actually called, because it's not possible to compare the contents of the list
|
||||
/// without the pool reference.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EntityList<T: EntityRef> {
|
||||
index: u32,
|
||||
unused: PhantomData<T>,
|
||||
}
|
||||
|
||||
/// Create an empty list.
|
||||
impl<T: EntityRef> Default for EntityList<T> {
|
||||
fn default() -> Self {
|
||||
EntityList {
|
||||
index: 0,
|
||||
unused: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EntityRef> Hash for EntityList<T> {
|
||||
fn hash<H: Hasher>(&self, _: &mut H) {
|
||||
panic!("hash called on EntityList");
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EntityRef> PartialEq for EntityList<T> {
|
||||
fn eq(&self, _: &EntityList<T>) -> bool {
|
||||
panic!("eq called on EntityList");
|
||||
}
|
||||
}
|
||||
impl<T: EntityRef> Eq for EntityList<T> {}
|
||||
|
||||
/// A memory pool for storing lists of `T`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ListPool<T: EntityRef> {
|
||||
// The main array containing the lists.
|
||||
data: Vec<T>,
|
||||
|
||||
// Heads of the free lists, one for each size class.
|
||||
free: Vec<usize>,
|
||||
}
|
||||
|
||||
/// Lists are allocated in sizes that are powers of two, starting from 4.
|
||||
/// Each power of two is assigned a size class number, so the size is `4 << SizeClass`.
|
||||
type SizeClass = u8;
|
||||
|
||||
/// Get the size of a given size class. The size includes the length field, so the maximum list
|
||||
/// length is one less than the class size.
|
||||
fn sclass_size(sclass: SizeClass) -> usize {
|
||||
4 << sclass
|
||||
}
|
||||
|
||||
/// Get the size class to use for a given list length.
|
||||
/// This always leaves room for the length element in addition to the list elements.
|
||||
fn sclass_for_length(len: usize) -> SizeClass {
|
||||
30 - (len as u32 | 3).leading_zeros() as SizeClass
|
||||
}
|
||||
|
||||
/// Is `len` the minimum length in its size class?
|
||||
fn is_sclass_min_length(len: usize) -> bool {
|
||||
len > 3 && len.is_power_of_two()
|
||||
}
|
||||
|
||||
impl<T: EntityRef> ListPool<T> {
|
||||
/// Create a new list pool.
|
||||
pub fn new() -> ListPool<T> {
|
||||
ListPool {
|
||||
data: Vec::new(),
|
||||
free: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear the pool, forgetting about all lists that use it.
|
||||
///
|
||||
/// This invalidates any existing entity lists that used this pool to allocate memory.
|
||||
///
|
||||
/// The pool's memory is not released to the operating system, but kept around for faster
|
||||
/// allocation in the future.
|
||||
pub fn clear(&mut self) {
|
||||
self.data.clear();
|
||||
self.free.clear();
|
||||
}
|
||||
|
||||
/// Read the length of a list field, if it exists.
|
||||
fn len_of(&self, list: &EntityList<T>) -> Option<usize> {
|
||||
let idx = list.index as usize;
|
||||
// `idx` points at the list elements. The list length is encoded in the element immediately
|
||||
// before the list elements.
|
||||
//
|
||||
// The `wrapping_sub` handles the special case 0, which is the empty list. This way, the
|
||||
// cost of the bounds check that we have to pay anyway is co-opted to handle the special
|
||||
// case of the empty list.
|
||||
self.data.get(idx.wrapping_sub(1)).map(|len| len.index())
|
||||
}
|
||||
|
||||
/// Allocate a storage block with a size given by `sclass`.
|
||||
///
|
||||
/// Returns the first index of an available segment of `self.data` containing
|
||||
/// `sclass_size(sclass)` elements.
|
||||
fn alloc(&mut self, sclass: SizeClass) -> usize {
|
||||
// First try the free list for this size class.
|
||||
match self.free.get(sclass as usize).cloned() {
|
||||
Some(head) if head > 0 => {
|
||||
// The free list pointers are offset by 1, using 0 to terminate the list.
|
||||
// A block on the free list has two entries: `[ 0, next ]`.
|
||||
// The 0 is where the length field would be stored for a block in use.
|
||||
// The free list heads and the next pointer point at the `next` field.
|
||||
self.free[sclass as usize] = self.data[head].index();
|
||||
head - 1
|
||||
}
|
||||
_ => {
|
||||
// Nothing on the free list. Allocate more memory.
|
||||
let offset = self.data.len();
|
||||
// We don't want to mess around with uninitialized data.
|
||||
// Just fill it up with nulls.
|
||||
self.data.resize(offset + sclass_size(sclass), T::new(0));
|
||||
offset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Free a storage block with a size given by `sclass`.
|
||||
///
|
||||
/// This must be a block that was previously allocated by `alloc()` with the same size class.
|
||||
fn free(&mut self, block: usize, sclass: SizeClass) {
|
||||
let sclass = sclass as usize;
|
||||
|
||||
// Make sure we have a free-list head for `sclass`.
|
||||
if self.free.len() <= sclass {
|
||||
self.free.resize(sclass + 1, 0);
|
||||
}
|
||||
|
||||
// Make sure the length field is cleared.
|
||||
self.data[block] = T::new(0);
|
||||
// Insert the block on the free list which is a single linked list.
|
||||
self.data[block + 1] = T::new(self.free[sclass]);
|
||||
self.free[sclass] = block + 1
|
||||
}
|
||||
|
||||
/// Returns two mutable slices representing the two requested blocks.
|
||||
///
|
||||
/// The two returned slices can be longer than the blocks. Each block is located at the front
|
||||
/// of the respective slice.
|
||||
fn mut_slices(&mut self, block0: usize, block1: usize) -> (&mut [T], &mut [T]) {
|
||||
if block0 < block1 {
|
||||
let (s0, s1) = self.data.split_at_mut(block1);
|
||||
(&mut s0[block0..], s1)
|
||||
} else {
|
||||
let (s1, s0) = self.data.split_at_mut(block0);
|
||||
(s0, &mut s1[block1..])
|
||||
}
|
||||
}
|
||||
|
||||
/// Reallocate a block to a different size class.
|
||||
///
|
||||
/// Copy `elems_to_copy` elements from the old to the new block.
|
||||
fn realloc(&mut self,
|
||||
block: usize,
|
||||
from_sclass: SizeClass,
|
||||
to_sclass: SizeClass,
|
||||
elems_to_copy: usize)
|
||||
-> usize {
|
||||
assert!(elems_to_copy <= sclass_size(from_sclass));
|
||||
assert!(elems_to_copy <= sclass_size(to_sclass));
|
||||
let new_block = self.alloc(to_sclass);
|
||||
|
||||
if elems_to_copy > 0 {
|
||||
let (old, new) = self.mut_slices(block, new_block);
|
||||
(&mut new[0..elems_to_copy]).copy_from_slice(&old[0..elems_to_copy]);
|
||||
}
|
||||
|
||||
self.free(block, from_sclass);
|
||||
new_block
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EntityRef> EntityList<T> {
|
||||
/// Create a new empty list.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns `true` if the list has a length of 0.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
// 0 is a magic value for the empty list. Any list in the pool array must have a positive
|
||||
// length.
|
||||
self.index == 0
|
||||
}
|
||||
|
||||
/// Get the number of elements in the list.
|
||||
pub fn len(&self, pool: &ListPool<T>) -> usize {
|
||||
// Both the empty list and any invalidated old lists will return `None`.
|
||||
pool.len_of(self).unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Returns `true` if the list is valid
|
||||
pub fn is_valid(&self, pool: &ListPool<T>) -> bool {
|
||||
// We consider an empty list to be valid
|
||||
self.is_empty() || pool.len_of(self) != None
|
||||
}
|
||||
|
||||
/// Get the list as a slice.
|
||||
pub fn as_slice<'a>(&'a self, pool: &'a ListPool<T>) -> &'a [T] {
|
||||
let idx = self.index as usize;
|
||||
match pool.len_of(self) {
|
||||
None => &[],
|
||||
Some(len) => &pool.data[idx..idx + len],
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a single element from the list.
|
||||
pub fn get(&self, index: usize, pool: &ListPool<T>) -> Option<T> {
|
||||
self.as_slice(pool).get(index).cloned()
|
||||
}
|
||||
|
||||
/// Get the first element from the list.
|
||||
pub fn first(&self, pool: &ListPool<T>) -> Option<T> {
|
||||
if self.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(pool.data[self.index as usize])
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the list as a mutable slice.
|
||||
pub fn as_mut_slice<'a>(&'a mut self, pool: &'a mut ListPool<T>) -> &'a mut [T] {
|
||||
let idx = self.index as usize;
|
||||
match pool.len_of(self) {
|
||||
None => &mut [],
|
||||
Some(len) => &mut pool.data[idx..idx + len],
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a mutable reference to a single element from the list.
|
||||
pub fn get_mut<'a>(&'a mut self, index: usize, pool: &'a mut ListPool<T>) -> Option<&'a mut T> {
|
||||
self.as_mut_slice(pool).get_mut(index)
|
||||
}
|
||||
|
||||
/// Removes all elements from the list.
|
||||
///
|
||||
/// The memory used by the list is put back in the pool.
|
||||
pub fn clear(&mut self, pool: &mut ListPool<T>) {
|
||||
let idx = self.index as usize;
|
||||
match pool.len_of(self) {
|
||||
None => assert_eq!(idx, 0, "Invalid pool"),
|
||||
Some(len) => pool.free(idx - 1, sclass_for_length(len)),
|
||||
}
|
||||
// Switch back to the empty list representation which has no storage.
|
||||
self.index = 0;
|
||||
}
|
||||
|
||||
/// Take all elements from this list and return them as a new list. Leave this list empty.
|
||||
///
|
||||
/// This is the equivalent of `Option::take()`.
|
||||
pub fn take(&mut self) -> EntityList<T> {
|
||||
mem::replace(self, Default::default())
|
||||
}
|
||||
|
||||
/// Appends an element to the back of the list.
|
||||
/// Returns the index where the element was inserted.
|
||||
pub fn push(&mut self, element: T, pool: &mut ListPool<T>) -> usize {
|
||||
let idx = self.index as usize;
|
||||
match pool.len_of(self) {
|
||||
None => {
|
||||
// This is an empty list. Allocate a block and set length=1.
|
||||
assert_eq!(idx, 0, "Invalid pool");
|
||||
let block = pool.alloc(sclass_for_length(1));
|
||||
pool.data[block] = T::new(1);
|
||||
pool.data[block + 1] = element;
|
||||
self.index = (block + 1) as u32;
|
||||
0
|
||||
}
|
||||
Some(len) => {
|
||||
// Do we need to reallocate?
|
||||
let new_len = len + 1;
|
||||
let block;
|
||||
if is_sclass_min_length(new_len) {
|
||||
// Reallocate, preserving length + all old elements.
|
||||
let sclass = sclass_for_length(len);
|
||||
block = pool.realloc(idx - 1, sclass, sclass + 1, len + 1);
|
||||
self.index = (block + 1) as u32;
|
||||
} else {
|
||||
block = idx - 1;
|
||||
}
|
||||
pool.data[block + new_len] = element;
|
||||
pool.data[block] = T::new(new_len);
|
||||
len
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Grow list by adding `count` uninitialized elements at the end.
|
||||
///
|
||||
/// Returns a mutable slice representing the whole list.
|
||||
fn grow<'a>(&'a mut self, count: usize, pool: &'a mut ListPool<T>) -> &'a mut [T] {
|
||||
let idx = self.index as usize;
|
||||
let new_len;
|
||||
let block;
|
||||
match pool.len_of(self) {
|
||||
None => {
|
||||
// This is an empty list. Allocate a block.
|
||||
assert_eq!(idx, 0, "Invalid pool");
|
||||
if count == 0 {
|
||||
return &mut [];
|
||||
}
|
||||
new_len = count;
|
||||
block = pool.alloc(sclass_for_length(new_len));
|
||||
self.index = (block + 1) as u32;
|
||||
}
|
||||
Some(len) => {
|
||||
// Do we need to reallocate?
|
||||
let sclass = sclass_for_length(len);
|
||||
new_len = len + count;
|
||||
let new_sclass = sclass_for_length(new_len);
|
||||
if new_sclass != sclass {
|
||||
block = pool.realloc(idx - 1, sclass, new_sclass, len + 1);
|
||||
self.index = (block + 1) as u32;
|
||||
} else {
|
||||
block = idx - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
pool.data[block] = T::new(new_len);
|
||||
&mut pool.data[block + 1..block + 1 + new_len]
|
||||
}
|
||||
|
||||
/// Appends multiple elements to the back of the list.
|
||||
pub fn extend<I>(&mut self, elements: I, pool: &mut ListPool<T>)
|
||||
where I: IntoIterator<Item = T>
|
||||
{
|
||||
// TODO: use `size_hint()` to reduce reallocations.
|
||||
for x in elements {
|
||||
self.push(x, pool);
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts an element as position `index` in the list, shifting all elements after it to the
|
||||
/// right.
|
||||
pub fn insert(&mut self, index: usize, element: T, pool: &mut ListPool<T>) {
|
||||
// Increase size by 1.
|
||||
self.push(element, pool);
|
||||
|
||||
// Move tail elements.
|
||||
let seq = self.as_mut_slice(pool);
|
||||
if index < seq.len() {
|
||||
let tail = &mut seq[index..];
|
||||
for i in (1..tail.len()).rev() {
|
||||
tail[i] = tail[i - 1];
|
||||
}
|
||||
tail[0] = element;
|
||||
} else {
|
||||
assert_eq!(index, seq.len());
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes the element at position `index` from the list. Potentially linear complexity.
|
||||
pub fn remove(&mut self, index: usize, pool: &mut ListPool<T>) {
|
||||
let len;
|
||||
{
|
||||
let seq = self.as_mut_slice(pool);
|
||||
len = seq.len();
|
||||
assert!(index < len);
|
||||
|
||||
// Copy elements down.
|
||||
for i in index..len - 1 {
|
||||
seq[i] = seq[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we deleted the last element.
|
||||
if len == 1 {
|
||||
self.clear(pool);
|
||||
return;
|
||||
}
|
||||
|
||||
// Do we need to reallocate to a smaller size class?
|
||||
let mut block = self.index as usize - 1;
|
||||
if is_sclass_min_length(len) {
|
||||
let sclass = sclass_for_length(len);
|
||||
block = pool.realloc(block, sclass, sclass - 1, len);
|
||||
self.index = (block + 1) as u32;
|
||||
}
|
||||
|
||||
// Finally adjust the length.
|
||||
pool.data[block] = T::new(len - 1);
|
||||
}
|
||||
|
||||
/// Removes the element at `index` in constant time by switching it with the last element of
|
||||
/// the list.
|
||||
pub fn swap_remove(&mut self, index: usize, pool: &mut ListPool<T>) {
|
||||
let len = self.len(pool);
|
||||
assert!(index < len);
|
||||
if index == len - 1 {
|
||||
self.remove(index, pool);
|
||||
} else {
|
||||
{
|
||||
let seq = self.as_mut_slice(pool);
|
||||
seq.swap(index, len - 1);
|
||||
}
|
||||
self.remove(len - 1, pool);
|
||||
}
|
||||
}
|
||||
|
||||
/// Grow the list by inserting `count` elements at `index`.
|
||||
///
|
||||
/// The new elements are not initialized, they will contain whatever happened to be in memory.
|
||||
/// Since the memory comes from the pool, this will be either zero entity references or
|
||||
/// whatever where in a previously deallocated list.
|
||||
pub fn grow_at(&mut self, index: usize, count: usize, pool: &mut ListPool<T>) {
|
||||
let mut data = self.grow(count, pool);
|
||||
|
||||
// Copy elements after `index` up.
|
||||
for i in (index + count..data.len()).rev() {
|
||||
data[i] = data[i - count];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use super::{sclass_size, sclass_for_length};
|
||||
use ir::Inst;
|
||||
use entity_ref::EntityRef;
|
||||
|
||||
#[test]
|
||||
fn size_classes() {
|
||||
assert_eq!(sclass_size(0), 4);
|
||||
assert_eq!(sclass_for_length(0), 0);
|
||||
assert_eq!(sclass_for_length(1), 0);
|
||||
assert_eq!(sclass_for_length(2), 0);
|
||||
assert_eq!(sclass_for_length(3), 0);
|
||||
assert_eq!(sclass_for_length(4), 1);
|
||||
assert_eq!(sclass_for_length(7), 1);
|
||||
assert_eq!(sclass_for_length(8), 2);
|
||||
assert_eq!(sclass_size(1), 8);
|
||||
for l in 0..300 {
|
||||
assert!(sclass_size(sclass_for_length(l)) >= l + 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_allocator() {
|
||||
let mut pool = ListPool::<Inst>::new();
|
||||
let b1 = pool.alloc(0);
|
||||
let b2 = pool.alloc(1);
|
||||
let b3 = pool.alloc(0);
|
||||
assert_ne!(b1, b2);
|
||||
assert_ne!(b1, b3);
|
||||
assert_ne!(b2, b3);
|
||||
pool.free(b2, 1);
|
||||
let b2a = pool.alloc(1);
|
||||
let b2b = pool.alloc(1);
|
||||
assert_ne!(b2a, b2b);
|
||||
// One of these should reuse the freed block.
|
||||
assert!(b2a == b2 || b2b == b2);
|
||||
|
||||
// Check the free lists for a size class smaller than the largest seen so far.
|
||||
pool.free(b1, 0);
|
||||
pool.free(b3, 0);
|
||||
let b1a = pool.alloc(0);
|
||||
let b3a = pool.alloc(0);
|
||||
assert_ne!(b1a, b3a);
|
||||
assert!(b1a == b1 || b1a == b3);
|
||||
assert!(b3a == b1 || b3a == b3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_list() {
|
||||
let pool = &mut ListPool::<Inst>::new();
|
||||
let mut list = EntityList::<Inst>::default();
|
||||
{
|
||||
let ilist = &list;
|
||||
assert!(ilist.is_empty());
|
||||
assert_eq!(ilist.len(pool), 0);
|
||||
assert_eq!(ilist.as_slice(pool), &[]);
|
||||
assert_eq!(ilist.get(0, pool), None);
|
||||
assert_eq!(ilist.get(100, pool), None);
|
||||
}
|
||||
assert_eq!(list.as_mut_slice(pool), &[]);
|
||||
assert_eq!(list.get_mut(0, pool), None);
|
||||
assert_eq!(list.get_mut(100, pool), None);
|
||||
|
||||
list.clear(pool);
|
||||
assert!(list.is_empty());
|
||||
assert_eq!(list.len(pool), 0);
|
||||
assert_eq!(list.as_slice(pool), &[]);
|
||||
assert_eq!(list.first(pool), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn push() {
|
||||
let pool = &mut ListPool::<Inst>::new();
|
||||
let mut list = EntityList::<Inst>::default();
|
||||
|
||||
let i1 = Inst::new(1);
|
||||
let i2 = Inst::new(2);
|
||||
let i3 = Inst::new(3);
|
||||
let i4 = Inst::new(4);
|
||||
|
||||
assert_eq!(list.push(i1, pool), 0);
|
||||
assert_eq!(list.len(pool), 1);
|
||||
assert!(!list.is_empty());
|
||||
assert_eq!(list.as_slice(pool), &[i1]);
|
||||
assert_eq!(list.first(pool), Some(i1));
|
||||
assert_eq!(list.get(0, pool), Some(i1));
|
||||
assert_eq!(list.get(1, pool), None);
|
||||
|
||||
assert_eq!(list.push(i2, pool), 1);
|
||||
assert_eq!(list.len(pool), 2);
|
||||
assert!(!list.is_empty());
|
||||
assert_eq!(list.as_slice(pool), &[i1, i2]);
|
||||
assert_eq!(list.first(pool), Some(i1));
|
||||
assert_eq!(list.get(0, pool), Some(i1));
|
||||
assert_eq!(list.get(1, pool), Some(i2));
|
||||
assert_eq!(list.get(2, pool), None);
|
||||
|
||||
assert_eq!(list.push(i3, pool), 2);
|
||||
assert_eq!(list.len(pool), 3);
|
||||
assert!(!list.is_empty());
|
||||
assert_eq!(list.as_slice(pool), &[i1, i2, i3]);
|
||||
assert_eq!(list.first(pool), Some(i1));
|
||||
assert_eq!(list.get(0, pool), Some(i1));
|
||||
assert_eq!(list.get(1, pool), Some(i2));
|
||||
assert_eq!(list.get(2, pool), Some(i3));
|
||||
assert_eq!(list.get(3, pool), None);
|
||||
|
||||
// This triggers a reallocation.
|
||||
assert_eq!(list.push(i4, pool), 3);
|
||||
assert_eq!(list.len(pool), 4);
|
||||
assert!(!list.is_empty());
|
||||
assert_eq!(list.as_slice(pool), &[i1, i2, i3, i4]);
|
||||
assert_eq!(list.first(pool), Some(i1));
|
||||
assert_eq!(list.get(0, pool), Some(i1));
|
||||
assert_eq!(list.get(1, pool), Some(i2));
|
||||
assert_eq!(list.get(2, pool), Some(i3));
|
||||
assert_eq!(list.get(3, pool), Some(i4));
|
||||
assert_eq!(list.get(4, pool), None);
|
||||
|
||||
list.extend([i1, i1, i2, i2, i3, i3, i4, i4].iter().cloned(), pool);
|
||||
assert_eq!(list.len(pool), 12);
|
||||
assert_eq!(list.as_slice(pool),
|
||||
&[i1, i2, i3, i4, i1, i1, i2, i2, i3, i3, i4, i4]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_remove() {
|
||||
let pool = &mut ListPool::<Inst>::new();
|
||||
let mut list = EntityList::<Inst>::default();
|
||||
|
||||
let i1 = Inst::new(1);
|
||||
let i2 = Inst::new(2);
|
||||
let i3 = Inst::new(3);
|
||||
let i4 = Inst::new(4);
|
||||
|
||||
list.insert(0, i4, pool);
|
||||
assert_eq!(list.as_slice(pool), &[i4]);
|
||||
|
||||
list.insert(0, i3, pool);
|
||||
assert_eq!(list.as_slice(pool), &[i3, i4]);
|
||||
|
||||
list.insert(2, i2, pool);
|
||||
assert_eq!(list.as_slice(pool), &[i3, i4, i2]);
|
||||
|
||||
list.insert(2, i1, pool);
|
||||
assert_eq!(list.as_slice(pool), &[i3, i4, i1, i2]);
|
||||
|
||||
list.remove(3, pool);
|
||||
assert_eq!(list.as_slice(pool), &[i3, i4, i1]);
|
||||
|
||||
list.remove(2, pool);
|
||||
assert_eq!(list.as_slice(pool), &[i3, i4]);
|
||||
|
||||
list.remove(0, pool);
|
||||
assert_eq!(list.as_slice(pool), &[i4]);
|
||||
|
||||
list.remove(0, pool);
|
||||
assert_eq!(list.as_slice(pool), &[]);
|
||||
assert!(list.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn growing() {
|
||||
let pool = &mut ListPool::<Inst>::new();
|
||||
let mut list = EntityList::<Inst>::default();
|
||||
|
||||
let i1 = Inst::new(1);
|
||||
let i2 = Inst::new(2);
|
||||
let i3 = Inst::new(3);
|
||||
let i4 = Inst::new(4);
|
||||
|
||||
// This is not supposed to change the list.
|
||||
list.grow_at(0, 0, pool);
|
||||
assert_eq!(list.len(pool), 0);
|
||||
assert!(list.is_empty());
|
||||
|
||||
list.grow_at(0, 2, pool);
|
||||
assert_eq!(list.len(pool), 2);
|
||||
|
||||
list.as_mut_slice(pool).copy_from_slice(&[i2, i3]);
|
||||
|
||||
list.grow_at(1, 0, pool);
|
||||
assert_eq!(list.as_slice(pool), &[i2, i3]);
|
||||
|
||||
list.grow_at(1, 1, pool);
|
||||
list.as_mut_slice(pool)[1] = i1;
|
||||
assert_eq!(list.as_slice(pool), &[i2, i1, i3]);
|
||||
|
||||
// Append nothing at the end.
|
||||
list.grow_at(3, 0, pool);
|
||||
assert_eq!(list.as_slice(pool), &[i2, i1, i3]);
|
||||
|
||||
// Append something at the end.
|
||||
list.grow_at(3, 1, pool);
|
||||
list.as_mut_slice(pool)[3] = i4;
|
||||
assert_eq!(list.as_slice(pool), &[i2, i1, i3, i4]);
|
||||
}
|
||||
}
|
||||
@@ -1,263 +0,0 @@
|
||||
//! Densely numbered entity references as mapping keys.
|
||||
//!
|
||||
//! The `EntityMap` data structure uses the dense index space to implement a map with a vector.
|
||||
//! There are primary and secondary entity maps:
|
||||
//!
|
||||
//! - A *primary* `EntityMap` contains the main definition of an entity, and it can be used to
|
||||
//! allocate new entity references with the `push` method. The values stores in a primary map
|
||||
//! must implement the `PrimaryEntityData` marker trait.
|
||||
//! - A *secondary* `EntityMap` contains additional data about entities kept in a primary map. The
|
||||
//! values need to implement `Clone + Default` traits so the map can be grown with `ensure`.
|
||||
|
||||
use entity_ref::EntityRef;
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::{Index, IndexMut};
|
||||
|
||||
/// A mapping `K -> V` for densely indexed entity references.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EntityMap<K, V>
|
||||
where K: EntityRef
|
||||
{
|
||||
elems: Vec<V>,
|
||||
unused: PhantomData<K>,
|
||||
}
|
||||
|
||||
/// Shared `EntityMap` implementation for all value types.
|
||||
impl<K, V> EntityMap<K, V>
|
||||
where K: EntityRef
|
||||
{
|
||||
/// Create a new empty map.
|
||||
pub fn new() -> Self {
|
||||
EntityMap {
|
||||
elems: Vec::new(),
|
||||
unused: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if `k` is a valid key in the map.
|
||||
pub fn is_valid(&self, k: K) -> bool {
|
||||
k.index() < self.elems.len()
|
||||
}
|
||||
|
||||
/// Get the element at `k` if it exists.
|
||||
pub fn get(&self, k: K) -> Option<&V> {
|
||||
self.elems.get(k.index())
|
||||
}
|
||||
|
||||
/// Is this map completely empty?
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.elems.is_empty()
|
||||
}
|
||||
|
||||
/// Remove all entries from this map.
|
||||
pub fn clear(&mut self) {
|
||||
self.elems.clear()
|
||||
}
|
||||
|
||||
/// Iterate over all the keys in this map.
|
||||
pub fn keys(&self) -> Keys<K> {
|
||||
Keys {
|
||||
pos: 0,
|
||||
rev_pos: self.elems.len(),
|
||||
unused: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A marker trait for data stored in primary entity maps.
|
||||
///
|
||||
/// A primary entity map can be used to allocate new entity references with the `push` method. It
|
||||
/// is important that entity references can't be created anywhere else, so the data stored in a
|
||||
/// primary entity map must be tagged as `PrimaryEntityData` to unlock the `push` method.
|
||||
pub trait PrimaryEntityData {}
|
||||
|
||||
/// Additional methods for primary entry maps only.
|
||||
///
|
||||
/// These are identified by the `PrimaryEntityData` marker trait.
|
||||
impl<K, V> EntityMap<K, V>
|
||||
where K: EntityRef,
|
||||
V: PrimaryEntityData
|
||||
{
|
||||
/// Get the key that will be assigned to the next pushed value.
|
||||
pub fn next_key(&self) -> K {
|
||||
K::new(self.elems.len())
|
||||
}
|
||||
|
||||
/// Append `v` to the mapping, assigning a new key which is returned.
|
||||
pub fn push(&mut self, v: V) -> K {
|
||||
let k = self.next_key();
|
||||
self.elems.push(v);
|
||||
k
|
||||
}
|
||||
|
||||
/// Get the total number of entity references created.
|
||||
pub fn len(&self) -> usize {
|
||||
self.elems.len()
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods for value types that implement `Clone` and `Default`.
|
||||
///
|
||||
/// When the value type implements these additional traits, the `EntityMap` can be resized
|
||||
/// explicitly with the `ensure` method.
|
||||
///
|
||||
/// Use this for secondary maps that are mapping keys created by another primary map.
|
||||
impl<K, V> EntityMap<K, V>
|
||||
where K: EntityRef,
|
||||
V: Clone + Default
|
||||
{
|
||||
/// Create a new secondary `EntityMap` that is prepared to hold `n` elements.
|
||||
///
|
||||
/// Use this when the length of the primary map is known:
|
||||
/// ```
|
||||
/// let secondary_map = EntityMap::with_capacity(primary_map.len());
|
||||
/// ```
|
||||
pub fn with_capacity(n: usize) -> Self {
|
||||
let mut map = EntityMap {
|
||||
elems: Vec::with_capacity(n),
|
||||
unused: PhantomData,
|
||||
};
|
||||
map.elems.resize(n, V::default());
|
||||
map
|
||||
}
|
||||
|
||||
/// Resize the map to have `n` entries by adding default entries as needed.
|
||||
pub fn resize(&mut self, n: usize) {
|
||||
self.elems.resize(n, V::default());
|
||||
}
|
||||
|
||||
/// Ensure that `k` is a valid key but adding default entries if necessary.
|
||||
///
|
||||
/// Return a mutable reference to the corresponding entry.
|
||||
pub fn ensure(&mut self, k: K) -> &mut V {
|
||||
if !self.is_valid(k) {
|
||||
self.resize(k.index() + 1)
|
||||
}
|
||||
&mut self.elems[k.index()]
|
||||
}
|
||||
|
||||
/// Get the element at `k` or the default value if `k` is out of range.
|
||||
pub fn get_or_default(&self, k: K) -> V {
|
||||
self.elems.get(k.index()).cloned().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Immutable indexing into an `EntityMap`.
|
||||
/// The indexed value must be in the map, either because it was created by `push`, or the key was
|
||||
/// passed to `ensure`.
|
||||
impl<K, V> Index<K> for EntityMap<K, V>
|
||||
where K: EntityRef
|
||||
{
|
||||
type Output = V;
|
||||
|
||||
fn index(&self, k: K) -> &V {
|
||||
&self.elems[k.index()]
|
||||
}
|
||||
}
|
||||
|
||||
/// Mutable indexing into an `EntityMap`.
|
||||
/// Use `ensure` instead if the key is not known to be valid.
|
||||
impl<K, V> IndexMut<K> for EntityMap<K, V>
|
||||
where K: EntityRef
|
||||
{
|
||||
fn index_mut(&mut self, k: K) -> &mut V {
|
||||
&mut self.elems[k.index()]
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over all keys in order.
|
||||
pub struct Keys<K>
|
||||
where K: EntityRef
|
||||
{
|
||||
pos: usize,
|
||||
rev_pos: usize,
|
||||
unused: PhantomData<K>,
|
||||
}
|
||||
|
||||
impl<K> Iterator for Keys<K>
|
||||
where K: EntityRef
|
||||
{
|
||||
type Item = K;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.pos < self.rev_pos {
|
||||
let k = K::new(self.pos);
|
||||
self.pos += 1;
|
||||
Some(k)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K> DoubleEndedIterator for Keys<K>
|
||||
where K: EntityRef
|
||||
{
|
||||
fn next_back(&mut self) -> Option<Self::Item> {
|
||||
if self.rev_pos > self.pos {
|
||||
let k = K::new(self.rev_pos - 1);
|
||||
self.rev_pos -= 1;
|
||||
Some(k)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// `EntityRef` impl for testing.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
struct E(u32);
|
||||
|
||||
impl EntityRef for E {
|
||||
fn new(i: usize) -> Self {
|
||||
E(i as u32)
|
||||
}
|
||||
fn index(self) -> usize {
|
||||
self.0 as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl PrimaryEntityData for isize {}
|
||||
|
||||
#[test]
|
||||
fn basic() {
|
||||
let r0 = E(0);
|
||||
let r1 = E(1);
|
||||
let r2 = E(2);
|
||||
let mut m = EntityMap::new();
|
||||
|
||||
let v: Vec<E> = m.keys().collect();
|
||||
assert_eq!(v, []);
|
||||
|
||||
assert!(!m.is_valid(r0));
|
||||
m.ensure(r2);
|
||||
m[r2] = 3;
|
||||
assert!(m.is_valid(r1));
|
||||
m[r1] = 5;
|
||||
|
||||
assert_eq!(m[r1], 5);
|
||||
assert_eq!(m[r2], 3);
|
||||
|
||||
let v: Vec<E> = m.keys().collect();
|
||||
assert_eq!(v, [r0, r1, r2]);
|
||||
|
||||
let shared = &m;
|
||||
assert_eq!(shared[r0], 0);
|
||||
assert_eq!(shared[r1], 5);
|
||||
assert_eq!(shared[r2], 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn push() {
|
||||
let mut m = EntityMap::new();
|
||||
let k1: E = m.push(12);
|
||||
let k2 = m.push(33);
|
||||
|
||||
assert_eq!(m[k1], 12);
|
||||
assert_eq!(m[k2], 33);
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
//! Densely numbered entity references as mapping keys.
|
||||
//!
|
||||
//! This module defines an `EntityRef` trait that should be implemented by reference types wrapping
|
||||
//! a small integer index.
|
||||
|
||||
/// A type wrapping a small integer index should implement `EntityRef` so it can be used as the key
|
||||
/// of an `EntityMap` or `SparseMap`.
|
||||
pub trait EntityRef: Copy + Eq {
|
||||
/// Create a new entity reference from a small integer.
|
||||
/// This should crash if the requested index is not representable.
|
||||
fn new(usize) -> Self;
|
||||
|
||||
/// Get the index that was used to create this entity reference.
|
||||
fn index(self) -> usize;
|
||||
}
|
||||
|
||||
/// Macro which provides the common implementation of a 32-bit entity reference.
|
||||
#[macro_export]
|
||||
macro_rules! entity_impl {
|
||||
// Basic traits.
|
||||
($entity:ident) => {
|
||||
impl $crate::entity_ref::EntityRef for $entity {
|
||||
fn new(index: usize) -> Self {
|
||||
assert!(index < (::std::u32::MAX as usize));
|
||||
$entity(index as u32)
|
||||
}
|
||||
|
||||
fn index(self) -> usize {
|
||||
self.0 as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl $crate::packed_option::ReservedValue for $entity {
|
||||
fn reserved_value() -> $entity {
|
||||
$entity(::std::u32::MAX)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Include basic `Display` impl using the given display prefix.
|
||||
// Display an `Ebb` reference as "ebb12".
|
||||
($entity:ident, $display_prefix:expr) => {
|
||||
entity_impl!($entity);
|
||||
|
||||
impl ::std::fmt::Display for $entity {
|
||||
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||
write!(f, "{}{}", $display_prefix, self.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,259 +0,0 @@
|
||||
//! A control flow graph represented as mappings of extended basic blocks to their predecessors
|
||||
//! and successors.
|
||||
//!
|
||||
//! Successors are represented as extended basic blocks while predecessors are represented by basic
|
||||
//! blocks. Basic blocks are denoted by tuples of EBB and branch/jump instructions. Each
|
||||
//! predecessor tuple corresponds to the end of a basic block.
|
||||
//!
|
||||
//! ```c
|
||||
//! Ebb0:
|
||||
//! ... ; beginning of basic block
|
||||
//!
|
||||
//! ...
|
||||
//!
|
||||
//! brz vx, Ebb1 ; end of basic block
|
||||
//!
|
||||
//! ... ; beginning of basic block
|
||||
//!
|
||||
//! ...
|
||||
//!
|
||||
//! jmp Ebb2 ; end of basic block
|
||||
//! ```
|
||||
//!
|
||||
//! Here `Ebb1` and `Ebb2` would each have a single predecessor denoted as `(Ebb0, brz)`
|
||||
//! and `(Ebb0, jmp Ebb2)` respectively.
|
||||
|
||||
use ir::{Function, Inst, Ebb};
|
||||
use ir::instructions::BranchInfo;
|
||||
use entity_map::EntityMap;
|
||||
use std::mem;
|
||||
|
||||
/// A basic block denoted by its enclosing Ebb and last instruction.
|
||||
pub type BasicBlock = (Ebb, Inst);
|
||||
|
||||
/// A container for the successors and predecessors of some Ebb.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct CFGNode {
|
||||
/// EBBs that are the targets of branches and jumps in this EBB.
|
||||
pub successors: Vec<Ebb>,
|
||||
/// Basic blocks that can branch or jump to this EBB.
|
||||
pub predecessors: Vec<BasicBlock>,
|
||||
}
|
||||
|
||||
/// The Control Flow Graph maintains a mapping of ebbs to their predecessors
|
||||
/// and successors where predecessors are basic blocks and successors are
|
||||
/// extended basic blocks.
|
||||
#[derive(Debug)]
|
||||
pub struct ControlFlowGraph {
|
||||
entry_block: Option<Ebb>,
|
||||
data: EntityMap<Ebb, CFGNode>,
|
||||
}
|
||||
|
||||
impl ControlFlowGraph {
|
||||
/// Allocate a new blank control flow graph.
|
||||
pub fn new() -> ControlFlowGraph {
|
||||
ControlFlowGraph {
|
||||
entry_block: None,
|
||||
data: EntityMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocate and compute the control flow graph for `func`.
|
||||
pub fn with_function(func: &Function) -> ControlFlowGraph {
|
||||
let mut cfg = ControlFlowGraph::new();
|
||||
cfg.compute(func);
|
||||
cfg
|
||||
}
|
||||
|
||||
/// Compute the control flow graph of `func`.
|
||||
///
|
||||
/// This will clear and overwrite any information already stored in this data structure.
|
||||
pub fn compute(&mut self, func: &Function) {
|
||||
self.entry_block = func.layout.entry_block();
|
||||
self.data.clear();
|
||||
self.data.resize(func.dfg.num_ebbs());
|
||||
|
||||
for ebb in &func.layout {
|
||||
self.compute_ebb(func, ebb);
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_ebb(&mut self, func: &Function, ebb: Ebb) {
|
||||
for inst in func.layout.ebb_insts(ebb) {
|
||||
match func.dfg[inst].analyze_branch(&func.dfg.value_lists) {
|
||||
BranchInfo::SingleDest(dest, _) => {
|
||||
self.add_edge((ebb, inst), dest);
|
||||
}
|
||||
BranchInfo::Table(jt) => {
|
||||
for (_, dest) in func.jump_tables[jt].entries() {
|
||||
self.add_edge((ebb, inst), dest);
|
||||
}
|
||||
}
|
||||
BranchInfo::NotABranch => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn invalidate_ebb_successors(&mut self, ebb: Ebb) {
|
||||
// Temporarily take ownership because we need mutable access to self.data inside the loop.
|
||||
// Unfortunately borrowck cannot see that our mut accesses to predecessors don't alias
|
||||
// our iteration over successors.
|
||||
let mut successors = mem::replace(&mut self.data[ebb].successors, Vec::new());
|
||||
for suc in successors.iter().cloned() {
|
||||
self.data[suc].predecessors.retain(|&(e, _)| e != ebb);
|
||||
}
|
||||
successors.clear();
|
||||
self.data[ebb].successors = successors;
|
||||
}
|
||||
|
||||
/// Recompute the control flow graph of `ebb`.
|
||||
///
|
||||
/// This is for use after modifying instructions within a specific EBB. It recomputes all edges
|
||||
/// from `ebb` while leaving edges to `ebb` intact. Its functionality a subset of that of the
|
||||
/// more expensive `compute`, and should be used when we know we don't need to recompute the CFG
|
||||
/// from scratch, but rather that our changes have been restricted to specific EBBs.
|
||||
pub fn recompute_ebb(&mut self, func: &Function, ebb: Ebb) {
|
||||
self.invalidate_ebb_successors(ebb);
|
||||
self.compute_ebb(func, ebb);
|
||||
}
|
||||
|
||||
fn add_edge(&mut self, from: BasicBlock, to: Ebb) {
|
||||
self.data[from.0].successors.push(to);
|
||||
self.data[to].predecessors.push(from);
|
||||
}
|
||||
|
||||
/// Get the CFG predecessor basic blocks to `ebb`.
|
||||
pub fn get_predecessors(&self, ebb: Ebb) -> &[BasicBlock] {
|
||||
&self.data[ebb].predecessors
|
||||
}
|
||||
|
||||
/// Get the CFG successors to `ebb`.
|
||||
pub fn get_successors(&self, ebb: Ebb) -> &[Ebb] {
|
||||
&self.data[ebb].successors
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ir::{Function, InstBuilder, Cursor, CursorBase, types};
|
||||
|
||||
#[test]
|
||||
fn empty() {
|
||||
let func = Function::new();
|
||||
ControlFlowGraph::with_function(&func);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_predecessors() {
|
||||
let mut func = Function::new();
|
||||
let ebb0 = func.dfg.make_ebb();
|
||||
let ebb1 = func.dfg.make_ebb();
|
||||
let ebb2 = func.dfg.make_ebb();
|
||||
func.layout.append_ebb(ebb0);
|
||||
func.layout.append_ebb(ebb1);
|
||||
func.layout.append_ebb(ebb2);
|
||||
|
||||
let cfg = ControlFlowGraph::with_function(&func);
|
||||
|
||||
let mut fun_ebbs = func.layout.ebbs();
|
||||
for ebb in func.layout.ebbs() {
|
||||
assert_eq!(ebb, fun_ebbs.next().unwrap());
|
||||
assert_eq!(cfg.get_predecessors(ebb).len(), 0);
|
||||
assert_eq!(cfg.get_successors(ebb).len(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn branches_and_jumps() {
|
||||
let mut func = Function::new();
|
||||
let ebb0 = func.dfg.make_ebb();
|
||||
let cond = func.dfg.append_ebb_arg(ebb0, types::I32);
|
||||
let ebb1 = func.dfg.make_ebb();
|
||||
let ebb2 = func.dfg.make_ebb();
|
||||
|
||||
let br_ebb0_ebb2;
|
||||
let br_ebb1_ebb1;
|
||||
let jmp_ebb0_ebb1;
|
||||
let jmp_ebb1_ebb2;
|
||||
|
||||
{
|
||||
let dfg = &mut func.dfg;
|
||||
let cur = &mut Cursor::new(&mut func.layout);
|
||||
|
||||
cur.insert_ebb(ebb0);
|
||||
br_ebb0_ebb2 = dfg.ins(cur).brnz(cond, ebb2, &[]);
|
||||
jmp_ebb0_ebb1 = dfg.ins(cur).jump(ebb1, &[]);
|
||||
|
||||
cur.insert_ebb(ebb1);
|
||||
br_ebb1_ebb1 = dfg.ins(cur).brnz(cond, ebb1, &[]);
|
||||
jmp_ebb1_ebb2 = dfg.ins(cur).jump(ebb2, &[]);
|
||||
|
||||
cur.insert_ebb(ebb2);
|
||||
}
|
||||
|
||||
let mut cfg = ControlFlowGraph::with_function(&func);
|
||||
|
||||
{
|
||||
let ebb0_predecessors = cfg.get_predecessors(ebb0);
|
||||
let ebb1_predecessors = cfg.get_predecessors(ebb1);
|
||||
let ebb2_predecessors = cfg.get_predecessors(ebb2);
|
||||
|
||||
let ebb0_successors = cfg.get_successors(ebb0);
|
||||
let ebb1_successors = cfg.get_successors(ebb1);
|
||||
let ebb2_successors = cfg.get_successors(ebb2);
|
||||
|
||||
assert_eq!(ebb0_predecessors.len(), 0);
|
||||
assert_eq!(ebb1_predecessors.len(), 2);
|
||||
assert_eq!(ebb2_predecessors.len(), 2);
|
||||
|
||||
assert_eq!(ebb1_predecessors.contains(&(ebb0, jmp_ebb0_ebb1)), true);
|
||||
assert_eq!(ebb1_predecessors.contains(&(ebb1, br_ebb1_ebb1)), true);
|
||||
assert_eq!(ebb2_predecessors.contains(&(ebb0, br_ebb0_ebb2)), true);
|
||||
assert_eq!(ebb2_predecessors.contains(&(ebb1, jmp_ebb1_ebb2)), true);
|
||||
|
||||
assert_eq!(ebb0_successors.len(), 2);
|
||||
assert_eq!(ebb1_successors.len(), 2);
|
||||
assert_eq!(ebb2_successors.len(), 0);
|
||||
|
||||
assert_eq!(ebb0_successors.contains(&ebb1), true);
|
||||
assert_eq!(ebb0_successors.contains(&ebb2), true);
|
||||
assert_eq!(ebb1_successors.contains(&ebb1), true);
|
||||
assert_eq!(ebb1_successors.contains(&ebb2), true);
|
||||
}
|
||||
|
||||
// Change some instructions and recompute ebb0
|
||||
func.dfg.replace(br_ebb0_ebb2).brnz(cond, ebb1, &[]);
|
||||
func.dfg.replace(jmp_ebb0_ebb1).return_(&[]);
|
||||
cfg.recompute_ebb(&mut func, ebb0);
|
||||
let br_ebb0_ebb1 = br_ebb0_ebb2;
|
||||
|
||||
{
|
||||
let ebb0_predecessors = cfg.get_predecessors(ebb0);
|
||||
let ebb1_predecessors = cfg.get_predecessors(ebb1);
|
||||
let ebb2_predecessors = cfg.get_predecessors(ebb2);
|
||||
|
||||
let ebb0_successors = cfg.get_successors(ebb0);
|
||||
let ebb1_successors = cfg.get_successors(ebb1);
|
||||
let ebb2_successors = cfg.get_successors(ebb2);
|
||||
|
||||
assert_eq!(ebb0_predecessors.len(), 0);
|
||||
assert_eq!(ebb1_predecessors.len(), 2);
|
||||
assert_eq!(ebb2_predecessors.len(), 1);
|
||||
|
||||
assert_eq!(ebb1_predecessors.contains(&(ebb0, br_ebb0_ebb1)), true);
|
||||
assert_eq!(ebb1_predecessors.contains(&(ebb1, br_ebb1_ebb1)), true);
|
||||
assert_eq!(ebb2_predecessors.contains(&(ebb0, br_ebb0_ebb2)), false);
|
||||
assert_eq!(ebb2_predecessors.contains(&(ebb1, jmp_ebb1_ebb2)), true);
|
||||
|
||||
assert_eq!(ebb0_successors.len(), 1);
|
||||
assert_eq!(ebb1_successors.len(), 2);
|
||||
assert_eq!(ebb2_successors.len(), 0);
|
||||
|
||||
assert_eq!(ebb0_successors.contains(&ebb1), true);
|
||||
assert_eq!(ebb0_successors.contains(&ebb2), false);
|
||||
assert_eq!(ebb1_successors.contains(&ebb1), true);
|
||||
assert_eq!(ebb1_successors.contains(&ebb2), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,266 +0,0 @@
|
||||
//! Cretonne instruction builder.
|
||||
//!
|
||||
//! A `Builder` provides a convenient interface for inserting instructions into a Cretonne
|
||||
//! function. Many of its methods are generated from the meta language instruction definitions.
|
||||
|
||||
use ir::types;
|
||||
use ir::{InstructionData, DataFlowGraph};
|
||||
use ir::{Opcode, Type, Inst, Value, Ebb, JumpTable, SigRef, FuncRef, StackSlot, ValueList,
|
||||
MemFlags};
|
||||
use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, Offset32, Uoffset32};
|
||||
use ir::condcodes::{IntCC, FloatCC};
|
||||
use isa::RegUnit;
|
||||
|
||||
/// Base trait for instruction builders.
|
||||
///
|
||||
/// The `InstBuilderBase` trait provides the basic functionality required by the methods of the
|
||||
/// generated `InstBuilder` trait. These methods should not normally be used directly. Use the
|
||||
/// methods in the `InstBuilder` trait instead.
|
||||
///
|
||||
/// Any data type that implements `InstBuilderBase` also gets all the methods of the `InstBuilder`
|
||||
/// trait.
|
||||
pub trait InstBuilderBase<'f>: Sized {
|
||||
/// Get an immutable reference to the data flow graph that will hold the constructed
|
||||
/// instructions.
|
||||
fn data_flow_graph(&self) -> &DataFlowGraph;
|
||||
/// Get a mutable reference to the data flow graph that will hold the constructed
|
||||
/// instructions.
|
||||
fn data_flow_graph_mut(&mut self) -> &mut DataFlowGraph;
|
||||
|
||||
/// Insert an instruction and return a reference to it, consuming the builder.
|
||||
///
|
||||
/// The result types may depend on a controlling type variable. For non-polymorphic
|
||||
/// instructions with multiple results, pass `VOID` for the `ctrl_typevar` argument.
|
||||
fn build(self, data: InstructionData, ctrl_typevar: Type) -> (Inst, &'f mut DataFlowGraph);
|
||||
}
|
||||
|
||||
// Include trait code generated by `lib/cretonne/meta/gen_instr.py`.
|
||||
//
|
||||
// This file defines the `InstBuilder` trait as an extension of `InstBuilderBase` with methods per
|
||||
// instruction format and per opcode.
|
||||
include!(concat!(env!("OUT_DIR"), "/builder.rs"));
|
||||
|
||||
/// Any type implementing `InstBuilderBase` gets all the `InstBuilder` methods for free.
|
||||
impl<'f, T: InstBuilderBase<'f>> InstBuilder<'f> for T {}
|
||||
|
||||
/// Base trait for instruction inserters.
|
||||
///
|
||||
/// This is an alternative base trait for an instruction builder to implement.
|
||||
///
|
||||
/// An instruction inserter can be adapted into an instruction builder by wrapping it in an
|
||||
/// `InsertBuilder`. This provides some common functionality for instruction builders that insert
|
||||
/// new instructions, as opposed to the `ReplaceBuilder` which overwrites existing instructions.
|
||||
pub trait InstInserterBase<'f>: Sized {
|
||||
/// Get an immutable reference to the data flow graph.
|
||||
fn data_flow_graph(&self) -> &DataFlowGraph;
|
||||
|
||||
/// Get a mutable reference to the data flow graph.
|
||||
fn data_flow_graph_mut(&mut self) -> &mut DataFlowGraph;
|
||||
|
||||
/// Insert a new instruction which belongs to the DFG.
|
||||
fn insert_built_inst(self, inst: Inst, ctrl_typevar: Type) -> &'f mut DataFlowGraph;
|
||||
}
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// Builder that inserts an instruction at the current position.
|
||||
///
|
||||
/// An `InsertBuilder` is a wrapper for an `InstInserterBase` that turns it into an instruction
|
||||
/// builder with some additional facilities for creating instructions that reuse existing values as
|
||||
/// their results.
|
||||
pub struct InsertBuilder<'f, IIB: InstInserterBase<'f>> {
|
||||
inserter: IIB,
|
||||
unused: PhantomData<&'f u32>,
|
||||
}
|
||||
|
||||
impl<'f, IIB: InstInserterBase<'f>> InsertBuilder<'f, IIB> {
|
||||
/// Create a new builder which inserts instructions at `pos`.
|
||||
/// The `dfg` and `pos.layout` references should be from the same `Function`.
|
||||
pub fn new(inserter: IIB) -> InsertBuilder<'f, IIB> {
|
||||
InsertBuilder {
|
||||
inserter,
|
||||
unused: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Reuse result values in `reuse`.
|
||||
///
|
||||
/// Convert this builder into one that will reuse the provided result values instead of
|
||||
/// allocating new ones. The provided values for reuse must not be attached to anything. Any
|
||||
/// missing result values will be allocated as normal.
|
||||
///
|
||||
/// The `reuse` argument is expected to be an array of `Option<Value>`.
|
||||
pub fn with_results<Array>(self, reuse: Array) -> InsertReuseBuilder<'f, IIB, Array>
|
||||
where Array: AsRef<[Option<Value>]>
|
||||
{
|
||||
InsertReuseBuilder {
|
||||
inserter: self.inserter,
|
||||
reuse,
|
||||
unused: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Reuse a single result value.
|
||||
///
|
||||
/// Convert this into a builder that will reuse `v` as the single result value. The reused
|
||||
/// result value `v` must not be attached to anything.
|
||||
///
|
||||
/// This method should only be used when building an instruction with exactly one result. Use
|
||||
/// `with_results()` for the more general case.
|
||||
pub fn with_result(self, v: Value) -> InsertReuseBuilder<'f, IIB, [Option<Value>; 1]> {
|
||||
// TODO: Specialize this to return a different builder that just attaches `v` instead of
|
||||
// calling `make_inst_results_reusing()`.
|
||||
self.with_results([Some(v)])
|
||||
}
|
||||
}
|
||||
|
||||
impl<'f, IIB: InstInserterBase<'f>> InstBuilderBase<'f> for InsertBuilder<'f, IIB> {
|
||||
fn data_flow_graph(&self) -> &DataFlowGraph {
|
||||
self.inserter.data_flow_graph()
|
||||
}
|
||||
|
||||
fn data_flow_graph_mut(&mut self) -> &mut DataFlowGraph {
|
||||
self.inserter.data_flow_graph_mut()
|
||||
}
|
||||
|
||||
fn build(mut self, data: InstructionData, ctrl_typevar: Type) -> (Inst, &'f mut DataFlowGraph) {
|
||||
let inst;
|
||||
{
|
||||
let dfg = self.inserter.data_flow_graph_mut();
|
||||
inst = dfg.make_inst(data);
|
||||
dfg.make_inst_results(inst, ctrl_typevar);
|
||||
}
|
||||
(inst, self.inserter.insert_built_inst(inst, ctrl_typevar))
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder that inserts a new instruction like `InsertBuilder`, but reusing result values.
|
||||
pub struct InsertReuseBuilder<'f, IIB, Array>
|
||||
where IIB: InstInserterBase<'f>,
|
||||
Array: AsRef<[Option<Value>]>
|
||||
{
|
||||
inserter: IIB,
|
||||
reuse: Array,
|
||||
unused: PhantomData<&'f u32>,
|
||||
}
|
||||
|
||||
impl<'f, IIB, Array> InstBuilderBase<'f> for InsertReuseBuilder<'f, IIB, Array>
|
||||
where IIB: InstInserterBase<'f>,
|
||||
Array: AsRef<[Option<Value>]>
|
||||
{
|
||||
fn data_flow_graph(&self) -> &DataFlowGraph {
|
||||
self.inserter.data_flow_graph()
|
||||
}
|
||||
|
||||
fn data_flow_graph_mut(&mut self) -> &mut DataFlowGraph {
|
||||
self.inserter.data_flow_graph_mut()
|
||||
}
|
||||
|
||||
fn build(mut self, data: InstructionData, ctrl_typevar: Type) -> (Inst, &'f mut DataFlowGraph) {
|
||||
let inst;
|
||||
{
|
||||
let dfg = self.inserter.data_flow_graph_mut();
|
||||
inst = dfg.make_inst(data);
|
||||
// Make an `Interator<Item = Option<Value>>`.
|
||||
let ru = self.reuse.as_ref().iter().cloned();
|
||||
dfg.make_inst_results_reusing(inst, ctrl_typevar, ru);
|
||||
}
|
||||
(inst, self.inserter.insert_built_inst(inst, ctrl_typevar))
|
||||
}
|
||||
}
|
||||
|
||||
/// Instruction builder that replaces an existing instruction.
|
||||
///
|
||||
/// The inserted instruction will have the same `Inst` number as the old one.
|
||||
///
|
||||
/// If the old instruction still has result values attached, it is assumed that the new instruction
|
||||
/// produces the same number and types of results. The old result values are preserved. If the
|
||||
/// replacement instruction format does not support multiple results, the builder panics. It is a
|
||||
/// bug to leave result values dangling.
|
||||
pub struct ReplaceBuilder<'f> {
|
||||
dfg: &'f mut DataFlowGraph,
|
||||
inst: Inst,
|
||||
}
|
||||
|
||||
impl<'f> ReplaceBuilder<'f> {
|
||||
/// Create a `ReplaceBuilder` that will overwrite `inst`.
|
||||
pub fn new(dfg: &'f mut DataFlowGraph, inst: Inst) -> ReplaceBuilder {
|
||||
ReplaceBuilder { dfg, inst }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'f> InstBuilderBase<'f> for ReplaceBuilder<'f> {
|
||||
fn data_flow_graph(&self) -> &DataFlowGraph {
|
||||
self.dfg
|
||||
}
|
||||
|
||||
fn data_flow_graph_mut(&mut self) -> &mut DataFlowGraph {
|
||||
self.dfg
|
||||
}
|
||||
|
||||
fn build(self, data: InstructionData, ctrl_typevar: Type) -> (Inst, &'f mut DataFlowGraph) {
|
||||
// Splat the new instruction on top of the old one.
|
||||
self.dfg[self.inst] = data;
|
||||
|
||||
if !self.dfg.has_results(self.inst) {
|
||||
// The old result values were either detached or non-existent.
|
||||
// Construct new ones.
|
||||
self.dfg.make_inst_results(self.inst, ctrl_typevar);
|
||||
}
|
||||
|
||||
(self.inst, self.dfg)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ir::{Function, Cursor, CursorBase, InstBuilder, ValueDef};
|
||||
use ir::types::*;
|
||||
use ir::condcodes::*;
|
||||
|
||||
#[test]
|
||||
fn types() {
|
||||
let mut func = Function::new();
|
||||
let dfg = &mut func.dfg;
|
||||
let ebb0 = dfg.make_ebb();
|
||||
let arg0 = dfg.append_ebb_arg(ebb0, I32);
|
||||
let pos = &mut Cursor::new(&mut func.layout);
|
||||
pos.insert_ebb(ebb0);
|
||||
|
||||
// Explicit types.
|
||||
let v0 = dfg.ins(pos).iconst(I32, 3);
|
||||
assert_eq!(dfg.value_type(v0), I32);
|
||||
|
||||
// Inferred from inputs.
|
||||
let v1 = dfg.ins(pos).iadd(arg0, v0);
|
||||
assert_eq!(dfg.value_type(v1), I32);
|
||||
|
||||
// Formula.
|
||||
let cmp = dfg.ins(pos).icmp(IntCC::Equal, arg0, v0);
|
||||
assert_eq!(dfg.value_type(cmp), B1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reuse_results() {
|
||||
let mut func = Function::new();
|
||||
let dfg = &mut func.dfg;
|
||||
let ebb0 = dfg.make_ebb();
|
||||
let arg0 = dfg.append_ebb_arg(ebb0, I32);
|
||||
let pos = &mut Cursor::new(&mut func.layout);
|
||||
pos.insert_ebb(ebb0);
|
||||
|
||||
let v0 = dfg.ins(pos).iadd_imm(arg0, 17);
|
||||
assert_eq!(dfg.value_type(v0), I32);
|
||||
let iadd = pos.prev_inst().unwrap();
|
||||
assert_eq!(dfg.value_def(v0), ValueDef::Res(iadd, 0));
|
||||
|
||||
// Detach v0 and reuse it for a different instruction.
|
||||
dfg.clear_results(iadd);
|
||||
let v0b = dfg.ins(pos).with_result(v0).iconst(I32, 3);
|
||||
assert_eq!(v0, v0b);
|
||||
assert_eq!(pos.current_inst(), Some(iadd));
|
||||
let iconst = pos.prev_inst().unwrap();
|
||||
assert!(iadd != iconst);
|
||||
assert_eq!(dfg.value_def(v0), ValueDef::Res(iconst, 0));
|
||||
}
|
||||
}
|
||||
@@ -1,349 +0,0 @@
|
||||
//! Condition codes for the Cretonne code generator.
|
||||
//!
|
||||
//! A condition code here is an enumerated type that determined how to compare two numbers. There
|
||||
//! are different rules for comparing integers and floating point numbers, so they use different
|
||||
//! condition codes.
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Common traits of condition codes.
|
||||
pub trait CondCode: Copy {
|
||||
/// Get the inverse condition code of `self`.
|
||||
///
|
||||
/// The inverse condition code produces the opposite result for all comparisons.
|
||||
/// That is, `cmp CC, x, y` is true if and only if `cmp CC.inverse(), x, y` is false.
|
||||
fn inverse(self) -> Self;
|
||||
|
||||
/// Get the reversed condition code for `self`.
|
||||
///
|
||||
/// The reversed condition code produces the same result as swapping `x` and `y` in the
|
||||
/// comparison. That is, `cmp CC, x, y` is the same as `cmp CC.reverse(), y, x`.
|
||||
fn reverse(self) -> Self;
|
||||
}
|
||||
|
||||
/// Condition code for comparing integers.
|
||||
///
|
||||
/// This condition code is used by the `icmp` instruction to compare integer values. There are
|
||||
/// separate codes for comparing the integers as signed or unsigned numbers where it makes a
|
||||
/// difference.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum IntCC {
|
||||
/// `==`.
|
||||
Equal,
|
||||
/// `!=`.
|
||||
NotEqual,
|
||||
/// Signed `<`.
|
||||
SignedLessThan,
|
||||
/// Signed `>=`.
|
||||
SignedGreaterThanOrEqual,
|
||||
/// Signed `>`.
|
||||
SignedGreaterThan,
|
||||
/// Signed `<=`.
|
||||
SignedLessThanOrEqual,
|
||||
/// Unsigned `<`.
|
||||
UnsignedLessThan,
|
||||
/// Unsigned `>=`.
|
||||
UnsignedGreaterThanOrEqual,
|
||||
/// Unsigned `>`.
|
||||
UnsignedGreaterThan,
|
||||
/// Unsigned `<=`.
|
||||
UnsignedLessThanOrEqual,
|
||||
}
|
||||
|
||||
impl CondCode for IntCC {
|
||||
fn inverse(self) -> Self {
|
||||
use self::IntCC::*;
|
||||
match self {
|
||||
Equal => NotEqual,
|
||||
NotEqual => Equal,
|
||||
SignedLessThan => SignedGreaterThanOrEqual,
|
||||
SignedGreaterThanOrEqual => SignedLessThan,
|
||||
SignedGreaterThan => SignedLessThanOrEqual,
|
||||
SignedLessThanOrEqual => SignedGreaterThan,
|
||||
UnsignedLessThan => UnsignedGreaterThanOrEqual,
|
||||
UnsignedGreaterThanOrEqual => UnsignedLessThan,
|
||||
UnsignedGreaterThan => UnsignedLessThanOrEqual,
|
||||
UnsignedLessThanOrEqual => UnsignedGreaterThan,
|
||||
}
|
||||
}
|
||||
|
||||
fn reverse(self) -> Self {
|
||||
use self::IntCC::*;
|
||||
match self {
|
||||
Equal => Equal,
|
||||
NotEqual => NotEqual,
|
||||
SignedGreaterThan => SignedLessThan,
|
||||
SignedGreaterThanOrEqual => SignedLessThanOrEqual,
|
||||
SignedLessThan => SignedGreaterThan,
|
||||
SignedLessThanOrEqual => SignedGreaterThanOrEqual,
|
||||
UnsignedGreaterThan => UnsignedLessThan,
|
||||
UnsignedGreaterThanOrEqual => UnsignedLessThanOrEqual,
|
||||
UnsignedLessThan => UnsignedGreaterThan,
|
||||
UnsignedLessThanOrEqual => UnsignedGreaterThanOrEqual,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for IntCC {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
use self::IntCC::*;
|
||||
f.write_str(match *self {
|
||||
Equal => "eq",
|
||||
NotEqual => "ne",
|
||||
SignedGreaterThan => "sgt",
|
||||
SignedGreaterThanOrEqual => "sge",
|
||||
SignedLessThan => "slt",
|
||||
SignedLessThanOrEqual => "sle",
|
||||
UnsignedGreaterThan => "ugt",
|
||||
UnsignedGreaterThanOrEqual => "uge",
|
||||
UnsignedLessThan => "ult",
|
||||
UnsignedLessThanOrEqual => "ule",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for IntCC {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
use self::IntCC::*;
|
||||
match s {
|
||||
"eq" => Ok(Equal),
|
||||
"ne" => Ok(NotEqual),
|
||||
"sge" => Ok(SignedGreaterThanOrEqual),
|
||||
"sgt" => Ok(SignedGreaterThan),
|
||||
"sle" => Ok(SignedLessThanOrEqual),
|
||||
"slt" => Ok(SignedLessThan),
|
||||
"uge" => Ok(UnsignedGreaterThanOrEqual),
|
||||
"ugt" => Ok(UnsignedGreaterThan),
|
||||
"ule" => Ok(UnsignedLessThanOrEqual),
|
||||
"ult" => Ok(UnsignedLessThan),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Condition code for comparing floating point numbers.
|
||||
///
|
||||
/// This condition code is used by the `fcmp` instruction to compare floating point values. Two
|
||||
/// IEEE floating point values relate in exactly one of four ways:
|
||||
///
|
||||
/// 1. `UN` - unordered when either value is NaN.
|
||||
/// 2. `EQ` - equal numerical value.
|
||||
/// 3. `LT` - `x` is less than `y`.
|
||||
/// 4. `GT` - `x` is greater than `y`.
|
||||
///
|
||||
/// Note that `0.0` and `-0.0` relate as `EQ` because they both represent the number 0.
|
||||
///
|
||||
/// The condition codes described here are used to produce a single boolean value from the
|
||||
/// comparison. The 14 condition codes here cover every possible combination of the relation above
|
||||
/// except the impossible `!UN & !EQ & !LT & !GT` and the always true `UN | EQ | LT | GT`.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum FloatCC {
|
||||
/// EQ | LT | GT
|
||||
Ordered,
|
||||
/// UN
|
||||
Unordered,
|
||||
|
||||
/// EQ
|
||||
Equal,
|
||||
/// The C '!=' operator is the inverse of '==': `NotEqual`.
|
||||
/// UN | LT | GT
|
||||
NotEqual,
|
||||
/// LT | GT
|
||||
OrderedNotEqual,
|
||||
/// UN | EQ
|
||||
UnorderedOrEqual,
|
||||
|
||||
/// LT
|
||||
LessThan,
|
||||
/// LT | EQ
|
||||
LessThanOrEqual,
|
||||
/// GT
|
||||
GreaterThan,
|
||||
/// GT | EQ
|
||||
GreaterThanOrEqual,
|
||||
|
||||
/// UN | LT
|
||||
UnorderedOrLessThan,
|
||||
/// UN | LT | EQ
|
||||
UnorderedOrLessThanOrEqual,
|
||||
/// UN | GT
|
||||
UnorderedOrGreaterThan,
|
||||
/// UN | GT | EQ
|
||||
UnorderedOrGreaterThanOrEqual,
|
||||
}
|
||||
|
||||
impl CondCode for FloatCC {
|
||||
fn inverse(self) -> Self {
|
||||
use self::FloatCC::*;
|
||||
match self {
|
||||
Ordered => Unordered,
|
||||
Unordered => Ordered,
|
||||
Equal => NotEqual,
|
||||
NotEqual => Equal,
|
||||
OrderedNotEqual => UnorderedOrEqual,
|
||||
UnorderedOrEqual => OrderedNotEqual,
|
||||
LessThan => UnorderedOrGreaterThanOrEqual,
|
||||
LessThanOrEqual => UnorderedOrGreaterThan,
|
||||
GreaterThan => UnorderedOrLessThanOrEqual,
|
||||
GreaterThanOrEqual => UnorderedOrLessThan,
|
||||
UnorderedOrLessThan => GreaterThanOrEqual,
|
||||
UnorderedOrLessThanOrEqual => GreaterThan,
|
||||
UnorderedOrGreaterThan => LessThanOrEqual,
|
||||
UnorderedOrGreaterThanOrEqual => LessThan,
|
||||
}
|
||||
}
|
||||
fn reverse(self) -> Self {
|
||||
use self::FloatCC::*;
|
||||
match self {
|
||||
Ordered => Ordered,
|
||||
Unordered => Unordered,
|
||||
Equal => Equal,
|
||||
NotEqual => NotEqual,
|
||||
OrderedNotEqual => OrderedNotEqual,
|
||||
UnorderedOrEqual => UnorderedOrEqual,
|
||||
LessThan => GreaterThan,
|
||||
LessThanOrEqual => GreaterThanOrEqual,
|
||||
GreaterThan => LessThan,
|
||||
GreaterThanOrEqual => LessThanOrEqual,
|
||||
UnorderedOrLessThan => UnorderedOrGreaterThan,
|
||||
UnorderedOrLessThanOrEqual => UnorderedOrGreaterThanOrEqual,
|
||||
UnorderedOrGreaterThan => UnorderedOrLessThan,
|
||||
UnorderedOrGreaterThanOrEqual => UnorderedOrLessThanOrEqual,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FloatCC {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
use self::FloatCC::*;
|
||||
f.write_str(match *self {
|
||||
Ordered => "ord",
|
||||
Unordered => "uno",
|
||||
Equal => "eq",
|
||||
NotEqual => "ne",
|
||||
OrderedNotEqual => "one",
|
||||
UnorderedOrEqual => "ueq",
|
||||
LessThan => "lt",
|
||||
LessThanOrEqual => "le",
|
||||
GreaterThan => "gt",
|
||||
GreaterThanOrEqual => "ge",
|
||||
UnorderedOrLessThan => "ult",
|
||||
UnorderedOrLessThanOrEqual => "ule",
|
||||
UnorderedOrGreaterThan => "ugt",
|
||||
UnorderedOrGreaterThanOrEqual => "uge",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for FloatCC {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
use self::FloatCC::*;
|
||||
match s {
|
||||
"ord" => Ok(Ordered),
|
||||
"uno" => Ok(Unordered),
|
||||
"eq" => Ok(Equal),
|
||||
"ne" => Ok(NotEqual),
|
||||
"one" => Ok(OrderedNotEqual),
|
||||
"ueq" => Ok(UnorderedOrEqual),
|
||||
"lt" => Ok(LessThan),
|
||||
"le" => Ok(LessThanOrEqual),
|
||||
"gt" => Ok(GreaterThan),
|
||||
"ge" => Ok(GreaterThanOrEqual),
|
||||
"ult" => Ok(UnorderedOrLessThan),
|
||||
"ule" => Ok(UnorderedOrLessThanOrEqual),
|
||||
"ugt" => Ok(UnorderedOrGreaterThan),
|
||||
"uge" => Ok(UnorderedOrGreaterThanOrEqual),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
static INT_ALL: [IntCC; 10] = [IntCC::Equal,
|
||||
IntCC::NotEqual,
|
||||
IntCC::SignedLessThan,
|
||||
IntCC::SignedGreaterThanOrEqual,
|
||||
IntCC::SignedGreaterThan,
|
||||
IntCC::SignedLessThanOrEqual,
|
||||
IntCC::UnsignedLessThan,
|
||||
IntCC::UnsignedGreaterThanOrEqual,
|
||||
IntCC::UnsignedGreaterThan,
|
||||
IntCC::UnsignedLessThanOrEqual];
|
||||
|
||||
#[test]
|
||||
fn int_inverse() {
|
||||
for r in &INT_ALL {
|
||||
let cc = *r;
|
||||
let inv = cc.inverse();
|
||||
assert!(cc != inv);
|
||||
assert_eq!(inv.inverse(), cc);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn int_reverse() {
|
||||
for r in &INT_ALL {
|
||||
let cc = *r;
|
||||
let rev = cc.reverse();
|
||||
assert_eq!(rev.reverse(), cc);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn int_display() {
|
||||
for r in &INT_ALL {
|
||||
let cc = *r;
|
||||
assert_eq!(cc.to_string().parse(), Ok(cc));
|
||||
}
|
||||
}
|
||||
|
||||
static FLOAT_ALL: [FloatCC; 14] = [FloatCC::Ordered,
|
||||
FloatCC::Unordered,
|
||||
FloatCC::Equal,
|
||||
FloatCC::NotEqual,
|
||||
FloatCC::OrderedNotEqual,
|
||||
FloatCC::UnorderedOrEqual,
|
||||
FloatCC::LessThan,
|
||||
FloatCC::LessThanOrEqual,
|
||||
FloatCC::GreaterThan,
|
||||
FloatCC::GreaterThanOrEqual,
|
||||
FloatCC::UnorderedOrLessThan,
|
||||
FloatCC::UnorderedOrLessThanOrEqual,
|
||||
FloatCC::UnorderedOrGreaterThan,
|
||||
FloatCC::UnorderedOrGreaterThanOrEqual];
|
||||
|
||||
#[test]
|
||||
fn float_inverse() {
|
||||
for r in &FLOAT_ALL {
|
||||
let cc = *r;
|
||||
let inv = cc.inverse();
|
||||
assert!(cc != inv);
|
||||
assert_eq!(inv.inverse(), cc);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_reverse() {
|
||||
for r in &FLOAT_ALL {
|
||||
let cc = *r;
|
||||
let rev = cc.reverse();
|
||||
assert_eq!(rev.reverse(), cc);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_display() {
|
||||
for r in &FLOAT_ALL {
|
||||
let cc = *r;
|
||||
assert_eq!(cc.to_string().parse(), Ok(cc));
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,183 +0,0 @@
|
||||
//! IL entity references.
|
||||
//!
|
||||
//! Instructions in Cretonne IL need to reference other entities in the function. This can be other
|
||||
//! parts of the function like extended basic blocks or stack slots, or it can be external entities
|
||||
//! that are declared in the function preamble in the text format.
|
||||
//!
|
||||
//! These entity references in instruction operands are not implemented as Rust references both
|
||||
//! because Rust's ownership and mutability rules make it difficult, and because 64-bit pointers
|
||||
//! take up a lot of space, and we want a compact in-memory representation. Instead, entity
|
||||
//! references are structs wrapping a `u32` index into a table in the `Function` main data
|
||||
//! structure. There is a separate index type for each entity type, so we don't lose type safety.
|
||||
//!
|
||||
//! The `entities` module defines public types for the entity references along with constants
|
||||
//! representing an invalid reference. We prefer to use `Option<EntityRef>` whenever possible, but
|
||||
//! unfortunately that type is twice as large as the 32-bit index type on its own. Thus, compact
|
||||
//! data structures use the `PackedOption<EntityRef>` representation, while function arguments and
|
||||
//! return values prefer the more Rust-like `Option<EntityRef>` variant.
|
||||
//!
|
||||
//! The entity references all implement the `Display` trait in a way that matches the textual IL
|
||||
//! format.
|
||||
|
||||
use std::fmt;
|
||||
use std::u32;
|
||||
|
||||
/// An opaque reference to an extended basic block in a function.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
|
||||
pub struct Ebb(u32);
|
||||
entity_impl!(Ebb, "ebb");
|
||||
|
||||
impl Ebb {
|
||||
/// Create a new EBB reference from its number. This corresponds to the `ebbNN` representation.
|
||||
///
|
||||
/// This method is for use by the parser.
|
||||
pub fn with_number(n: u32) -> Option<Ebb> {
|
||||
if n < u32::MAX { Some(Ebb(n)) } else { None }
|
||||
}
|
||||
}
|
||||
|
||||
/// An opaque reference to an SSA value.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
|
||||
pub struct Value(u32);
|
||||
entity_impl!(Value, "v");
|
||||
|
||||
impl Value {
|
||||
/// Create a value from its number representation.
|
||||
/// This is the number in the `vNN` notation.
|
||||
///
|
||||
/// This method is for use by the parser.
|
||||
pub fn with_number(n: u32) -> Option<Value> {
|
||||
if n < u32::MAX / 2 {
|
||||
Some(Value(n))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An opaque reference to an instruction in a function.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
|
||||
pub struct Inst(u32);
|
||||
entity_impl!(Inst, "inst");
|
||||
|
||||
/// An opaque reference to a stack slot.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct StackSlot(u32);
|
||||
entity_impl!(StackSlot, "ss");
|
||||
|
||||
/// An opaque reference to a jump table.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct JumpTable(u32);
|
||||
entity_impl!(JumpTable, "jt");
|
||||
|
||||
/// A reference to an external function.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct FuncRef(u32);
|
||||
entity_impl!(FuncRef, "fn");
|
||||
|
||||
/// A reference to a function signature.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct SigRef(u32);
|
||||
entity_impl!(SigRef, "sig");
|
||||
|
||||
/// A reference to any of the entities defined in this module.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub enum AnyEntity {
|
||||
/// The whole function.
|
||||
Function,
|
||||
/// An extended basic block.
|
||||
Ebb(Ebb),
|
||||
/// An instruction.
|
||||
Inst(Inst),
|
||||
/// An SSA value.
|
||||
Value(Value),
|
||||
/// A stack slot.
|
||||
StackSlot(StackSlot),
|
||||
/// A jump table.
|
||||
JumpTable(JumpTable),
|
||||
/// An external function.
|
||||
FuncRef(FuncRef),
|
||||
/// A function call signature.
|
||||
SigRef(SigRef),
|
||||
}
|
||||
|
||||
impl fmt::Display for AnyEntity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
AnyEntity::Function => write!(f, "function"),
|
||||
AnyEntity::Ebb(r) => r.fmt(f),
|
||||
AnyEntity::Inst(r) => r.fmt(f),
|
||||
AnyEntity::Value(r) => r.fmt(f),
|
||||
AnyEntity::StackSlot(r) => r.fmt(f),
|
||||
AnyEntity::JumpTable(r) => r.fmt(f),
|
||||
AnyEntity::FuncRef(r) => r.fmt(f),
|
||||
AnyEntity::SigRef(r) => r.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Ebb> for AnyEntity {
|
||||
fn from(r: Ebb) -> AnyEntity {
|
||||
AnyEntity::Ebb(r)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Inst> for AnyEntity {
|
||||
fn from(r: Inst) -> AnyEntity {
|
||||
AnyEntity::Inst(r)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Value> for AnyEntity {
|
||||
fn from(r: Value) -> AnyEntity {
|
||||
AnyEntity::Value(r)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StackSlot> for AnyEntity {
|
||||
fn from(r: StackSlot) -> AnyEntity {
|
||||
AnyEntity::StackSlot(r)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<JumpTable> for AnyEntity {
|
||||
fn from(r: JumpTable) -> AnyEntity {
|
||||
AnyEntity::JumpTable(r)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FuncRef> for AnyEntity {
|
||||
fn from(r: FuncRef) -> AnyEntity {
|
||||
AnyEntity::FuncRef(r)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SigRef> for AnyEntity {
|
||||
fn from(r: SigRef) -> AnyEntity {
|
||||
AnyEntity::SigRef(r)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::u32;
|
||||
|
||||
#[test]
|
||||
fn value_with_number() {
|
||||
assert_eq!(Value::with_number(0).unwrap().to_string(), "v0");
|
||||
assert_eq!(Value::with_number(1).unwrap().to_string(), "v1");
|
||||
|
||||
assert_eq!(Value::with_number(u32::MAX / 2), None);
|
||||
assert!(Value::with_number(u32::MAX / 2 - 1).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memory() {
|
||||
use std::mem;
|
||||
use packed_option::PackedOption;
|
||||
// This is the whole point of `PackedOption`.
|
||||
assert_eq!(mem::size_of::<Value>(),
|
||||
mem::size_of::<PackedOption<Value>>());
|
||||
}
|
||||
}
|
||||
@@ -1,389 +0,0 @@
|
||||
//! External function calls.
|
||||
//!
|
||||
//! To a Cretonne function, all functions are "external". Directly called functions must be
|
||||
//! declared in the preamble, and all function calls must have a signature.
|
||||
//!
|
||||
//! This module declares the data types used to represent external functions and call signatures.
|
||||
|
||||
use ir::{Type, FunctionName, SigRef, ArgumentLoc};
|
||||
use isa::{RegInfo, RegUnit};
|
||||
use std::cmp;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Function signature.
|
||||
///
|
||||
/// The function signature describes the types of arguments and return values along with other
|
||||
/// details that are needed to call a function correctly.
|
||||
///
|
||||
/// A signature can optionally include ISA-specific ABI information which specifies exactly how
|
||||
/// arguments and return values are passed.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Signature {
|
||||
/// Types of the arguments passed to the function.
|
||||
pub argument_types: Vec<ArgumentType>,
|
||||
/// Types returned from the function.
|
||||
pub return_types: Vec<ArgumentType>,
|
||||
|
||||
/// Calling convention.
|
||||
pub call_conv: CallConv,
|
||||
|
||||
/// When the signature has been legalized to a specific ISA, this holds the size of the
|
||||
/// argument array on the stack. Before legalization, this is `None`.
|
||||
///
|
||||
/// This can be computed from the legalized `argument_types` array as the maximum (offset plus
|
||||
/// byte size) of the `ArgumentLoc::Stack(offset)` argument.
|
||||
pub argument_bytes: Option<u32>,
|
||||
}
|
||||
|
||||
impl Signature {
|
||||
/// Create a new blank signature.
|
||||
pub fn new(call_conv: CallConv) -> Signature {
|
||||
Signature {
|
||||
argument_types: Vec::new(),
|
||||
return_types: Vec::new(),
|
||||
call_conv,
|
||||
argument_bytes: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the size of the stack arguments and mark signature as legalized.
|
||||
///
|
||||
/// Even if there are no stack arguments, this will set `argument_types` to `Some(0)` instead
|
||||
/// of `None`. This indicates that the signature has been legalized.
|
||||
pub fn compute_argument_bytes(&mut self) {
|
||||
let bytes = self.argument_types
|
||||
.iter()
|
||||
.filter_map(|arg| match arg.location {
|
||||
ArgumentLoc::Stack(offset) if offset >= 0 => {
|
||||
Some(offset as u32 + arg.value_type.bytes())
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.fold(0, cmp::max);
|
||||
self.argument_bytes = Some(bytes);
|
||||
}
|
||||
|
||||
/// Return an object that can display `self` with correct register names.
|
||||
pub fn display<'a, R: Into<Option<&'a RegInfo>>>(&'a self, regs: R) -> DisplaySignature<'a> {
|
||||
DisplaySignature(self, regs.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper type capable of displaying a `Signature` with correct register names.
|
||||
pub struct DisplaySignature<'a>(&'a Signature, Option<&'a RegInfo>);
|
||||
|
||||
fn write_list(f: &mut fmt::Formatter,
|
||||
args: &[ArgumentType],
|
||||
regs: Option<&RegInfo>)
|
||||
-> fmt::Result {
|
||||
match args.split_first() {
|
||||
None => {}
|
||||
Some((first, rest)) => {
|
||||
write!(f, "{}", first.display(regs))?;
|
||||
for arg in rest {
|
||||
write!(f, ", {}", arg.display(regs))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for DisplaySignature<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "(")?;
|
||||
write_list(f, &self.0.argument_types, self.1)?;
|
||||
write!(f, ")")?;
|
||||
if !self.0.return_types.is_empty() {
|
||||
write!(f, " -> ")?;
|
||||
write_list(f, &self.0.return_types, self.1)?;
|
||||
}
|
||||
write!(f, " {}", self.0.call_conv)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Signature {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.display(None).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Function argument or return value type.
|
||||
///
|
||||
/// This describes the value type being passed to or from a function along with flags that affect
|
||||
/// how the argument is passed.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct ArgumentType {
|
||||
/// Type of the argument value.
|
||||
pub value_type: Type,
|
||||
/// Special purpose of argument, or `Normal`.
|
||||
pub purpose: ArgumentPurpose,
|
||||
/// Method for extending argument to a full register.
|
||||
pub extension: ArgumentExtension,
|
||||
|
||||
/// ABI-specific location of this argument, or `Unassigned` for arguments that have not yet
|
||||
/// been legalized.
|
||||
pub location: ArgumentLoc,
|
||||
}
|
||||
|
||||
impl ArgumentType {
|
||||
/// Create an argument type with default flags.
|
||||
pub fn new(vt: Type) -> ArgumentType {
|
||||
ArgumentType {
|
||||
value_type: vt,
|
||||
extension: ArgumentExtension::None,
|
||||
purpose: ArgumentPurpose::Normal,
|
||||
location: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an argument type for a special-purpose register.
|
||||
pub fn special_reg(vt: Type, purpose: ArgumentPurpose, regunit: RegUnit) -> ArgumentType {
|
||||
ArgumentType {
|
||||
value_type: vt,
|
||||
extension: ArgumentExtension::None,
|
||||
purpose,
|
||||
location: ArgumentLoc::Reg(regunit),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return an object that can display `self` with correct register names.
|
||||
pub fn display<'a, R: Into<Option<&'a RegInfo>>>(&'a self, regs: R) -> DisplayArgumentType<'a> {
|
||||
DisplayArgumentType(self, regs.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper type capable of displaying an `ArgumentType` with correct register names.
|
||||
pub struct DisplayArgumentType<'a>(&'a ArgumentType, Option<&'a RegInfo>);
|
||||
|
||||
impl<'a> fmt::Display for DisplayArgumentType<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0.value_type)?;
|
||||
match self.0.extension {
|
||||
ArgumentExtension::None => {}
|
||||
ArgumentExtension::Uext => write!(f, " uext")?,
|
||||
ArgumentExtension::Sext => write!(f, " sext")?,
|
||||
}
|
||||
if self.0.purpose != ArgumentPurpose::Normal {
|
||||
write!(f, " {}", self.0.purpose)?;
|
||||
}
|
||||
|
||||
if self.0.location.is_assigned() {
|
||||
write!(f, " [{}]", self.0.location.display(self.1))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ArgumentType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.display(None).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Function argument extension options.
|
||||
///
|
||||
/// On some architectures, small integer function arguments are extended to the width of a
|
||||
/// general-purpose register.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum ArgumentExtension {
|
||||
/// No extension, high bits are indeterminate.
|
||||
None,
|
||||
/// Unsigned extension: high bits in register are 0.
|
||||
Uext,
|
||||
/// Signed extension: high bits in register replicate sign bit.
|
||||
Sext,
|
||||
}
|
||||
|
||||
/// The special purpose of a function argument.
|
||||
///
|
||||
/// Function arguments and return values are used to pass user program values between functions,
|
||||
/// but they are also used to represent special registers with significance to the ABI such as
|
||||
/// frame pointers and callee-saved registers.
|
||||
///
|
||||
/// The argument purpose is used to indicate any special meaning of an argument or return value.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum ArgumentPurpose {
|
||||
/// A normal user program value passed to or from a function.
|
||||
Normal,
|
||||
|
||||
/// Struct return pointer.
|
||||
///
|
||||
/// When a function needs to return more data than will fit in registers, the caller passes a
|
||||
/// pointer to a memory location where the return value can be written. In some ABIs, this
|
||||
/// struct return pointer is passed in a specific register.
|
||||
///
|
||||
/// This argument kind can also appear as a return value for ABIs that require a function with
|
||||
/// a `StructReturn` pointer argument to also return that pointer in a register.
|
||||
StructReturn,
|
||||
|
||||
/// The link register.
|
||||
///
|
||||
/// Most RISC architectures implement calls by saving the return address in a designated
|
||||
/// register rather than pushing it on the stack. This is represented with a `Link` argument.
|
||||
///
|
||||
/// Similarly, some return instructions expect the return address in a register represented as
|
||||
/// a `Link` return value.
|
||||
Link,
|
||||
|
||||
/// The frame pointer.
|
||||
///
|
||||
/// This indicates the frame pointer register which has a special meaning in some ABIs.
|
||||
///
|
||||
/// The frame pointer appears as an argument and as a return value since it is a callee-saved
|
||||
/// register.
|
||||
FramePointer,
|
||||
|
||||
/// A callee-saved register.
|
||||
///
|
||||
/// Some calling conventions have registers that must be saved by the callee. These registers
|
||||
/// are represented as `CalleeSaved` arguments and return values.
|
||||
CalleeSaved,
|
||||
}
|
||||
|
||||
/// Text format names of the `ArgumentPurpose` variants.
|
||||
static PURPOSE_NAMES: [&str; 5] = ["normal", "sret", "link", "fp", "csr"];
|
||||
|
||||
impl fmt::Display for ArgumentPurpose {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str(PURPOSE_NAMES[*self as usize])
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ArgumentPurpose {
|
||||
type Err = ();
|
||||
fn from_str(s: &str) -> Result<ArgumentPurpose, ()> {
|
||||
match s {
|
||||
"normal" => Ok(ArgumentPurpose::Normal),
|
||||
"sret" => Ok(ArgumentPurpose::StructReturn),
|
||||
"link" => Ok(ArgumentPurpose::Link),
|
||||
"fp" => Ok(ArgumentPurpose::FramePointer),
|
||||
"csr" => Ok(ArgumentPurpose::CalleeSaved),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An external function.
|
||||
///
|
||||
/// Information about a function that can be called directly with a direct `call` instruction.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ExtFuncData {
|
||||
/// Name of the external function.
|
||||
pub name: FunctionName,
|
||||
/// Call signature of function.
|
||||
pub signature: SigRef,
|
||||
}
|
||||
|
||||
impl fmt::Display for ExtFuncData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{} {}", self.signature, self.name)
|
||||
}
|
||||
}
|
||||
|
||||
/// A Calling convention.
|
||||
///
|
||||
/// A function's calling convention determines exactly how arguments and return values are passed,
|
||||
/// and how stack frames are managed. Since all of these details depend on both the instruction set
|
||||
/// architecture and possibly the operating system, a function's calling convention is only fully
|
||||
/// determined by a `(TargetIsa, CallConv)` tuple.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum CallConv {
|
||||
/// The C calling convention.
|
||||
///
|
||||
/// This is the native calling convention that a C compiler would use on the platform.
|
||||
Native,
|
||||
|
||||
/// A JIT-compiled WebAssembly function in the SpiderMonkey VM.
|
||||
SpiderWASM,
|
||||
}
|
||||
|
||||
impl fmt::Display for CallConv {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use self::CallConv::*;
|
||||
f.write_str(match *self {
|
||||
Native => "native",
|
||||
SpiderWASM => "spiderwasm",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for CallConv {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
use self::CallConv::*;
|
||||
match s {
|
||||
"native" => Ok(Native),
|
||||
"spiderwasm" => Ok(SpiderWASM),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ir::types::{I32, F32, B8};
|
||||
|
||||
#[test]
|
||||
fn argument_type() {
|
||||
let mut t = ArgumentType::new(I32);
|
||||
assert_eq!(t.to_string(), "i32");
|
||||
t.extension = ArgumentExtension::Uext;
|
||||
assert_eq!(t.to_string(), "i32 uext");
|
||||
t.purpose = ArgumentPurpose::StructReturn;
|
||||
assert_eq!(t.to_string(), "i32 uext sret");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn argument_purpose() {
|
||||
let all_purpose = [ArgumentPurpose::Normal,
|
||||
ArgumentPurpose::StructReturn,
|
||||
ArgumentPurpose::Link,
|
||||
ArgumentPurpose::FramePointer,
|
||||
ArgumentPurpose::CalleeSaved];
|
||||
for (&e, &n) in all_purpose.iter().zip(PURPOSE_NAMES.iter()) {
|
||||
assert_eq!(e.to_string(), n);
|
||||
assert_eq!(Ok(e), n.parse());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_conv() {
|
||||
for &cc in &[CallConv::Native, CallConv::SpiderWASM] {
|
||||
assert_eq!(Ok(cc), cc.to_string().parse())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signatures() {
|
||||
let mut sig = Signature::new(CallConv::SpiderWASM);
|
||||
assert_eq!(sig.to_string(), "() spiderwasm");
|
||||
sig.argument_types.push(ArgumentType::new(I32));
|
||||
assert_eq!(sig.to_string(), "(i32) spiderwasm");
|
||||
sig.return_types.push(ArgumentType::new(F32));
|
||||
assert_eq!(sig.to_string(), "(i32) -> f32 spiderwasm");
|
||||
sig.argument_types
|
||||
.push(ArgumentType::new(I32.by(4).unwrap()));
|
||||
assert_eq!(sig.to_string(), "(i32, i32x4) -> f32 spiderwasm");
|
||||
sig.return_types.push(ArgumentType::new(B8));
|
||||
assert_eq!(sig.to_string(), "(i32, i32x4) -> f32, b8 spiderwasm");
|
||||
|
||||
// Test the offset computation algorithm.
|
||||
assert_eq!(sig.argument_bytes, None);
|
||||
sig.argument_types[1].location = ArgumentLoc::Stack(8);
|
||||
sig.compute_argument_bytes();
|
||||
// An `i32x4` at offset 8 requires a 24-byte argument array.
|
||||
assert_eq!(sig.argument_bytes, Some(24));
|
||||
// Order does not matter.
|
||||
sig.argument_types[0].location = ArgumentLoc::Stack(24);
|
||||
sig.compute_argument_bytes();
|
||||
assert_eq!(sig.argument_bytes, Some(28));
|
||||
|
||||
// Writing ABI-annotated signatures.
|
||||
assert_eq!(sig.to_string(),
|
||||
"(i32 [24], i32x4 [8]) -> f32, b8 spiderwasm");
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
//! Function names.
|
||||
//!
|
||||
//! The name of a function doesn't have any meaning to Cretonne which compiles functions
|
||||
//! independently.
|
||||
|
||||
use std::fmt::{self, Write};
|
||||
use std::ascii::AsciiExt;
|
||||
|
||||
/// The name of a function can be any sequence of bytes.
|
||||
///
|
||||
/// Function names are mostly a testing and debugging tool.
|
||||
/// In particular, `.cton` files use function names to identify functions.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
pub struct FunctionName(NameRepr);
|
||||
|
||||
impl FunctionName {
|
||||
/// Creates a new function name from a sequence of bytes.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use cretonne::ir::FunctionName;
|
||||
/// // Create `FunctionName` from a string.
|
||||
/// let name = FunctionName::new("hello");
|
||||
/// assert_eq!(name.to_string(), "%hello");
|
||||
///
|
||||
/// // Create `FunctionName` from a sequence of bytes.
|
||||
/// let bytes: &[u8] = &[10, 9, 8];
|
||||
/// let name = FunctionName::new(bytes);
|
||||
/// assert_eq!(name.to_string(), "#0a0908");
|
||||
/// ```
|
||||
pub fn new<T>(v: T) -> FunctionName
|
||||
where T: Into<Vec<u8>>
|
||||
{
|
||||
let vec = v.into();
|
||||
if vec.len() <= NAME_LENGTH_THRESHOLD {
|
||||
let mut bytes = [0u8; NAME_LENGTH_THRESHOLD];
|
||||
for (i, &byte) in vec.iter().enumerate() {
|
||||
bytes[i] = byte;
|
||||
}
|
||||
FunctionName(NameRepr::Short {
|
||||
length: vec.len() as u8,
|
||||
bytes: bytes,
|
||||
})
|
||||
} else {
|
||||
FunctionName(NameRepr::Long(vec))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to interpret bytes as ASCII alphanumerical characters and `_`.
|
||||
fn try_as_name(bytes: &[u8]) -> Option<String> {
|
||||
let mut name = String::with_capacity(bytes.len());
|
||||
for c in bytes.iter().map(|&b| b as char) {
|
||||
if c.is_ascii() && c.is_alphanumeric() || c == '_' {
|
||||
name.push(c);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(name)
|
||||
}
|
||||
|
||||
const NAME_LENGTH_THRESHOLD: usize = 22;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum NameRepr {
|
||||
Short {
|
||||
length: u8,
|
||||
bytes: [u8; NAME_LENGTH_THRESHOLD],
|
||||
},
|
||||
Long(Vec<u8>),
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for NameRepr {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
match *self {
|
||||
NameRepr::Short { length, ref bytes } => &bytes[0..length as usize],
|
||||
NameRepr::Long(ref vec) => vec.as_ref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for NameRepr {
|
||||
fn default() -> Self {
|
||||
NameRepr::Short {
|
||||
length: 0,
|
||||
bytes: [0; NAME_LENGTH_THRESHOLD],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for FunctionName {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if let Some(name) = try_as_name(self.0.as_ref()) {
|
||||
write!(f, "%{}", name)
|
||||
} else {
|
||||
f.write_char('#')?;
|
||||
for byte in self.0.as_ref() {
|
||||
write!(f, "{:02x}", byte)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::FunctionName;
|
||||
|
||||
#[test]
|
||||
fn displaying() {
|
||||
assert_eq!(FunctionName::new("").to_string(), "%");
|
||||
assert_eq!(FunctionName::new("x").to_string(), "%x");
|
||||
assert_eq!(FunctionName::new("x_1").to_string(), "%x_1");
|
||||
assert_eq!(FunctionName::new(" ").to_string(), "#20");
|
||||
assert_eq!(FunctionName::new("кретон").to_string(),
|
||||
"#d0bad180d0b5d182d0bed0bd");
|
||||
assert_eq!(FunctionName::new("印花棉布").to_string(),
|
||||
"#e58db0e88ab1e6a389e5b883");
|
||||
assert_eq!(FunctionName::new(vec![0, 1, 2, 3, 4, 5]).to_string(),
|
||||
"#000102030405");
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
//! Intermediate representation of a function.
|
||||
//!
|
||||
//! The `Function` struct defined in this module owns all of its extended basic blocks and
|
||||
//! instructions.
|
||||
|
||||
use entity_map::{EntityMap, PrimaryEntityData};
|
||||
use ir::{FunctionName, CallConv, Signature, JumpTableData, DataFlowGraph, Layout};
|
||||
use ir::{JumpTables, InstEncodings, ValueLocations, StackSlots, EbbOffsets};
|
||||
use isa::TargetIsa;
|
||||
use std::fmt;
|
||||
use write::write_function;
|
||||
|
||||
/// A function.
|
||||
///
|
||||
/// Functions can be cloned, but it is not a very fast operation.
|
||||
/// The clone will have all the same entity numbers as the original.
|
||||
#[derive(Clone)]
|
||||
pub struct Function {
|
||||
/// Name of this function. Mostly used by `.cton` files.
|
||||
pub name: FunctionName,
|
||||
|
||||
/// Signature of this function.
|
||||
pub signature: Signature,
|
||||
|
||||
/// Stack slots allocated in this function.
|
||||
pub stack_slots: StackSlots,
|
||||
|
||||
/// Jump tables used in this function.
|
||||
pub jump_tables: JumpTables,
|
||||
|
||||
/// Data flow graph containing the primary definition of all instructions, EBBs and values.
|
||||
pub dfg: DataFlowGraph,
|
||||
|
||||
/// Layout of EBBs and instructions in the function body.
|
||||
pub layout: Layout,
|
||||
|
||||
/// Encoding recipe and bits for the legal instructions.
|
||||
/// Illegal instructions have the `Encoding::default()` value.
|
||||
pub encodings: InstEncodings,
|
||||
|
||||
/// Location assigned to every value.
|
||||
pub locations: ValueLocations,
|
||||
|
||||
/// Code offsets of the EBB headers.
|
||||
///
|
||||
/// This information is only transiently available after the `binemit::relax_branches` function
|
||||
/// computes it, and it can easily be recomputed by calling that function. It is not included
|
||||
/// in the textual IL format.
|
||||
pub offsets: EbbOffsets,
|
||||
}
|
||||
|
||||
impl PrimaryEntityData for JumpTableData {}
|
||||
|
||||
impl Function {
|
||||
/// Create a function with the given name and signature.
|
||||
pub fn with_name_signature(name: FunctionName, sig: Signature) -> Function {
|
||||
Function {
|
||||
name,
|
||||
signature: sig,
|
||||
stack_slots: StackSlots::new(),
|
||||
jump_tables: EntityMap::new(),
|
||||
dfg: DataFlowGraph::new(),
|
||||
layout: Layout::new(),
|
||||
encodings: EntityMap::new(),
|
||||
locations: EntityMap::new(),
|
||||
offsets: EntityMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new empty, anonymous function with a native calling convention.
|
||||
pub fn new() -> Function {
|
||||
Self::with_name_signature(FunctionName::default(), Signature::new(CallConv::Native))
|
||||
}
|
||||
|
||||
/// Return an object that can display this function with correct ISA-specific annotations.
|
||||
pub fn display<'a, I: Into<Option<&'a TargetIsa>>>(&'a self, isa: I) -> DisplayFunction<'a> {
|
||||
DisplayFunction(self, isa.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper type capable of displaying a `Function` with correct ISA annotations.
|
||||
pub struct DisplayFunction<'a>(&'a Function, Option<&'a TargetIsa>);
|
||||
|
||||
impl<'a> fmt::Display for DisplayFunction<'a> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
write_function(fmt, self.0, self.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Function {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
write_function(fmt, self, None)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Function {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
write_function(fmt, self, None)
|
||||
}
|
||||
}
|
||||
@@ -1,932 +0,0 @@
|
||||
|
||||
//! Immediate operands for Cretonne instructions
|
||||
//!
|
||||
//! This module defines the types of immediate operands that can appear on Cretonne instructions.
|
||||
//! Each type here should have a corresponding definition in the `cretonne.immediates` Python
|
||||
//! module in the meta language.
|
||||
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::{i32, u32};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[cfg(test)]
|
||||
use std::mem;
|
||||
|
||||
/// 64-bit immediate integer operand.
|
||||
///
|
||||
/// An `Imm64` operand can also be used to represent immediate values of smaller integer types by
|
||||
/// sign-extending to `i64`.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub struct Imm64(i64);
|
||||
|
||||
impl Imm64 {
|
||||
/// Create a new `Imm64` representing the signed number `x`.
|
||||
pub fn new(x: i64) -> Imm64 {
|
||||
Imm64(x)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<i64> for Imm64 {
|
||||
fn into(self) -> i64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for Imm64 {
|
||||
fn from(x: i64) -> Self {
|
||||
Imm64(x)
|
||||
}
|
||||
}
|
||||
|
||||
// Hexadecimal with a multiple of 4 digits and group separators:
|
||||
//
|
||||
// 0xfff0
|
||||
// 0x0001_ffff
|
||||
// 0xffff_ffff_fff8_4400
|
||||
//
|
||||
fn write_hex(x: i64, f: &mut Formatter) -> fmt::Result {
|
||||
let mut pos = (64 - x.leading_zeros() - 1) & 0xf0;
|
||||
write!(f, "0x{:04x}", (x >> pos) & 0xffff)?;
|
||||
while pos > 0 {
|
||||
pos -= 16;
|
||||
write!(f, "_{:04x}", (x >> pos) & 0xffff)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Display for Imm64 {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
let x = self.0;
|
||||
if -10_000 < x && x < 10_000 {
|
||||
// Use decimal for small numbers.
|
||||
write!(f, "{}", x)
|
||||
} else {
|
||||
write_hex(x, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a 64-bit number.
|
||||
fn parse_i64(s: &str) -> Result<i64, &'static str> {
|
||||
let mut value: u64 = 0;
|
||||
let mut digits = 0;
|
||||
let negative = s.starts_with('-');
|
||||
let s2 = if negative || s.starts_with('+') {
|
||||
&s[1..]
|
||||
} else {
|
||||
s
|
||||
};
|
||||
|
||||
if s2.starts_with("0x") {
|
||||
// Hexadecimal.
|
||||
for ch in s2[2..].chars() {
|
||||
match ch.to_digit(16) {
|
||||
Some(digit) => {
|
||||
digits += 1;
|
||||
if digits > 16 {
|
||||
return Err("Too many hexadecimal digits");
|
||||
}
|
||||
// This can't overflow given the digit limit.
|
||||
value = (value << 4) | digit as u64;
|
||||
}
|
||||
None => {
|
||||
// Allow embedded underscores, but fail on anything else.
|
||||
if ch != '_' {
|
||||
return Err("Invalid character in hexadecimal number");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Decimal number, possibly negative.
|
||||
for ch in s2.chars() {
|
||||
match ch.to_digit(16) {
|
||||
Some(digit) => {
|
||||
digits += 1;
|
||||
match value.checked_mul(10) {
|
||||
None => return Err("Too large decimal number"),
|
||||
Some(v) => value = v,
|
||||
}
|
||||
match value.checked_add(digit as u64) {
|
||||
None => return Err("Too large decimal number"),
|
||||
Some(v) => value = v,
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// Allow embedded underscores, but fail on anything else.
|
||||
if ch != '_' {
|
||||
return Err("Invalid character in decimal number");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if digits == 0 {
|
||||
return Err("No digits in number");
|
||||
}
|
||||
|
||||
// We support the range-and-a-half from -2^63 .. 2^64-1.
|
||||
if negative {
|
||||
value = value.wrapping_neg();
|
||||
// Don't allow large negative values to wrap around and become positive.
|
||||
if value as i64 > 0 {
|
||||
return Err("Negative number too small");
|
||||
}
|
||||
}
|
||||
Ok(value as i64)
|
||||
}
|
||||
|
||||
impl FromStr for Imm64 {
|
||||
type Err = &'static str;
|
||||
|
||||
// Parse a decimal or hexadecimal `Imm64`, formatted as above.
|
||||
fn from_str(s: &str) -> Result<Imm64, &'static str> {
|
||||
parse_i64(s).map(Imm64::new)
|
||||
}
|
||||
}
|
||||
|
||||
/// 8-bit unsigned integer immediate operand.
|
||||
///
|
||||
/// This is used to indicate lane indexes typically.
|
||||
pub type Uimm8 = u8;
|
||||
|
||||
/// 32-bit signed immediate offset.
|
||||
///
|
||||
/// This is used to encode an immediate offset for load/store instructions. All supported ISAs have
|
||||
/// a maximum load/store offset that fits in an `i32`.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub struct Offset32(i32);
|
||||
|
||||
impl Offset32 {
|
||||
/// Create a new `Offset32` representing the signed number `x`.
|
||||
pub fn new(x: i32) -> Offset32 {
|
||||
Offset32(x)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<i32> for Offset32 {
|
||||
fn into(self) -> i32 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<i64> for Offset32 {
|
||||
fn into(self) -> i64 {
|
||||
self.0 as i64
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for Offset32 {
|
||||
fn from(x: i32) -> Self {
|
||||
Offset32(x)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Offset32 {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
// 0 displays as an empty offset.
|
||||
if self.0 == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Always include a sign.
|
||||
write!(f, "{}", if self.0 < 0 { '-' } else { '+' })?;
|
||||
|
||||
let val = (self.0 as i64).abs();
|
||||
if val < 10_000 {
|
||||
write!(f, "{}", val)
|
||||
} else {
|
||||
write_hex(val, f)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Offset32 {
|
||||
type Err = &'static str;
|
||||
|
||||
// Parse a decimal or hexadecimal `Offset32`, formatted as above.
|
||||
fn from_str(s: &str) -> Result<Offset32, &'static str> {
|
||||
if !(s.starts_with('-') || s.starts_with('+')) {
|
||||
return Err("Offset must begin with sign");
|
||||
}
|
||||
parse_i64(s).and_then(|x| if i32::MIN as i64 <= x && x <= i32::MAX as i64 {
|
||||
Ok(Offset32::new(x as i32))
|
||||
} else {
|
||||
Err("Offset out of range")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// 32-bit unsigned immediate offset.
|
||||
///
|
||||
/// This is used to encode an immediate offset for WebAssembly heap_load/heap_store instructions.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub struct Uoffset32(u32);
|
||||
|
||||
impl Uoffset32 {
|
||||
/// Create a new `Uoffset32` representing the number `x`.
|
||||
pub fn new(x: u32) -> Uoffset32 {
|
||||
Uoffset32(x)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u32> for Uoffset32 {
|
||||
fn into(self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<i64> for Uoffset32 {
|
||||
fn into(self) -> i64 {
|
||||
self.0 as i64
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for Uoffset32 {
|
||||
fn from(x: u32) -> Self {
|
||||
Uoffset32(x)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Uoffset32 {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
// 0 displays as an empty offset.
|
||||
if self.0 == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Always include a sign.
|
||||
if self.0 < 10_000 {
|
||||
write!(f, "+{}", self.0)
|
||||
} else {
|
||||
write!(f, "+")?;
|
||||
write_hex(self.0 as i64, f)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Uoffset32 {
|
||||
type Err = &'static str;
|
||||
|
||||
// Parse a decimal or hexadecimal `Uoffset32`, formatted as above.
|
||||
fn from_str(s: &str) -> Result<Uoffset32, &'static str> {
|
||||
if !s.starts_with('+') {
|
||||
return Err("Unsigned offset must begin with '+' sign");
|
||||
}
|
||||
parse_i64(s).and_then(|x| if 0 <= x && x <= u32::MAX as i64 {
|
||||
Ok(Uoffset32::new(x as u32))
|
||||
} else {
|
||||
Err("Offset out of range")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// An IEEE binary32 immediate floating point value, represented as a u32
|
||||
/// containing the bitpattern.
|
||||
///
|
||||
/// All bit patterns are allowed.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct Ieee32(u32);
|
||||
|
||||
/// An IEEE binary64 immediate floating point value, represented as a u64
|
||||
/// containing the bitpattern.
|
||||
///
|
||||
/// All bit patterns are allowed.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct Ieee64(u64);
|
||||
|
||||
// Format a floating point number in a way that is reasonably human-readable, and that can be
|
||||
// converted back to binary without any rounding issues. The hexadecimal formatting of normal and
|
||||
// subnormal numbers is compatible with C99 and the `printf "%a"` format specifier. The NaN and Inf
|
||||
// formats are not supported by C99.
|
||||
//
|
||||
// The encoding parameters are:
|
||||
//
|
||||
// w - exponent field width in bits
|
||||
// t - trailing significand field width in bits
|
||||
//
|
||||
fn format_float(bits: u64, w: u8, t: u8, f: &mut Formatter) -> fmt::Result {
|
||||
debug_assert!(w > 0 && w <= 16, "Invalid exponent range");
|
||||
debug_assert!(1 + w + t <= 64, "Too large IEEE format for u64");
|
||||
debug_assert!((t + w + 1).is_power_of_two(), "Unexpected IEEE format size");
|
||||
|
||||
let max_e_bits = (1u64 << w) - 1;
|
||||
let t_bits = bits & ((1u64 << t) - 1); // Trailing significand.
|
||||
let e_bits = (bits >> t) & max_e_bits; // Biased exponent.
|
||||
let sign_bit = (bits >> (w + t)) & 1;
|
||||
|
||||
let bias: i32 = (1 << (w - 1)) - 1;
|
||||
let e = e_bits as i32 - bias; // Unbiased exponent.
|
||||
let emin = 1 - bias; // Minimum exponent.
|
||||
|
||||
// How many hexadecimal digits are needed for the trailing significand?
|
||||
let digits = (t + 3) / 4;
|
||||
// Trailing significand left-aligned in `digits` hexadecimal digits.
|
||||
let left_t_bits = t_bits << (4 * digits - t);
|
||||
|
||||
// All formats share the leading sign.
|
||||
if sign_bit != 0 {
|
||||
write!(f, "-")?;
|
||||
}
|
||||
|
||||
if e_bits == 0 {
|
||||
if t_bits == 0 {
|
||||
// Zero.
|
||||
write!(f, "0.0")
|
||||
} else {
|
||||
// Subnormal.
|
||||
write!(f, "0x0.{0:01$x}p{2}", left_t_bits, digits as usize, emin)
|
||||
}
|
||||
} else if e_bits == max_e_bits {
|
||||
// Always print a `+` or `-` sign for these special values.
|
||||
// This makes them easier to parse as they can't be confused as identifiers.
|
||||
if sign_bit == 0 {
|
||||
write!(f, "+")?;
|
||||
}
|
||||
if t_bits == 0 {
|
||||
// Infinity.
|
||||
write!(f, "Inf")
|
||||
} else {
|
||||
// NaN.
|
||||
let payload = t_bits & ((1 << (t - 1)) - 1);
|
||||
if t_bits & (1 << (t - 1)) != 0 {
|
||||
// Quiet NaN.
|
||||
if payload != 0 {
|
||||
write!(f, "NaN:0x{:x}", payload)
|
||||
} else {
|
||||
write!(f, "NaN")
|
||||
}
|
||||
} else {
|
||||
// Signaling NaN.
|
||||
write!(f, "sNaN:0x{:x}", payload)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Normal number.
|
||||
write!(f, "0x1.{0:01$x}p{2}", left_t_bits, digits as usize, e)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse a float using the same format as `format_float` above.
|
||||
//
|
||||
// The encoding parameters are:
|
||||
//
|
||||
// w - exponent field width in bits
|
||||
// t - trailing significand field width in bits
|
||||
//
|
||||
fn parse_float(s: &str, w: u8, t: u8) -> Result<u64, &'static str> {
|
||||
debug_assert!(w > 0 && w <= 16, "Invalid exponent range");
|
||||
debug_assert!(1 + w + t <= 64, "Too large IEEE format for u64");
|
||||
debug_assert!((t + w + 1).is_power_of_two(), "Unexpected IEEE format size");
|
||||
|
||||
let (sign_bit, s2) = if s.starts_with('-') {
|
||||
(1u64 << (t + w), &s[1..])
|
||||
} else if s.starts_with('+') {
|
||||
(0, &s[1..])
|
||||
} else {
|
||||
(0, s)
|
||||
};
|
||||
|
||||
if !s2.starts_with("0x") {
|
||||
let max_e_bits = ((1u64 << w) - 1) << t;
|
||||
let quiet_bit = 1u64 << (t - 1);
|
||||
|
||||
// The only decimal encoding allowed is 0.
|
||||
if s2 == "0.0" {
|
||||
return Ok(sign_bit);
|
||||
}
|
||||
|
||||
if s2 == "Inf" {
|
||||
// +/- infinity: e = max, t = 0.
|
||||
return Ok(sign_bit | max_e_bits);
|
||||
}
|
||||
if s2 == "NaN" {
|
||||
// Canonical quiet NaN: e = max, t = quiet.
|
||||
return Ok(sign_bit | max_e_bits | quiet_bit);
|
||||
}
|
||||
if s2.starts_with("NaN:0x") {
|
||||
// Quiet NaN with payload.
|
||||
return match u64::from_str_radix(&s2[6..], 16) {
|
||||
Ok(payload) if payload < quiet_bit => {
|
||||
Ok(sign_bit | max_e_bits | quiet_bit | payload)
|
||||
}
|
||||
_ => Err("Invalid NaN payload"),
|
||||
};
|
||||
}
|
||||
if s2.starts_with("sNaN:0x") {
|
||||
// Signaling NaN with payload.
|
||||
return match u64::from_str_radix(&s2[7..], 16) {
|
||||
Ok(payload) if 0 < payload && payload < quiet_bit => {
|
||||
Ok(sign_bit | max_e_bits | payload)
|
||||
}
|
||||
_ => Err("Invalid sNaN payload"),
|
||||
};
|
||||
}
|
||||
|
||||
return Err("Float must be hexadecimal");
|
||||
}
|
||||
let s3 = &s2[2..];
|
||||
|
||||
let mut digits = 0u8;
|
||||
let mut digits_before_period: Option<u8> = None;
|
||||
let mut significand = 0u64;
|
||||
let mut exponent = 0i32;
|
||||
|
||||
for (idx, ch) in s3.char_indices() {
|
||||
match ch {
|
||||
'.' => {
|
||||
// This is the radix point. There can only be one.
|
||||
if digits_before_period != None {
|
||||
return Err("Multiple radix points");
|
||||
} else {
|
||||
digits_before_period = Some(digits);
|
||||
}
|
||||
}
|
||||
'p' => {
|
||||
// The following exponent is a decimal number.
|
||||
let exp_str = &s3[1 + idx..];
|
||||
match exp_str.parse::<i16>() {
|
||||
Ok(e) => {
|
||||
exponent = e as i32;
|
||||
break;
|
||||
}
|
||||
Err(_) => return Err("Bad exponent"),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
match ch.to_digit(16) {
|
||||
Some(digit) => {
|
||||
digits += 1;
|
||||
if digits > 16 {
|
||||
return Err("Too many digits");
|
||||
}
|
||||
significand = (significand << 4) | digit as u64;
|
||||
}
|
||||
None => return Err("Invalid character"),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if digits == 0 {
|
||||
return Err("No digits");
|
||||
}
|
||||
|
||||
if significand == 0 {
|
||||
// This is +/- 0.0.
|
||||
return Ok(sign_bit);
|
||||
}
|
||||
|
||||
// Number of bits appearing after the radix point.
|
||||
match digits_before_period {
|
||||
None => {} // No radix point present.
|
||||
Some(d) => exponent -= 4 * (digits - d) as i32,
|
||||
};
|
||||
|
||||
// Normalize the significand and exponent.
|
||||
let significant_bits = (64 - significand.leading_zeros()) as u8;
|
||||
if significant_bits > t + 1 {
|
||||
let adjust = significant_bits - (t + 1);
|
||||
if significand & ((1u64 << adjust) - 1) != 0 {
|
||||
return Err("Too many significant bits");
|
||||
}
|
||||
// Adjust significand down.
|
||||
significand >>= adjust;
|
||||
exponent += adjust as i32;
|
||||
} else {
|
||||
let adjust = t + 1 - significant_bits;
|
||||
significand <<= adjust;
|
||||
exponent -= adjust as i32;
|
||||
}
|
||||
assert_eq!(significand >> t, 1);
|
||||
|
||||
// Trailing significand excludes the high bit.
|
||||
let t_bits = significand & ((1 << t) - 1);
|
||||
|
||||
let max_exp = (1i32 << w) - 2;
|
||||
let bias: i32 = (1 << (w - 1)) - 1;
|
||||
exponent += bias + t as i32;
|
||||
|
||||
if exponent > max_exp {
|
||||
Err("Magnitude too large")
|
||||
} else if exponent > 0 {
|
||||
// This is a normal number.
|
||||
let e_bits = (exponent as u64) << t;
|
||||
Ok(sign_bit | e_bits | t_bits)
|
||||
} else if 1 - exponent <= t as i32 {
|
||||
// This is a subnormal number: e = 0, t = significand bits.
|
||||
// Renormalize significand for exponent = 1.
|
||||
let adjust = 1 - exponent;
|
||||
if significand & ((1u64 << adjust) - 1) != 0 {
|
||||
Err("Subnormal underflow")
|
||||
} else {
|
||||
significand >>= adjust;
|
||||
Ok(sign_bit | significand)
|
||||
}
|
||||
} else {
|
||||
Err("Magnitude too small")
|
||||
}
|
||||
}
|
||||
|
||||
impl Ieee32 {
|
||||
/// Create a new `Ieee32` containing the bits of `x`.
|
||||
pub fn with_bits(x: u32) -> Ieee32 {
|
||||
Ieee32(x)
|
||||
}
|
||||
|
||||
/// Create a new `Ieee32` representing the number `x`.
|
||||
#[cfg(test)]
|
||||
pub fn with_float(x: f32) -> Ieee32 {
|
||||
Ieee32(unsafe { mem::transmute(x) })
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Ieee32 {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
let bits: u32 = self.0;
|
||||
format_float(bits as u64, 8, 23, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Ieee32 {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Ieee32, &'static str> {
|
||||
match parse_float(s, 8, 23) {
|
||||
Ok(b) => Ok(Ieee32(b as u32)),
|
||||
Err(s) => Err(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ieee64 {
|
||||
/// Create a new `Ieee64` containing the bits of `x`.
|
||||
pub fn with_bits(x: u64) -> Ieee64 {
|
||||
Ieee64(x)
|
||||
}
|
||||
|
||||
/// Create a new `Ieee64` representing the number `x`.
|
||||
#[cfg(test)]
|
||||
pub fn with_float(x: f64) -> Ieee64 {
|
||||
Ieee64(unsafe { mem::transmute(x) })
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Ieee64 {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
let bits: u64 = self.0;
|
||||
format_float(bits, 11, 52, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Ieee64 {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Ieee64, &'static str> {
|
||||
match parse_float(s, 11, 52) {
|
||||
Ok(b) => Ok(Ieee64(b)),
|
||||
Err(s) => Err(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::{f32, f64};
|
||||
use std::str::FromStr;
|
||||
use std::fmt::Display;
|
||||
|
||||
#[test]
|
||||
fn format_imm64() {
|
||||
assert_eq!(Imm64(0).to_string(), "0");
|
||||
assert_eq!(Imm64(9999).to_string(), "9999");
|
||||
assert_eq!(Imm64(10000).to_string(), "0x2710");
|
||||
assert_eq!(Imm64(-9999).to_string(), "-9999");
|
||||
assert_eq!(Imm64(-10000).to_string(), "0xffff_ffff_ffff_d8f0");
|
||||
assert_eq!(Imm64(0xffff).to_string(), "0xffff");
|
||||
assert_eq!(Imm64(0x10000).to_string(), "0x0001_0000");
|
||||
}
|
||||
|
||||
// Verify that `text` can be parsed as a `T` into a value that displays as `want`.
|
||||
fn parse_ok<T: FromStr + Display>(text: &str, want: &str)
|
||||
where <T as FromStr>::Err: Display
|
||||
{
|
||||
match text.parse::<T>() {
|
||||
Err(s) => panic!("\"{}\".parse() error: {}", text, s),
|
||||
Ok(x) => assert_eq!(x.to_string(), want),
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that `text` fails to parse as `T` with the error `msg`.
|
||||
fn parse_err<T: FromStr + Display>(text: &str, msg: &str)
|
||||
where <T as FromStr>::Err: Display
|
||||
{
|
||||
match text.parse::<T>() {
|
||||
Err(s) => assert_eq!(s.to_string(), msg),
|
||||
Ok(x) => panic!("Wanted Err({}), but got {}", msg, x),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_imm64() {
|
||||
parse_ok::<Imm64>("0", "0");
|
||||
parse_ok::<Imm64>("1", "1");
|
||||
parse_ok::<Imm64>("-0", "0");
|
||||
parse_ok::<Imm64>("-1", "-1");
|
||||
parse_ok::<Imm64>("0x0", "0");
|
||||
parse_ok::<Imm64>("0xf", "15");
|
||||
parse_ok::<Imm64>("-0x9", "-9");
|
||||
|
||||
// Probe limits.
|
||||
parse_ok::<Imm64>("0xffffffff_ffffffff", "-1");
|
||||
parse_ok::<Imm64>("0x80000000_00000000", "0x8000_0000_0000_0000");
|
||||
parse_ok::<Imm64>("-0x80000000_00000000", "0x8000_0000_0000_0000");
|
||||
parse_err::<Imm64>("-0x80000000_00000001", "Negative number too small");
|
||||
parse_ok::<Imm64>("18446744073709551615", "-1");
|
||||
parse_ok::<Imm64>("-9223372036854775808", "0x8000_0000_0000_0000");
|
||||
// Overflow both the `checked_add` and `checked_mul`.
|
||||
parse_err::<Imm64>("18446744073709551616", "Too large decimal number");
|
||||
parse_err::<Imm64>("184467440737095516100", "Too large decimal number");
|
||||
parse_err::<Imm64>("-9223372036854775809", "Negative number too small");
|
||||
|
||||
// Underscores are allowed where digits go.
|
||||
parse_ok::<Imm64>("0_0", "0");
|
||||
parse_ok::<Imm64>("-_10_0", "-100");
|
||||
parse_ok::<Imm64>("_10_", "10");
|
||||
parse_ok::<Imm64>("0x97_88_bb", "0x0097_88bb");
|
||||
parse_ok::<Imm64>("0x_97_", "151");
|
||||
|
||||
parse_err::<Imm64>("", "No digits in number");
|
||||
parse_err::<Imm64>("-", "No digits in number");
|
||||
parse_err::<Imm64>("_", "No digits in number");
|
||||
parse_err::<Imm64>("0x", "No digits in number");
|
||||
parse_err::<Imm64>("0x_", "No digits in number");
|
||||
parse_err::<Imm64>("-0x", "No digits in number");
|
||||
parse_err::<Imm64>(" ", "Invalid character in decimal number");
|
||||
parse_err::<Imm64>("0 ", "Invalid character in decimal number");
|
||||
parse_err::<Imm64>(" 0", "Invalid character in decimal number");
|
||||
parse_err::<Imm64>("--", "Invalid character in decimal number");
|
||||
parse_err::<Imm64>("-0x-", "Invalid character in hexadecimal number");
|
||||
|
||||
// Hex count overflow.
|
||||
parse_err::<Imm64>("0x0_0000_0000_0000_0000", "Too many hexadecimal digits");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_offset32() {
|
||||
assert_eq!(Offset32(0).to_string(), "");
|
||||
assert_eq!(Offset32(1).to_string(), "+1");
|
||||
assert_eq!(Offset32(-1).to_string(), "-1");
|
||||
assert_eq!(Offset32(9999).to_string(), "+9999");
|
||||
assert_eq!(Offset32(10000).to_string(), "+0x2710");
|
||||
assert_eq!(Offset32(-9999).to_string(), "-9999");
|
||||
assert_eq!(Offset32(-10000).to_string(), "-0x2710");
|
||||
assert_eq!(Offset32(0xffff).to_string(), "+0xffff");
|
||||
assert_eq!(Offset32(0x10000).to_string(), "+0x0001_0000");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_offset32() {
|
||||
parse_ok::<Offset32>("+0", "");
|
||||
parse_ok::<Offset32>("+1", "+1");
|
||||
parse_ok::<Offset32>("-0", "");
|
||||
parse_ok::<Offset32>("-1", "-1");
|
||||
parse_ok::<Offset32>("+0x0", "");
|
||||
parse_ok::<Offset32>("+0xf", "+15");
|
||||
parse_ok::<Offset32>("-0x9", "-9");
|
||||
parse_ok::<Offset32>("-0x8000_0000", "-0x8000_0000");
|
||||
|
||||
parse_err::<Offset32>("+0x8000_0000", "Offset out of range");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_uoffset32() {
|
||||
assert_eq!(Uoffset32(0).to_string(), "");
|
||||
assert_eq!(Uoffset32(1).to_string(), "+1");
|
||||
assert_eq!(Uoffset32(9999).to_string(), "+9999");
|
||||
assert_eq!(Uoffset32(10000).to_string(), "+0x2710");
|
||||
assert_eq!(Uoffset32(0xffff).to_string(), "+0xffff");
|
||||
assert_eq!(Uoffset32(0x10000).to_string(), "+0x0001_0000");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_uoffset32() {
|
||||
parse_ok::<Uoffset32>("+0", "");
|
||||
parse_ok::<Uoffset32>("+1", "+1");
|
||||
parse_ok::<Uoffset32>("+0x0", "");
|
||||
parse_ok::<Uoffset32>("+0xf", "+15");
|
||||
parse_ok::<Uoffset32>("+0x8000_0000", "+0x8000_0000");
|
||||
parse_ok::<Uoffset32>("+0xffff_ffff", "+0xffff_ffff");
|
||||
|
||||
parse_err::<Uoffset32>("+0x1_0000_0000", "Offset out of range");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_ieee32() {
|
||||
assert_eq!(Ieee32::with_float(0.0).to_string(), "0.0");
|
||||
assert_eq!(Ieee32::with_float(-0.0).to_string(), "-0.0");
|
||||
assert_eq!(Ieee32::with_float(1.0).to_string(), "0x1.000000p0");
|
||||
assert_eq!(Ieee32::with_float(1.5).to_string(), "0x1.800000p0");
|
||||
assert_eq!(Ieee32::with_float(0.5).to_string(), "0x1.000000p-1");
|
||||
assert_eq!(Ieee32::with_float(f32::EPSILON).to_string(),
|
||||
"0x1.000000p-23");
|
||||
assert_eq!(Ieee32::with_float(f32::MIN).to_string(), "-0x1.fffffep127");
|
||||
assert_eq!(Ieee32::with_float(f32::MAX).to_string(), "0x1.fffffep127");
|
||||
// Smallest positive normal number.
|
||||
assert_eq!(Ieee32::with_float(f32::MIN_POSITIVE).to_string(),
|
||||
"0x1.000000p-126");
|
||||
// Subnormals.
|
||||
assert_eq!(Ieee32::with_float(f32::MIN_POSITIVE / 2.0).to_string(),
|
||||
"0x0.800000p-126");
|
||||
assert_eq!(Ieee32::with_float(f32::MIN_POSITIVE * f32::EPSILON).to_string(),
|
||||
"0x0.000002p-126");
|
||||
assert_eq!(Ieee32::with_float(f32::INFINITY).to_string(), "+Inf");
|
||||
assert_eq!(Ieee32::with_float(f32::NEG_INFINITY).to_string(), "-Inf");
|
||||
assert_eq!(Ieee32::with_float(f32::NAN).to_string(), "+NaN");
|
||||
assert_eq!(Ieee32::with_float(-f32::NAN).to_string(), "-NaN");
|
||||
// Construct some qNaNs with payloads.
|
||||
assert_eq!(Ieee32(0x7fc00001).to_string(), "+NaN:0x1");
|
||||
assert_eq!(Ieee32(0x7ff00001).to_string(), "+NaN:0x300001");
|
||||
// Signaling NaNs.
|
||||
assert_eq!(Ieee32(0x7f800001).to_string(), "+sNaN:0x1");
|
||||
assert_eq!(Ieee32(0x7fa00001).to_string(), "+sNaN:0x200001");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_ieee32() {
|
||||
parse_ok::<Ieee32>("0.0", "0.0");
|
||||
parse_ok::<Ieee32>("+0.0", "0.0");
|
||||
parse_ok::<Ieee32>("-0.0", "-0.0");
|
||||
parse_ok::<Ieee32>("0x0", "0.0");
|
||||
parse_ok::<Ieee32>("0x0.0", "0.0");
|
||||
parse_ok::<Ieee32>("0x.0", "0.0");
|
||||
parse_ok::<Ieee32>("0x0.", "0.0");
|
||||
parse_ok::<Ieee32>("0x1", "0x1.000000p0");
|
||||
parse_ok::<Ieee32>("+0x1", "0x1.000000p0");
|
||||
parse_ok::<Ieee32>("-0x1", "-0x1.000000p0");
|
||||
parse_ok::<Ieee32>("0x10", "0x1.000000p4");
|
||||
parse_ok::<Ieee32>("0x10.0", "0x1.000000p4");
|
||||
parse_err::<Ieee32>("0.", "Float must be hexadecimal");
|
||||
parse_err::<Ieee32>(".0", "Float must be hexadecimal");
|
||||
parse_err::<Ieee32>("0", "Float must be hexadecimal");
|
||||
parse_err::<Ieee32>("-0", "Float must be hexadecimal");
|
||||
parse_err::<Ieee32>(".", "Float must be hexadecimal");
|
||||
parse_err::<Ieee32>("", "Float must be hexadecimal");
|
||||
parse_err::<Ieee32>("-", "Float must be hexadecimal");
|
||||
parse_err::<Ieee32>("0x", "No digits");
|
||||
parse_err::<Ieee32>("0x..", "Multiple radix points");
|
||||
|
||||
// Check significant bits.
|
||||
parse_ok::<Ieee32>("0x0.ffffff", "0x1.fffffep-1");
|
||||
parse_ok::<Ieee32>("0x1.fffffe", "0x1.fffffep0");
|
||||
parse_ok::<Ieee32>("0x3.fffffc", "0x1.fffffep1");
|
||||
parse_ok::<Ieee32>("0x7.fffff8", "0x1.fffffep2");
|
||||
parse_ok::<Ieee32>("0xf.fffff0", "0x1.fffffep3");
|
||||
parse_err::<Ieee32>("0x1.ffffff", "Too many significant bits");
|
||||
parse_err::<Ieee32>("0x1.fffffe0000000000", "Too many digits");
|
||||
|
||||
// Exponents.
|
||||
parse_ok::<Ieee32>("0x1p3", "0x1.000000p3");
|
||||
parse_ok::<Ieee32>("0x1p-3", "0x1.000000p-3");
|
||||
parse_ok::<Ieee32>("0x1.0p3", "0x1.000000p3");
|
||||
parse_ok::<Ieee32>("0x2.0p3", "0x1.000000p4");
|
||||
parse_ok::<Ieee32>("0x1.0p127", "0x1.000000p127");
|
||||
parse_ok::<Ieee32>("0x1.0p-126", "0x1.000000p-126");
|
||||
parse_ok::<Ieee32>("0x0.1p-122", "0x1.000000p-126");
|
||||
parse_err::<Ieee32>("0x2.0p127", "Magnitude too large");
|
||||
|
||||
// Subnormals.
|
||||
parse_ok::<Ieee32>("0x1.0p-127", "0x0.800000p-126");
|
||||
parse_ok::<Ieee32>("0x1.0p-149", "0x0.000002p-126");
|
||||
parse_ok::<Ieee32>("0x0.000002p-126", "0x0.000002p-126");
|
||||
parse_err::<Ieee32>("0x0.100001p-126", "Subnormal underflow");
|
||||
parse_err::<Ieee32>("0x1.8p-149", "Subnormal underflow");
|
||||
parse_err::<Ieee32>("0x1.0p-150", "Magnitude too small");
|
||||
|
||||
// NaNs and Infs.
|
||||
parse_ok::<Ieee32>("Inf", "+Inf");
|
||||
parse_ok::<Ieee32>("+Inf", "+Inf");
|
||||
parse_ok::<Ieee32>("-Inf", "-Inf");
|
||||
parse_ok::<Ieee32>("NaN", "+NaN");
|
||||
parse_ok::<Ieee32>("+NaN", "+NaN");
|
||||
parse_ok::<Ieee32>("-NaN", "-NaN");
|
||||
parse_ok::<Ieee32>("NaN:0x0", "+NaN");
|
||||
parse_err::<Ieee32>("NaN:", "Float must be hexadecimal");
|
||||
parse_err::<Ieee32>("NaN:0", "Float must be hexadecimal");
|
||||
parse_err::<Ieee32>("NaN:0x", "Invalid NaN payload");
|
||||
parse_ok::<Ieee32>("NaN:0x000001", "+NaN:0x1");
|
||||
parse_ok::<Ieee32>("NaN:0x300001", "+NaN:0x300001");
|
||||
parse_err::<Ieee32>("NaN:0x400001", "Invalid NaN payload");
|
||||
parse_ok::<Ieee32>("sNaN:0x1", "+sNaN:0x1");
|
||||
parse_err::<Ieee32>("sNaN:0x0", "Invalid sNaN payload");
|
||||
parse_ok::<Ieee32>("sNaN:0x200001", "+sNaN:0x200001");
|
||||
parse_err::<Ieee32>("sNaN:0x400001", "Invalid sNaN payload");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_ieee64() {
|
||||
assert_eq!(Ieee64::with_float(0.0).to_string(), "0.0");
|
||||
assert_eq!(Ieee64::with_float(-0.0).to_string(), "-0.0");
|
||||
assert_eq!(Ieee64::with_float(1.0).to_string(), "0x1.0000000000000p0");
|
||||
assert_eq!(Ieee64::with_float(1.5).to_string(), "0x1.8000000000000p0");
|
||||
assert_eq!(Ieee64::with_float(0.5).to_string(), "0x1.0000000000000p-1");
|
||||
assert_eq!(Ieee64::with_float(f64::EPSILON).to_string(),
|
||||
"0x1.0000000000000p-52");
|
||||
assert_eq!(Ieee64::with_float(f64::MIN).to_string(),
|
||||
"-0x1.fffffffffffffp1023");
|
||||
assert_eq!(Ieee64::with_float(f64::MAX).to_string(),
|
||||
"0x1.fffffffffffffp1023");
|
||||
// Smallest positive normal number.
|
||||
assert_eq!(Ieee64::with_float(f64::MIN_POSITIVE).to_string(),
|
||||
"0x1.0000000000000p-1022");
|
||||
// Subnormals.
|
||||
assert_eq!(Ieee64::with_float(f64::MIN_POSITIVE / 2.0).to_string(),
|
||||
"0x0.8000000000000p-1022");
|
||||
assert_eq!(Ieee64::with_float(f64::MIN_POSITIVE * f64::EPSILON).to_string(),
|
||||
"0x0.0000000000001p-1022");
|
||||
assert_eq!(Ieee64::with_float(f64::INFINITY).to_string(), "+Inf");
|
||||
assert_eq!(Ieee64::with_float(f64::NEG_INFINITY).to_string(), "-Inf");
|
||||
assert_eq!(Ieee64::with_float(f64::NAN).to_string(), "+NaN");
|
||||
assert_eq!(Ieee64::with_float(-f64::NAN).to_string(), "-NaN");
|
||||
// Construct some qNaNs with payloads.
|
||||
assert_eq!(Ieee64(0x7ff8000000000001).to_string(), "+NaN:0x1");
|
||||
assert_eq!(Ieee64(0x7ffc000000000001).to_string(),
|
||||
"+NaN:0x4000000000001");
|
||||
// Signaling NaNs.
|
||||
assert_eq!(Ieee64(0x7ff0000000000001).to_string(), "+sNaN:0x1");
|
||||
assert_eq!(Ieee64(0x7ff4000000000001).to_string(),
|
||||
"+sNaN:0x4000000000001");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_ieee64() {
|
||||
parse_ok::<Ieee64>("0.0", "0.0");
|
||||
parse_ok::<Ieee64>("-0.0", "-0.0");
|
||||
parse_ok::<Ieee64>("0x0", "0.0");
|
||||
parse_ok::<Ieee64>("0x0.0", "0.0");
|
||||
parse_ok::<Ieee64>("0x.0", "0.0");
|
||||
parse_ok::<Ieee64>("0x0.", "0.0");
|
||||
parse_ok::<Ieee64>("0x1", "0x1.0000000000000p0");
|
||||
parse_ok::<Ieee64>("-0x1", "-0x1.0000000000000p0");
|
||||
parse_ok::<Ieee64>("0x10", "0x1.0000000000000p4");
|
||||
parse_ok::<Ieee64>("0x10.0", "0x1.0000000000000p4");
|
||||
parse_err::<Ieee64>("0.", "Float must be hexadecimal");
|
||||
parse_err::<Ieee64>(".0", "Float must be hexadecimal");
|
||||
parse_err::<Ieee64>("0", "Float must be hexadecimal");
|
||||
parse_err::<Ieee64>("-0", "Float must be hexadecimal");
|
||||
parse_err::<Ieee64>(".", "Float must be hexadecimal");
|
||||
parse_err::<Ieee64>("", "Float must be hexadecimal");
|
||||
parse_err::<Ieee64>("-", "Float must be hexadecimal");
|
||||
parse_err::<Ieee64>("0x", "No digits");
|
||||
parse_err::<Ieee64>("0x..", "Multiple radix points");
|
||||
|
||||
// Check significant bits.
|
||||
parse_ok::<Ieee64>("0x0.fffffffffffff8", "0x1.fffffffffffffp-1");
|
||||
parse_ok::<Ieee64>("0x1.fffffffffffff", "0x1.fffffffffffffp0");
|
||||
parse_ok::<Ieee64>("0x3.ffffffffffffe", "0x1.fffffffffffffp1");
|
||||
parse_ok::<Ieee64>("0x7.ffffffffffffc", "0x1.fffffffffffffp2");
|
||||
parse_ok::<Ieee64>("0xf.ffffffffffff8", "0x1.fffffffffffffp3");
|
||||
parse_err::<Ieee64>("0x3.fffffffffffff", "Too many significant bits");
|
||||
parse_err::<Ieee64>("0x001.fffffe00000000", "Too many digits");
|
||||
|
||||
// Exponents.
|
||||
parse_ok::<Ieee64>("0x1p3", "0x1.0000000000000p3");
|
||||
parse_ok::<Ieee64>("0x1p-3", "0x1.0000000000000p-3");
|
||||
parse_ok::<Ieee64>("0x1.0p3", "0x1.0000000000000p3");
|
||||
parse_ok::<Ieee64>("0x2.0p3", "0x1.0000000000000p4");
|
||||
parse_ok::<Ieee64>("0x1.0p1023", "0x1.0000000000000p1023");
|
||||
parse_ok::<Ieee64>("0x1.0p-1022", "0x1.0000000000000p-1022");
|
||||
parse_ok::<Ieee64>("0x0.1p-1018", "0x1.0000000000000p-1022");
|
||||
parse_err::<Ieee64>("0x2.0p1023", "Magnitude too large");
|
||||
|
||||
// Subnormals.
|
||||
parse_ok::<Ieee64>("0x1.0p-1023", "0x0.8000000000000p-1022");
|
||||
parse_ok::<Ieee64>("0x1.0p-1074", "0x0.0000000000001p-1022");
|
||||
parse_ok::<Ieee64>("0x0.0000000000001p-1022", "0x0.0000000000001p-1022");
|
||||
parse_err::<Ieee64>("0x0.10000000000008p-1022", "Subnormal underflow");
|
||||
parse_err::<Ieee64>("0x1.8p-1074", "Subnormal underflow");
|
||||
parse_err::<Ieee64>("0x1.0p-1075", "Magnitude too small");
|
||||
|
||||
// NaNs and Infs.
|
||||
parse_ok::<Ieee64>("Inf", "+Inf");
|
||||
parse_ok::<Ieee64>("-Inf", "-Inf");
|
||||
parse_ok::<Ieee64>("NaN", "+NaN");
|
||||
parse_ok::<Ieee64>("-NaN", "-NaN");
|
||||
parse_ok::<Ieee64>("NaN:0x0", "+NaN");
|
||||
parse_err::<Ieee64>("NaN:", "Float must be hexadecimal");
|
||||
parse_err::<Ieee64>("NaN:0", "Float must be hexadecimal");
|
||||
parse_err::<Ieee64>("NaN:0x", "Invalid NaN payload");
|
||||
parse_ok::<Ieee64>("NaN:0x000001", "+NaN:0x1");
|
||||
parse_ok::<Ieee64>("NaN:0x4000000000001", "+NaN:0x4000000000001");
|
||||
parse_err::<Ieee64>("NaN:0x8000000000001", "Invalid NaN payload");
|
||||
parse_ok::<Ieee64>("sNaN:0x1", "+sNaN:0x1");
|
||||
parse_err::<Ieee64>("sNaN:0x0", "Invalid sNaN payload");
|
||||
parse_ok::<Ieee64>("sNaN:0x4000000000001", "+sNaN:0x4000000000001");
|
||||
parse_err::<Ieee64>("sNaN:0x8000000000001", "Invalid sNaN payload");
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user