Merge branch 'bytecodealliance:main' into main

This commit is contained in:
Advance Software
2021-10-11 10:04:05 +01:00
committed by GitHub
637 changed files with 6190 additions and 74091 deletions

View File

@@ -131,7 +131,6 @@ jobs:
# Check some feature combinations of the `wasmtime` crate
- run: cargo check -p wasmtime --no-default-features
- run: cargo check -p wasmtime --no-default-features --features wat
- run: cargo check -p wasmtime --no-default-features --features lightbeam
- run: cargo check -p wasmtime --no-default-features --features jitdump
- run: cargo check -p wasmtime --no-default-features --features vtune
- run: cargo check -p wasmtime --no-default-features --features cache
@@ -236,6 +235,12 @@ jobs:
qemu_target: aarch64-linux-user
# FIXME(#3183) shouldn't be necessary to specify this
qemu_flags: -cpu max,pauth=off
- os: ubuntu-latest
target: s390x-unknown-linux-gnu
gcc_package: gcc-s390x-linux-gnu
gcc: s390x-linux-gnu-gcc
qemu: qemu-s390x -L /usr/s390x-linux-gnu
qemu_target: s390x-linux-user
steps:
- uses: actions/checkout@v2
with:
@@ -301,7 +306,7 @@ jobs:
RUST_BACKTRACE: 1
if: matrix.target == ''
# Build and test all features except for lightbeam
# Build and test all features
- run: ./ci/run-tests.sh --locked
env:
RUST_BACKTRACE: 1
@@ -323,38 +328,6 @@ jobs:
env:
RUST_BACKTRACE: 1
# Build and test lightbeam. Note that
# Lightbeam tests fail right now, but we don't want to block on that.
- run: cargo build --package lightbeam
if: matrix.target != 'aarch64-unknown-linux-gnu'
- run: cargo test --package lightbeam
if: matrix.target != 'aarch64-unknown-linux-gnu'
continue-on-error: true
env:
RUST_BACKTRACE: 1
# Perform all tests (debug mode) for `wasmtime` with the old x86 backend.
test_x86:
name: Test old x86 backend
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- uses: ./.github/actions/install-rust
with:
toolchain: stable
# Install wasm32 targets in order to build various tests throughout the
# repo.
- run: rustup target add wasm32-wasi
- run: rustup target add wasm32-unknown-unknown
# Run the old x86 backend CI (we will eventually remove this).
- run: ./ci/run-tests.sh --features old-x86-backend --locked
env:
RUST_BACKTRACE: 1
# Build and test the wasi-nn module.
test_wasi_nn:
name: Test wasi-nn module
@@ -434,6 +407,11 @@ jobs:
target: aarch64-unknown-linux-gnu
gcc_package: gcc-aarch64-linux-gnu
gcc: aarch64-linux-gnu-gcc
- build: s390x-linux
os: ubuntu-latest
target: s390x-unknown-linux-gnu
gcc_package: gcc-s390x-linux-gnu
gcc: s390x-linux-gnu-gcc
steps:
- uses: actions/checkout@v2
with:

525
Cargo.lock generated
View File

@@ -19,29 +19,30 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aead"
version = "0.3.2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331"
checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877"
dependencies = [
"generic-array",
]
[[package]]
name = "aes"
version = "0.6.0"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561"
checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
dependencies = [
"aes-soft",
"aesni",
"cfg-if 1.0.0",
"cipher",
"cpufeatures 0.2.1",
"opaque-debug",
]
[[package]]
name = "aes-gcm"
version = "0.8.0"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5278b5fabbb9bd46e24aa69b2fdea62c99088e0a950a9be40e3e0101298f88da"
checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6"
dependencies = [
"aead",
"aes",
@@ -51,26 +52,6 @@ dependencies = [
"subtle",
]
[[package]]
name = "aes-soft"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072"
dependencies = [
"cipher",
"opaque-debug",
]
[[package]]
name = "aesni"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce"
dependencies = [
"cipher",
"opaque-debug",
]
[[package]]
name = "ahash"
version = "0.4.7"
@@ -125,12 +106,6 @@ dependencies = [
"derive_arbitrary",
]
[[package]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "async-trait"
version = "0.1.50"
@@ -186,6 +161,12 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "base64ct"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40a96587c05c810ddbb79e2674d519cff1379517e7b91d166dff7a7cc0e9af6e"
[[package]]
name = "bincode"
version = "1.3.3"
@@ -235,17 +216,6 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitvec"
version = "0.18.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98fcd36dda4e17b7d7abc64cb549bf0201f4ab71e00700c798ca7e62ed3761fa"
dependencies = [
"funty",
"radium",
"wyz",
]
[[package]]
name = "block-buffer"
version = "0.9.0"
@@ -427,19 +397,21 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chacha20"
version = "0.6.0"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed8738f14471a99f0e316c327e68fc82a3611cc2895fcb604b89eedaf8f39d95"
checksum = "01b72a433d0cf2aef113ba70f62634c56fddb0f244e6377185c56a7cadbd8f91"
dependencies = [
"cfg-if 1.0.0",
"cipher",
"cpufeatures 0.2.1",
"zeroize",
]
[[package]]
name = "chacha20poly1305"
version = "0.7.1"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af1fc18e6d90c40164bf6c317476f2a98f04661e310e79830366b7e914c58a8e"
checksum = "3b84ed6d1d5f7aa9bdde921a5090e0ca4d934d250ea3b402a5fab3a994e28a2a"
dependencies = [
"aead",
"chacha20",
@@ -457,15 +429,14 @@ dependencies = [
"libc",
"num-integer",
"num-traits",
"time",
"winapi",
]
[[package]]
name = "cipher"
version = "0.2.5"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801"
checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
dependencies = [
"generic-array",
]
@@ -522,15 +493,9 @@ dependencies = [
[[package]]
name = "const-oid"
version = "0.4.5"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f6b64db6932c7e49332728e3a6bd82c6b7e16016607d20923b537c3bc4c0d5f"
[[package]]
name = "convert_case"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
checksum = "fdab415d6744056100f40250a66bc430c1a46f7a02e20bc11c94c79a0f0464df"
[[package]]
name = "cpp_demangle"
@@ -552,10 +517,13 @@ dependencies = [
]
[[package]]
name = "cpuid-bool"
version = "0.2.0"
name = "cpufeatures"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba"
checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
dependencies = [
"libc",
]
[[package]]
name = "cranelift"
@@ -708,7 +676,7 @@ name = "cranelift-native"
version = "0.77.0"
dependencies = [
"cranelift-codegen",
"rsix",
"libc",
"target-lexicon",
]
@@ -900,10 +868,22 @@ dependencies = [
]
[[package]]
name = "crypto-mac"
version = "0.10.0"
name = "crypto-bigint"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4857fd85a0c34b3c3297875b747c1e02e06b6a0ea32dd892d8192b9ce0813ea6"
checksum = "d12477e115c0d570c12a2dfd859f80b55b60ddb5075df210d3af06d133a69f45"
dependencies = [
"generic-array",
"rand_core 0.6.3",
"subtle",
"zeroize",
]
[[package]]
name = "crypto-mac"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
dependencies = [
"generic-array",
"subtle",
@@ -933,9 +913,9 @@ dependencies = [
[[package]]
name = "ctr"
version = "0.6.0"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb4a30d54f7443bf3d6191dcd486aca19e67cb3c49fa7a06a319966346707e7f"
checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea"
dependencies = [
"cipher",
]
@@ -961,11 +941,12 @@ dependencies = [
[[package]]
name = "der"
version = "0.1.0"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51f59c66c30bb7445c8320a5f9233e437e3572368099f25532a59054328899b4"
checksum = "28e98c534e9c8a0483aa01d6f6913bc063de254311bd267c9cf535e9b70e15b2"
dependencies = [
"const-oid",
"crypto-bigint",
]
[[package]]
@@ -990,29 +971,6 @@ dependencies = [
"syn",
]
[[package]]
name = "derive_more"
version = "0.99.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cc7b9cef1e351660e5443924e4f43ab25fbbed3e9a5f052df3677deb4d6b320"
dependencies = [
"convert_case",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "derive_utils"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "532b4c15dccee12c7044f1fcad956e98410860b22231e44a3b827464797ca7bf"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "digest"
version = "0.9.0"
@@ -1059,38 +1017,13 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
[[package]]
name = "dynasm"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdc2d9a5e44da60059bd38db2d05cbb478619541b8c79890547861ec1e3194f0"
dependencies = [
"bitflags",
"byteorder",
"lazy_static",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "dynasmrt"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42276e3f205fe63887cca255aa9a65a63fb72764c30b9a6252a7c7e46994f689"
dependencies = [
"byteorder",
"dynasm",
"memmap2",
]
[[package]]
name = "ecdsa"
version = "0.10.2"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fbdb4ff710acb4db8ca29f93b897529ea6d6a45626d5183b47e012aa6ae7e4"
checksum = "43ee23aa5b4f68c7a092b5c3beb25f50c406adc75e2363634f242f28ab255372"
dependencies = [
"der",
"elliptic-curve",
"hmac",
"signature",
@@ -1127,18 +1060,16 @@ checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "elliptic-curve"
version = "0.8.5"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2db227e61a43a34915680bdda462ec0e212095518020a88a1f91acd16092c39"
checksum = "beca177dcb8eb540133e7680baff45e7cc4d93bf22002676cec549f82343721b"
dependencies = [
"bitvec",
"digest",
"crypto-bigint",
"ff",
"funty",
"generic-array",
"group",
"pkcs8",
"rand_core 0.5.1",
"rand_core 0.6.3",
"subtle",
"zeroize",
]
@@ -1216,12 +1147,11 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
[[package]]
name = "ff"
version = "0.8.0"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01646e077d4ebda82b73f1bca002ea1e91561a77df2431a9e79729bcc31950ef"
checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f"
dependencies = [
"bitvec",
"rand_core 0.5.1",
"rand_core 0.6.3",
"subtle",
]
@@ -1290,12 +1220,6 @@ version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e398fae362f4124bbe630d99519fb2d68a03e2e3a23b441028cdcdc4f4895687"
[[package]]
name = "funty"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
[[package]]
name = "gcc"
version = "0.3.55"
@@ -1336,9 +1260,9 @@ dependencies = [
[[package]]
name = "ghash"
version = "0.3.1"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97304e4cd182c3846f7575ced3890c53012ce534ad9114046b0a9e00bb30a375"
checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99"
dependencies = [
"opaque-debug",
"polyval",
@@ -1363,12 +1287,12 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "group"
version = "0.8.0"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc11f9f5fbf1943b48ae7c2bf6846e7d827a512d1be4f23af708f5ca5d01dde1"
checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912"
dependencies = [
"ff",
"rand_core 0.5.1",
"rand_core 0.6.3",
"subtle",
]
@@ -1407,9 +1331,9 @@ dependencies = [
[[package]]
name = "hkdf"
version = "0.10.0"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51ab2f639c231793c5f6114bdb9bbe50a7dbbfcd7c7c6bd8475dec2d991e964f"
checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b"
dependencies = [
"digest",
"hmac",
@@ -1417,9 +1341,9 @@ dependencies = [
[[package]]
name = "hmac"
version = "0.10.1"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15"
checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
dependencies = [
"crypto-mac",
"digest",
@@ -1494,17 +1418,6 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135"
[[package]]
name = "iter-enum"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f947f0d9df7e69c4df60a950c0a83741455bb9ebd8fd9b5a87994dda4dbb005"
dependencies = [
"derive_utils",
"quote",
"syn",
]
[[package]]
name = "itertools"
version = "0.9.0"
@@ -1558,9 +1471,9 @@ dependencies = [
[[package]]
name = "k256"
version = "0.7.3"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4476a0808212a9e81ce802eb1a0cfc60e73aea296553bacc0fac7e1268bc572a"
checksum = "903ae2481bcdfdb7b68e0a9baa4b7c9aff600b9ae2e8e5bb5833b8c91ab851ea"
dependencies = [
"cfg-if 1.0.0",
"ecdsa",
@@ -1591,9 +1504,9 @@ checksum = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a"
[[package]]
name = "libc"
version = "0.2.99"
version = "0.2.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7f823d141fe0a24df1e23b4af4e3c7ba9e5966ec514ea068c93024aa7deb765"
checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6"
[[package]]
name = "libfuzzer-sys"
@@ -1622,30 +1535,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
[[package]]
name = "lightbeam"
version = "0.30.0"
dependencies = [
"anyhow",
"arrayvec",
"capstone",
"cranelift-codegen",
"derive_more",
"dynasm",
"dynasmrt",
"iter-enum",
"itertools 0.10.0",
"lazy_static",
"memoffset",
"more-asserts",
"quickcheck",
"smallvec",
"thiserror",
"typemap",
"wasmparser",
"wat",
]
[[package]]
name = "linux-raw-sys"
version = "0.0.24"
@@ -1805,22 +1694,11 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e0d047c1062aa51e256408c560894e5251f08925980e53cf1aa5bd00eec6512"
dependencies = [
"autocfg 1.0.1",
"num-integer",
"num-traits",
]
[[package]]
name = "num-bigint-dig"
version = "0.6.1"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d51546d704f52ef14b3c962b5776e53d5b862e5790e40a350d366c209bd7f7a"
checksum = "4547ee5541c18742396ae2c895d0717d0f886d8823b8399cdaf7b07d63ad0480"
dependencies = [
"autocfg 0.1.7",
"byteorder",
@@ -1829,7 +1707,7 @@ dependencies = [
"num-integer",
"num-iter",
"num-traits",
"rand 0.7.3",
"rand 0.8.3",
"serde",
"smallvec",
"zeroize",
@@ -1863,7 +1741,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
dependencies = [
"autocfg 1.0.1",
"num-bigint 0.2.6",
"num-bigint",
"num-integer",
"num-traits",
]
@@ -1875,6 +1753,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg 1.0.1",
"libm",
]
[[package]]
@@ -1991,9 +1870,9 @@ dependencies = [
[[package]]
name = "p256"
version = "0.7.3"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8adcc06fe90ec8fb2d2ad46746d2cbd639b158d4240364aa832da7e263dbee91"
checksum = "d053368e1bae4c8a672953397bd1bd7183dde1c72b0b7612a15719173148d186"
dependencies = [
"ecdsa",
"elliptic-curve",
@@ -2148,14 +2027,12 @@ name = "peepmatic-traits"
version = "0.77.0"
[[package]]
name = "pem"
version = "0.8.3"
name = "pem-rfc7468"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb"
checksum = "e71fb2d401a15271d52aade6d9410fb4ead603a86da5503f92e872e1df790265"
dependencies = [
"base64",
"once_cell",
"regex",
"base64ct",
]
[[package]]
@@ -2165,13 +2042,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905"
[[package]]
name = "pkcs8"
version = "0.3.3"
name = "pkcs1"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4839a901843f3942576e65857f0ebf2e190ef7024d3c62a94099ba3f819ad1d"
checksum = "116bee8279d783c0cf370efa1a94632f2108e5ef0bb32df31f051647810a4e2c"
dependencies = [
"der",
"subtle-encoding",
"pem-rfc7468",
"zeroize",
]
[[package]]
name = "pkcs8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447"
dependencies = [
"der",
"pem-rfc7468",
"pkcs1",
"spki",
"zeroize",
]
@@ -2205,21 +2095,23 @@ dependencies = [
[[package]]
name = "poly1305"
version = "0.6.2"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b7456bc1ad2d4cf82b3a016be4c2ac48daf11bf990c1603ebd447fe6f30fca8"
checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede"
dependencies = [
"cpuid-bool",
"cpufeatures 0.2.1",
"opaque-debug",
"universal-hash",
]
[[package]]
name = "polyval"
version = "0.4.5"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eebcc4aa140b9abd2bc40d9c3f7ccec842679cd79045ac3a7ac698c1a064b7cd"
checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1"
dependencies = [
"cpuid-bool",
"cfg-if 1.0.0",
"cpufeatures 0.2.1",
"opaque-debug",
"universal-hash",
]
@@ -2232,31 +2124,43 @@ checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
[[package]]
name = "pqcrypto"
version = "0.12.2"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d3874384bf37d988b83f806d632e2f7fca69a8cd0338efaa64e8e7664573052"
checksum = "9da39bd0587bff4189521766c34f3203263926f7527906578a96d22a81a700d5"
dependencies = [
"pqcrypto-kyber",
"pqcrypto-traits",
]
[[package]]
name = "pqcrypto-kyber"
version = "0.6.7"
name = "pqcrypto-internals"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33550a5b6e0844d1b2363f67e15e4ca64586bb4fb2363a83af762e6c2d092bff"
checksum = "c5397335b92875d36fb30f91557c3769517c9cfbc212568a5b8ceafd304eca84"
dependencies = [
"cc",
"getrandom 0.2.3",
"libc",
]
[[package]]
name = "pqcrypto-kyber"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a2b5431714840eb2c0ccc167d3d17940e66f48da9ab96c51ea1d4e0aa46d6a1"
dependencies = [
"cc",
"glob",
"libc",
"pqcrypto-internals",
"pqcrypto-traits",
]
[[package]]
name = "pqcrypto-traits"
version = "0.3.3"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4e1563eff60a9ae869cacee0a33fa5c4ba27861fec6e3e23de95eb0ae805e4b"
checksum = "97e91cb6af081c6daad5fa705f8adb0634c027662052cb3174bdf2957bf07e25"
[[package]]
name = "pretty_env_logger"
@@ -2342,17 +2246,6 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quickcheck"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6"
dependencies = [
"env_logger 0.8.3",
"log",
"rand 0.8.3",
]
[[package]]
name = "quote"
version = "1.0.9"
@@ -2362,12 +2255,6 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "radium"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "def50a86306165861203e7f84ecffbbdfdea79f0e51039b33de1e952358c47ac"
[[package]]
name = "rand"
version = "0.7.3"
@@ -2389,7 +2276,7 @@ checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
dependencies = [
"libc",
"rand_chacha 0.3.0",
"rand_core 0.6.2",
"rand_core 0.6.3",
"rand_hc 0.3.0",
]
@@ -2410,7 +2297,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
dependencies = [
"ppv-lite86",
"rand_core 0.6.2",
"rand_core 0.6.3",
]
[[package]]
@@ -2424,9 +2311,9 @@ dependencies = [
[[package]]
name = "rand_core"
version = "0.6.2"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom 0.2.3",
]
@@ -2446,7 +2333,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
dependencies = [
"rand_core 0.6.2",
"rand_core 0.6.3",
]
[[package]]
@@ -2455,7 +2342,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f"
dependencies = [
"rand_core 0.6.2",
"rand_core 0.6.3",
]
[[package]]
@@ -2569,9 +2456,9 @@ dependencies = [
[[package]]
name = "rsa"
version = "0.3.0"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3648b669b10afeab18972c105e284a7b953a669b0be3514c27f9b17acab2f9cd"
checksum = "e05c2603e2823634ab331437001b411b9ed11660fbc4066f3908c84a9439260d"
dependencies = [
"byteorder",
"digest",
@@ -2580,27 +2467,14 @@ dependencies = [
"num-integer",
"num-iter",
"num-traits",
"pem",
"rand 0.7.3",
"sha2",
"simple_asn1 0.4.1",
"pkcs1",
"pkcs8",
"rand 0.8.3",
"serde",
"subtle",
"thiserror",
"zeroize",
]
[[package]]
name = "rsa-export"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fce6de48e7cae950d65a62e67da01c1bb44576f0c3ea3e5749088cf0205a7263"
dependencies = [
"num-bigint-dig",
"pem",
"rsa",
"simple_asn1 0.5.3",
]
[[package]]
name = "rsix"
version = "0.23.2"
@@ -2757,7 +2631,7 @@ checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12"
dependencies = [
"block-buffer",
"cfg-if 1.0.0",
"cpufeatures",
"cpufeatures 0.1.4",
"digest",
"opaque-debug",
]
@@ -2800,35 +2674,12 @@ dependencies = [
[[package]]
name = "signature"
version = "1.2.2"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29f060a7d147e33490ec10da418795238fd7545bba241504d6b31a409f2e6210"
checksum = "c19772be3c4dd2ceaacf03cb41d5885f2a02c4d8804884918e3a258480803335"
dependencies = [
"digest",
"rand_core 0.5.1",
]
[[package]]
name = "simple_asn1"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692ca13de57ce0613a363c8c2f1de925adebc81b04c923ac60c5488bb44abe4b"
dependencies = [
"chrono",
"num-bigint 0.2.6",
"num-traits",
]
[[package]]
name = "simple_asn1"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc31e6cf34ad4321d3a2b8f934949b429e314519f753a77962f16c664dca8e13"
dependencies = [
"chrono",
"num-bigint 0.4.0",
"num-traits",
"thiserror",
"rand_core 0.6.3",
]
[[package]]
@@ -2852,6 +2703,15 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "spki"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32"
dependencies = [
"der",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
@@ -2900,15 +2760,6 @@ version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
[[package]]
name = "subtle-encoding"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dcb1ed7b8330c5eed5441052651dd7a12c75e2ed88f2ec024ae1fa3a5e59945"
dependencies = [
"zeroize",
]
[[package]]
name = "syn"
version = "1.0.72"
@@ -3045,16 +2896,6 @@ dependencies = [
"once_cell",
]
[[package]]
name = "time"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "tinytemplate"
version = "1.2.1"
@@ -3178,21 +3019,6 @@ dependencies = [
"tracing-serde",
]
[[package]]
name = "traitobject"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079"
[[package]]
name = "typemap"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "653be63c80a3296da5551e1bfd2cca35227e13cdd08c6668903ae2f4f77aa1f6"
dependencies = [
"unsafe-any",
]
[[package]]
name = "typenum"
version = "1.13.0"
@@ -3227,15 +3053,6 @@ dependencies = [
"subtle",
]
[[package]]
name = "unsafe-any"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f30360d7979f5e9c6e6cea48af192ea8fab4afb3cf72597154b8f08935bc9c7f"
dependencies = [
"traitobject",
]
[[package]]
name = "unsafe-io"
version = "0.9.1"
@@ -3371,7 +3188,7 @@ dependencies = [
[[package]]
name = "wasi-crypto"
version = "0.1.4"
version = "0.1.5"
dependencies = [
"aes-gcm",
"anyhow",
@@ -3388,8 +3205,8 @@ dependencies = [
"parking_lot",
"pqcrypto",
"rand_core 0.5.1",
"rand_core 0.6.3",
"rsa",
"rsa-export",
"serde",
"sha2",
"subtle",
@@ -3797,20 +3614,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "wasmtime-lightbeam"
version = "0.30.0"
dependencies = [
"anyhow",
"cranelift-codegen",
"gimli",
"lightbeam",
"object",
"target-lexicon",
"wasmparser",
"wasmtime-environ",
]
[[package]]
name = "wasmtime-runtime"
version = "0.30.0"
@@ -4047,12 +3850,6 @@ dependencies = [
"wast 35.0.2",
]
[[package]]
name = "wyz"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
[[package]]
name = "xoodyak"
version = "0.7.2"
@@ -4085,18 +3882,18 @@ dependencies = [
[[package]]
name = "zeroize"
version = "1.3.0"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd"
checksum = "bf68b08513768deaa790264a7fac27a58cbf2705cfcdc9448362229217d7e970"
dependencies = [
"zeroize_derive",
]
[[package]]
name = "zeroize_derive"
version = "1.1.0"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2c1e130bebaeab2f23886bf9acbaca14b092408c452543c857f66399cd6dab1"
checksum = "bdff2024a851a322b08f179173ae2ba620445aef1e838f0c196820eade4ae0c7"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -75,7 +75,6 @@ members = [
"cranelift",
"crates/bench-api",
"crates/c-api",
"crates/lightbeam/wasmtime",
"crates/misc/run-examples",
"examples/fib-debug/wasm",
"examples/wasi/wasm",
@@ -83,13 +82,12 @@ members = [
"fuzz",
]
exclude = [
'crates/wasi-common/WASI/tools/witx-cli',
'crates/wasi-common/WASI/tools/witx-cli',
'docs/rust_wasi_markdown_parser'
]
[features]
default = ["jitdump", "wasmtime/wat", "wasmtime/parallel-compilation", "wasi-nn"]
lightbeam = ["wasmtime/lightbeam"]
jitdump = ["wasmtime/jitdump"]
vtune = ["wasmtime/vtune"]
wasi-crypto = ["wasmtime-wasi-crypto"]
@@ -102,9 +100,6 @@ posix-signals-on-macos = ["wasmtime/posix-signals-on-macos"]
# backend is the default now.
experimental_x64 = []
# Use the old x86 backend.
old-x86-backend = ["wasmtime/old-x86-backend"]
[badges]
maintenance = { status = "actively-developed" }

View File

@@ -70,8 +70,8 @@ Hello, world!
quickly generate high-quality machine code at runtime.
* **Configurable**. Whether you need to precompile your wasm ahead of time,
generate code blazingly fast with Lightbeam, or interpret it at runtime,
Wasmtime has you covered for all your wasm-executing needs.
or interpret it at runtime, Wasmtime has you covered for all your
wasm-executing needs.
* **WASI**. Wasmtime supports a rich set of APIs for interacting with the host
environment through the [WASI standard](https://wasi.dev).

View File

@@ -4,6 +4,43 @@
## Unreleased
### Added
* New `Func::new_unchecked` and `Func::call_unchecked` APIs have been added with
accompanying functions in the C API to improve the performance of calls into
wasm and the host in the C API.
[#3350](https://github.com/bytecodealliance/wasmtime/pull/3350)
* Release binaries are now available for the s390x-unknown-linux-gnu
architecture.
[#3372](https://github.com/bytecodealliance/wasmtime/pull/3372)
### Changed
* The `Func::call` method now takes a slice to write the results into rather
than returning a boxed slice.
[#3319](https://github.com/bytecodealliance/wasmtime/pull/3319)
* Trampolines are now covered when jitdump profiling is enabled.
[#3344](https://github.com/bytecodealliance/wasmtime/pull/3344)
### Fixed
* Debugging with GDB has been fixed on Windows.
[#3373](https://github.com/bytecodealliance/wasmtime/pull/3373)
### Removed
* The Lightbeam backend has been removed, as per [RFC 14].
[#3390](https://github.com/bytecodealliance/wasmtime/pull/3390)
[RFC 14]: https://github.com/bytecodealliance/rfcs/pull/14
* Cranelift's old x86 backend has been removed, as per [RFC 12].
[#3309](https://github.com/bytecodealliance/wasmtime/pull/3009)
[RFC 12]: https://github.com/bytecodealliance/rfcs/pull/12
## 0.30.0
Released 2021-09-17.

View File

@@ -17,11 +17,7 @@ fn main() -> anyhow::Result<()> {
);
let mut out = String::new();
for strategy in &[
"Cranelift",
#[cfg(feature = "lightbeam")]
"Lightbeam",
] {
for strategy in &["Cranelift"] {
writeln!(out, "#[cfg(test)]")?;
writeln!(out, "#[allow(non_snake_case)]")?;
writeln!(out, "mod {} {{", strategy)?;
@@ -185,24 +181,12 @@ fn write_testsuite_tests(
/// Ignore tests that aren't supported yet.
fn ignore(testsuite: &str, testname: &str, strategy: &str) -> bool {
match strategy {
#[cfg(feature = "lightbeam")]
"Lightbeam" => match (testsuite, testname) {
("simd", _) => return true,
("multi_value", _) => return true,
("reference_types", _) => return true,
("bulk_memory_operations", _) => return true,
_ => (),
},
"Cranelift" => match (testsuite, testname) {
// Skip all reference types tests on the old backend. The modern
// implementation of reference types uses atomic instructions
// for reference counts on `externref`, but the old backend does not
// implement atomic instructions.
("reference_types", _) if cfg!(feature = "old-x86-backend") => return true,
// No simd support yet for s390x.
("simd", _) if platform_is_s390x() => return true,
// No memory64 support yet for s390x.
("memory64", _) if platform_is_s390x() => return true,
("memory64", "simd") if platform_is_s390x() => return true,
// No full atomics support yet for s390x.
("memory64", "threads") if platform_is_s390x() => return true,
_ => {}
},
_ => panic!("unrecognized strategy"),

View File

@@ -3,7 +3,6 @@
cargo test \
--features "test-programs/test_programs" \
--workspace \
--exclude '*lightbeam*' \
--exclude 'wasmtime-wasi-*' \
--exclude 'peepmatic*' \
--exclude wasi-crypto \

View File

@@ -1,34 +0,0 @@
#!/bin/bash
set -e
VERSION=${1:-3.7.3}
# Python 3.6 stands in our way -- nuking it
yum erase -y rh-python36
rm -rf /opt/rh/rh-python36
yum install -y gcc bzip2-devel libffi-devel zlib-devel
cd /usr/src/
# pip3.7 needs new openssl
curl -O -L https://github.com/openssl/openssl/archive/OpenSSL_1_1_1c.tar.gz
tar -zxvf OpenSSL_1_1_1c.tar.gz
cd openssl-OpenSSL_1_1_1c
./Configure shared zlib linux-x86_64
make -sj4
make install
cd ..
rm -rf openssl-OpenSSL_1_1_1c
# Fixing libssl.so.1.1: cannot open shared object file
echo "/usr/local/lib64" >> /etc/ld.so.conf && ldconfig
curl -O -L https://www.python.org/ftp/python/${VERSION}/Python-${VERSION}.tgz
tar xzf Python-${VERSION}.tgz
cd Python-${VERSION}
./configure
make -sj4
make install
cd ..
rm -rf Python-${VERSION}

View File

@@ -63,7 +63,6 @@ unwind = ["gimli"]
# If no ISA targets are explicitly enabled, the ISA target for the host machine is enabled.
x86 = []
arm64 = []
riscv = []
s390x = []
arm32 = [] # Work-in-progress codegen backend for ARM.
@@ -71,14 +70,10 @@ arm32 = [] # Work-in-progress codegen backend for ARM.
# backend is the default now.
experimental_x64 = []
# Make the old x86 backend the default.
old-x86-backend = []
# Option to enable all architectures.
all-arch = [
"x86",
"arm64",
"riscv",
"s390x"
]

View File

@@ -5,12 +5,9 @@
#[cfg(feature = "x86")]
mod x86 {
use cranelift_codegen::isa::x64::encoding::{
evex::{EvexContext, EvexInstruction, EvexMasking, EvexVectorLength, Register},
rex::OpcodeMap,
rex::{encode_modrm, LegacyPrefixes},
ByteSink,
evex::{EvexInstruction, EvexVectorLength, Register},
rex::{LegacyPrefixes, OpcodeMap},
};
use cranelift_codegen_shared::isa::x86::EncodingBits;
use criterion::{criterion_group, Criterion};
// Define the benchmarks.
@@ -34,26 +31,6 @@ mod x86 {
.encode(&mut sink);
});
});
group.bench_function("encode_evex (function pattern)", |b| {
let mut sink = vec![];
let bits = EncodingBits::new(&[0x66, 0x0f, 0x38, 0x1f], 0, 1);
let vvvvv = Register::from(0);
b.iter(|| {
sink.clear();
encode_evex(
bits,
rax,
vvvvv,
rdx,
EvexContext::Other {
length: EvexVectorLength::V128,
},
EvexMasking::default(),
&mut sink,
);
})
});
}
criterion_group!(benches, x64_evex_encoding_benchmarks);
@@ -65,66 +42,6 @@ mod x86 {
benches();
Criterion::default().configure_from_args().final_summary();
}
/// From the legacy x86 backend: a mechanism for encoding an EVEX
/// instruction, including the prefixes, the instruction opcode, and the
/// ModRM byte. This EVEX encoding function only encodes the `reg` (operand
/// 1), `vvvv` (operand 2), `rm` (operand 3) form; other forms are possible
/// (see section 2.6.2, Intel Software Development Manual, volume 2A),
/// requiring refactoring of this function or separate functions for each
/// form (e.g. as for the REX prefix).
#[inline(always)]
pub fn encode_evex<CS: ByteSink + ?Sized>(
enc: EncodingBits,
reg: Register,
vvvvv: Register,
rm: Register,
context: EvexContext,
masking: EvexMasking,
sink: &mut CS,
) {
let reg: u8 = reg.into();
let rm: u8 = rm.into();
let vvvvv: u8 = vvvvv.into();
// EVEX prefix.
sink.put1(0x62);
debug_assert!(enc.mm() < 0b100);
let mut p0 = enc.mm() & 0b11;
p0 |= evex2(rm, reg) << 4; // bits 3:2 are always unset
sink.put1(p0);
let mut p1 = enc.pp() | 0b100; // bit 2 is always set
p1 |= (!(vvvvv) & 0b1111) << 3;
p1 |= (enc.rex_w() & 0b1) << 7;
sink.put1(p1);
let mut p2 = masking.aaa_bits();
p2 |= (!(vvvvv >> 4) & 0b1) << 3;
p2 |= context.bits() << 4;
p2 |= masking.z_bit() << 7;
sink.put1(p2);
// Opcode.
sink.put1(enc.opcode_byte());
// ModR/M byte.
sink.put1(encode_modrm(3, reg & 7, rm & 7))
}
/// From the legacy x86 backend: encode the RXBR' bits of the EVEX P0 byte.
/// For an explanation of these bits, see section 2.6.1 in the Intel
/// Software Development Manual, volume 2A. These bits can be used by
/// different addressing modes (see section 2.6.2), requiring different
/// `vex*` functions than this one.
fn evex2(rm: u8, reg: u8) -> u8 {
let b = !(rm >> 3) & 1;
let x = !(rm >> 4) & 1;
let r = !(reg >> 3) & 1;
let r_ = !(reg >> 4) & 1;
0x00 | r_ | (b << 1) | (x << 2) | (r << 3)
}
}
fn main() {

View File

@@ -27,15 +27,6 @@ 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 new_backend_isas = if env::var("CARGO_FEATURE_X64").is_ok() {
// The x64 (new backend for x86_64) is a bit particular: it only requires generating
// the shared meta code; the only ISA-specific code is for settings.
vec![meta::isa::Isa::X86]
} else {
Vec::new()
};
// Configure isa targets using the old backend.
let isa_targets = meta::isa::Isa::all()
.iter()
.cloned()
@@ -45,7 +36,7 @@ fn main() {
})
.collect::<Vec<_>>();
let old_backend_isas = if new_backend_isas.is_empty() && isa_targets.is_empty() {
let isas = if isa_targets.is_empty() {
// Try to match native target.
let target_name = target_triple.split('-').next().unwrap();
let isa = meta::isa_from_arch(&target_name).expect("error when identifying target");
@@ -65,23 +56,14 @@ fn main() {
crate_dir.join("build.rs").to_str().unwrap()
);
if let Err(err) = meta::generate(&old_backend_isas, &new_backend_isas, &out_dir) {
if let Err(err) = meta::generate(&isas, &out_dir) {
eprintln!("Error: {}", err);
process::exit(1);
}
if env::var("CRANELIFT_VERBOSE").is_ok() {
for isa in &old_backend_isas {
println!(
"cargo:warning=Includes old-backend support for {} ISA",
isa.to_string()
);
}
for isa in &new_backend_isas {
println!(
"cargo:warning=Includes new-backend support for {} ISA",
isa.to_string()
);
for isa in &isas {
println!("cargo:warning=Includes support for {} ISA", isa.to_string());
}
println!(
"cargo:warning=Build step took {:?}.",

View File

@@ -1,755 +0,0 @@
use crate::cdsl::instructions::{InstSpec, Instruction, InstructionPredicate};
use crate::cdsl::operands::{OperandKind, OperandKindFields};
use crate::cdsl::types::ValueType;
use crate::cdsl::typevar::{TypeSetBuilder, TypeVar};
use cranelift_entity::{entity_impl, PrimaryMap, SparseMap, SparseMapValue};
use std::fmt;
use std::iter::IntoIterator;
pub(crate) enum Expr {
Var(VarIndex),
Literal(Literal),
}
impl Expr {
pub fn maybe_literal(&self) -> Option<&Literal> {
match &self {
Expr::Literal(lit) => Some(lit),
_ => None,
}
}
pub fn maybe_var(&self) -> Option<VarIndex> {
if let Expr::Var(var) = &self {
Some(*var)
} else {
None
}
}
pub fn unwrap_var(&self) -> VarIndex {
self.maybe_var()
.expect("tried to unwrap a non-Var content in Expr::unwrap_var")
}
pub fn to_rust_code(&self, var_pool: &VarPool) -> String {
match self {
Expr::Var(var_index) => var_pool.get(*var_index).to_rust_code(),
Expr::Literal(literal) => literal.to_rust_code(),
}
}
}
/// An AST definition associates a set of variables with the values produced by an expression.
pub(crate) struct Def {
pub apply: Apply,
pub defined_vars: Vec<VarIndex>,
}
impl Def {
pub fn to_comment_string(&self, var_pool: &VarPool) -> String {
let results = self
.defined_vars
.iter()
.map(|&x| var_pool.get(x).name.as_str())
.collect::<Vec<_>>();
let results = if results.len() == 1 {
results[0].to_string()
} else {
format!("({})", results.join(", "))
};
format!("{} := {}", results, self.apply.to_comment_string(var_pool))
}
}
pub(crate) struct DefPool {
pool: PrimaryMap<DefIndex, Def>,
}
impl DefPool {
pub fn new() -> Self {
Self {
pool: PrimaryMap::new(),
}
}
pub fn get(&self, index: DefIndex) -> &Def {
self.pool.get(index).unwrap()
}
pub fn next_index(&self) -> DefIndex {
self.pool.next_key()
}
pub fn create_inst(&mut self, apply: Apply, defined_vars: Vec<VarIndex>) -> DefIndex {
self.pool.push(Def {
apply,
defined_vars,
})
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub(crate) struct DefIndex(u32);
entity_impl!(DefIndex);
/// A definition which would lead to generate a block creation.
#[derive(Clone)]
pub(crate) struct Block {
/// Instruction index after which the block entry is set.
pub location: DefIndex,
/// Variable holding the new created block.
pub name: VarIndex,
}
pub(crate) struct BlockPool {
pool: SparseMap<DefIndex, Block>,
}
impl SparseMapValue<DefIndex> for Block {
fn key(&self) -> DefIndex {
self.location
}
}
impl BlockPool {
pub fn new() -> Self {
Self {
pool: SparseMap::new(),
}
}
pub fn get(&self, index: DefIndex) -> Option<&Block> {
self.pool.get(index)
}
pub fn create_block(&mut self, name: VarIndex, location: DefIndex) {
if self.pool.contains_key(location) {
panic!("Attempt to insert 2 blocks after the same instruction")
}
self.pool.insert(Block { location, name });
}
pub fn is_empty(&self) -> bool {
self.pool.is_empty()
}
}
// Implement IntoIterator such that we can iterate over blocks which are in the block pool.
impl<'a> IntoIterator for &'a BlockPool {
type Item = <&'a SparseMap<DefIndex, Block> as IntoIterator>::Item;
type IntoIter = <&'a SparseMap<DefIndex, Block> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.pool.into_iter()
}
}
#[derive(Clone, Debug)]
pub(crate) enum Literal {
/// A value of an enumerated immediate operand.
///
/// Some immediate operand kinds like `intcc` and `floatcc` have an enumerated range of values
/// corresponding to a Rust enum type. An `Enumerator` object is an AST leaf node representing one
/// of the values.
Enumerator {
rust_type: &'static str,
value: &'static str,
},
/// A bitwise value of an immediate operand, used for bitwise exact floating point constants.
Bits { rust_type: &'static str, value: u64 },
/// A value of an integer immediate operand.
Int(i64),
/// A empty list of variable set of arguments.
EmptyVarArgs,
}
impl Literal {
pub fn enumerator_for(kind: &OperandKind, value: &'static str) -> Self {
let value = match &kind.fields {
OperandKindFields::ImmEnum(values) => values.get(value).unwrap_or_else(|| {
panic!(
"nonexistent value '{}' in enumeration '{}'",
value, kind.rust_type
)
}),
_ => panic!("enumerator is for enum values"),
};
Literal::Enumerator {
rust_type: kind.rust_type,
value,
}
}
pub fn bits(kind: &OperandKind, bits: u64) -> Self {
match kind.fields {
OperandKindFields::ImmValue => {}
_ => panic!("bits_of is for immediate scalar types"),
}
Literal::Bits {
rust_type: kind.rust_type,
value: bits,
}
}
pub fn constant(kind: &OperandKind, value: i64) -> Self {
match kind.fields {
OperandKindFields::ImmValue => {}
_ => panic!("constant is for immediate scalar types"),
}
Literal::Int(value)
}
pub fn empty_vararg() -> Self {
Literal::EmptyVarArgs
}
pub fn to_rust_code(&self) -> String {
match self {
Literal::Enumerator { rust_type, value } => format!("{}::{}", rust_type, value),
Literal::Bits { rust_type, value } => format!("{}::with_bits({:#x})", rust_type, value),
Literal::Int(val) => val.to_string(),
Literal::EmptyVarArgs => "&[]".into(),
}
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum PatternPosition {
Source,
Destination,
}
/// 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 are defined only in the destination pattern.
pub(crate) struct Var {
pub name: String,
/// The `Def` defining this variable in a source pattern.
pub src_def: Option<DefIndex>,
/// The `Def` defining this variable in a destination pattern.
pub dst_def: Option<DefIndex>,
/// TypeVar representing the type of this variable.
type_var: Option<TypeVar>,
/// Is this the original type variable, or has it be redefined with set_typevar?
is_original_type_var: bool,
}
impl Var {
fn new(name: String) -> Self {
Self {
name,
src_def: None,
dst_def: None,
type_var: None,
is_original_type_var: false,
}
}
/// Is this an input value to the src pattern?
pub fn is_input(&self) -> bool {
self.src_def.is_none() && self.dst_def.is_none()
}
/// Is this an output value, defined in both src and dst patterns?
pub fn is_output(&self) -> bool {
self.src_def.is_some() && self.dst_def.is_some()
}
/// Is this an intermediate value, defined only in the src pattern?
pub fn is_intermediate(&self) -> bool {
self.src_def.is_some() && self.dst_def.is_none()
}
/// Is this a temp value, defined only in the dst pattern?
pub fn is_temp(&self) -> bool {
self.src_def.is_none() && self.dst_def.is_some()
}
/// Get the def of this variable according to the position.
pub fn get_def(&self, position: PatternPosition) -> Option<DefIndex> {
match position {
PatternPosition::Source => self.src_def,
PatternPosition::Destination => self.dst_def,
}
}
pub fn set_def(&mut self, position: PatternPosition, def: DefIndex) {
assert!(
self.get_def(position).is_none(),
"redefinition of variable {}",
self.name
);
match position {
PatternPosition::Source => {
self.src_def = Some(def);
}
PatternPosition::Destination => {
self.dst_def = Some(def);
}
}
}
/// Get the type variable representing the type of this variable.
pub fn get_or_create_typevar(&mut self) -> TypeVar {
match &self.type_var {
Some(tv) => tv.clone(),
None => {
// Create a new type var in which we allow all types.
let tv = TypeVar::new(
format!("typeof_{}", self.name),
format!("Type of the pattern variable {:?}", self),
TypeSetBuilder::all(),
);
self.type_var = Some(tv.clone());
self.is_original_type_var = true;
tv
}
}
}
pub fn get_typevar(&self) -> Option<TypeVar> {
self.type_var.clone()
}
pub fn set_typevar(&mut self, tv: TypeVar) {
self.is_original_type_var = if let Some(previous_tv) = &self.type_var {
*previous_tv == tv
} else {
false
};
self.type_var = Some(tv);
}
/// Check if this variable has a free type variable. If not, the type of this variable is
/// computed from the type of another variable.
pub fn has_free_typevar(&self) -> bool {
match &self.type_var {
Some(tv) => tv.base.is_none() && self.is_original_type_var,
None => false,
}
}
pub fn to_rust_code(&self) -> String {
self.name.clone()
}
fn rust_type(&self) -> String {
self.type_var.as_ref().unwrap().to_rust_code()
}
}
impl fmt::Debug for Var {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
fmt.write_fmt(format_args!(
"Var({}{}{})",
self.name,
if self.src_def.is_some() { ", src" } else { "" },
if self.dst_def.is_some() { ", dst" } else { "" }
))
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub(crate) struct VarIndex(u32);
entity_impl!(VarIndex);
pub(crate) struct VarPool {
pool: PrimaryMap<VarIndex, Var>,
}
impl VarPool {
pub fn new() -> Self {
Self {
pool: PrimaryMap::new(),
}
}
pub fn get(&self, index: VarIndex) -> &Var {
self.pool.get(index).unwrap()
}
pub fn get_mut(&mut self, index: VarIndex) -> &mut Var {
self.pool.get_mut(index).unwrap()
}
pub fn create(&mut self, name: impl Into<String>) -> VarIndex {
self.pool.push(Var::new(name.into()))
}
}
/// Contains constants created in the AST that must be inserted into the true [ConstantPool] when
/// the legalizer code is generated. The constant data is named in the order it is inserted;
/// inserting data using [insert] will avoid duplicates.
///
/// [ConstantPool]: ../../../cranelift_codegen/ir/constant/struct.ConstantPool.html
/// [insert]: ConstPool::insert
pub(crate) struct ConstPool {
pool: Vec<Vec<u8>>,
}
impl ConstPool {
/// Create an empty constant pool.
pub fn new() -> Self {
Self { pool: vec![] }
}
/// Create a name for a constant from its position in the pool.
fn create_name(position: usize) -> String {
format!("const{}", position)
}
/// Insert constant data into the pool, returning the name of the variable used to reference it.
/// This method will search for data that matches the new data and return the existing constant
/// name to avoid duplicates.
pub fn insert(&mut self, data: Vec<u8>) -> String {
let possible_position = self.pool.iter().position(|d| d == &data);
let position = if let Some(found_position) = possible_position {
found_position
} else {
let new_position = self.pool.len();
self.pool.push(data);
new_position
};
ConstPool::create_name(position)
}
/// Iterate over the name/value pairs in the pool.
pub fn iter(&self) -> impl Iterator<Item = (String, &Vec<u8>)> {
self.pool
.iter()
.enumerate()
.map(|(i, v)| (ConstPool::create_name(i), v))
}
}
/// 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.
pub(crate) struct Apply {
pub inst: Instruction,
pub args: Vec<Expr>,
pub value_types: Vec<ValueType>,
}
impl Apply {
pub fn new(target: InstSpec, args: Vec<Expr>) -> Self {
let (inst, value_types) = match target {
InstSpec::Inst(inst) => (inst, Vec::new()),
InstSpec::Bound(bound_inst) => (bound_inst.inst, bound_inst.value_types),
};
// Apply should only operate on concrete value types, not "any".
let value_types = value_types
.into_iter()
.map(|vt| vt.expect("shouldn't be Any"))
.collect();
// Basic check on number of arguments.
assert!(
inst.operands_in.len() == args.len(),
"incorrect number of arguments in instruction {}",
inst.name
);
// Check that the kinds of Literals arguments match the expected operand.
for &imm_index in &inst.imm_opnums {
let arg = &args[imm_index];
if let Some(literal) = arg.maybe_literal() {
let op = &inst.operands_in[imm_index];
match &op.kind.fields {
OperandKindFields::ImmEnum(values) => {
if let Literal::Enumerator { value, .. } = literal {
assert!(
values.iter().any(|(_key, v)| v == value),
"Nonexistent enum value '{}' passed to field of kind '{}' -- \
did you use the right enum?",
value,
op.kind.rust_type
);
} else {
panic!(
"Passed non-enum field value {:?} to field of kind {}",
literal, op.kind.rust_type
);
}
}
OperandKindFields::ImmValue => match &literal {
Literal::Enumerator { value, .. } => panic!(
"Expected immediate value in immediate field of kind '{}', \
obtained enum value '{}'",
op.kind.rust_type, value
),
Literal::Bits { .. } | Literal::Int(_) | Literal::EmptyVarArgs => {}
},
_ => {
panic!(
"Literal passed to non-literal field of kind {}",
op.kind.rust_type
);
}
}
}
}
Self {
inst,
args,
value_types,
}
}
fn to_comment_string(&self, var_pool: &VarPool) -> String {
let args = self
.args
.iter()
.map(|arg| arg.to_rust_code(var_pool))
.collect::<Vec<_>>()
.join(", ");
let mut inst_and_bound_types = vec![self.inst.name.to_string()];
inst_and_bound_types.extend(self.value_types.iter().map(|vt| vt.to_string()));
let inst_name = inst_and_bound_types.join(".");
format!("{}({})", inst_name, args)
}
pub fn inst_predicate(&self, var_pool: &VarPool) -> InstructionPredicate {
let mut pred = InstructionPredicate::new();
for (format_field, &op_num) in self
.inst
.format
.imm_fields
.iter()
.zip(self.inst.imm_opnums.iter())
{
let arg = &self.args[op_num];
if arg.maybe_var().is_some() {
// Ignore free variables for now.
continue;
}
pred = pred.and(InstructionPredicate::new_is_field_equal_ast(
&*self.inst.format,
format_field,
arg.to_rust_code(var_pool),
));
}
// Add checks for any bound secondary type variables. We can't check the controlling type
// variable this way since it may not appear as the type of an operand.
if self.value_types.len() > 1 {
let poly = self
.inst
.polymorphic_info
.as_ref()
.expect("must have polymorphic info if it has bounded types");
for (bound_type, type_var) in
self.value_types[1..].iter().zip(poly.other_typevars.iter())
{
pred = pred.and(InstructionPredicate::new_typevar_check(
&self.inst, type_var, bound_type,
));
}
}
pred
}
/// Same as `inst_predicate()`, but also check the controlling type variable.
pub fn inst_predicate_with_ctrl_typevar(&self, var_pool: &VarPool) -> InstructionPredicate {
let mut pred = self.inst_predicate(var_pool);
if !self.value_types.is_empty() {
let bound_type = &self.value_types[0];
let poly = self.inst.polymorphic_info.as_ref().unwrap();
let type_check = if poly.use_typevar_operand {
InstructionPredicate::new_typevar_check(&self.inst, &poly.ctrl_typevar, bound_type)
} else {
InstructionPredicate::new_ctrl_typevar_check(&bound_type)
};
pred = pred.and(type_check);
}
pred
}
pub fn rust_builder(&self, defined_vars: &[VarIndex], var_pool: &VarPool) -> String {
let mut args = self
.args
.iter()
.map(|expr| expr.to_rust_code(var_pool))
.collect::<Vec<_>>()
.join(", ");
// Do we need to pass an explicit type argument?
if let Some(poly) = &self.inst.polymorphic_info {
if !poly.use_typevar_operand {
args = format!("{}, {}", var_pool.get(defined_vars[0]).rust_type(), args);
}
}
format!("{}({})", self.inst.snake_name(), args)
}
}
// Simple helpers for legalize actions construction.
pub(crate) enum DummyExpr {
Var(DummyVar),
Literal(Literal),
Constant(DummyConstant),
Apply(InstSpec, Vec<DummyExpr>),
Block(DummyVar),
}
#[derive(Clone)]
pub(crate) struct DummyVar {
pub name: String,
}
impl Into<DummyExpr> for DummyVar {
fn into(self) -> DummyExpr {
DummyExpr::Var(self)
}
}
impl Into<DummyExpr> for Literal {
fn into(self) -> DummyExpr {
DummyExpr::Literal(self)
}
}
#[derive(Clone)]
pub(crate) struct DummyConstant(pub(crate) Vec<u8>);
pub(crate) fn constant(data: Vec<u8>) -> DummyConstant {
DummyConstant(data)
}
impl Into<DummyExpr> for DummyConstant {
fn into(self) -> DummyExpr {
DummyExpr::Constant(self)
}
}
pub(crate) fn var(name: &str) -> DummyVar {
DummyVar {
name: name.to_owned(),
}
}
pub(crate) struct DummyDef {
pub expr: DummyExpr,
pub defined_vars: Vec<DummyVar>,
}
pub(crate) struct ExprBuilder {
expr: DummyExpr,
}
impl ExprBuilder {
pub fn apply(inst: InstSpec, args: Vec<DummyExpr>) -> Self {
let expr = DummyExpr::Apply(inst, args);
Self { expr }
}
pub fn assign_to(self, defined_vars: Vec<DummyVar>) -> DummyDef {
DummyDef {
expr: self.expr,
defined_vars,
}
}
pub fn block(name: DummyVar) -> Self {
let expr = DummyExpr::Block(name);
Self { expr }
}
}
macro_rules! def_rhs {
// inst(a, b, c)
($inst:ident($($src:expr),*)) => {
ExprBuilder::apply($inst.into(), vec![$($src.clone().into()),*])
};
// inst.type(a, b, c)
($inst:ident.$type:ident($($src:expr),*)) => {
ExprBuilder::apply($inst.bind($type).into(), vec![$($src.clone().into()),*])
};
}
// Helper macro to define legalization recipes.
macro_rules! def {
// x = ...
($dest:ident = $($tt:tt)*) => {
def_rhs!($($tt)*).assign_to(vec![$dest.clone()])
};
// (x, y, ...) = ...
(($($dest:ident),*) = $($tt:tt)*) => {
def_rhs!($($tt)*).assign_to(vec![$($dest.clone()),*])
};
// An instruction with no results.
($($tt:tt)*) => {
def_rhs!($($tt)*).assign_to(Vec::new())
}
}
// Helper macro to define legalization recipes.
macro_rules! block {
// a basic block definition, splitting the current block in 2.
($block: ident) => {
ExprBuilder::block($block).assign_to(Vec::new())
};
}
#[cfg(test)]
mod tests {
use crate::cdsl::ast::ConstPool;
#[test]
fn const_pool_returns_var_names() {
let mut c = ConstPool::new();
assert_eq!(c.insert([0, 1, 2].to_vec()), "const0");
assert_eq!(c.insert([1, 2, 3].to_vec()), "const1");
}
#[test]
fn const_pool_avoids_duplicates() {
let data = [0, 1, 2].to_vec();
let mut c = ConstPool::new();
assert_eq!(c.pool.len(), 0);
assert_eq!(c.insert(data.clone()), "const0");
assert_eq!(c.pool.len(), 1);
assert_eq!(c.insert(data), "const0");
assert_eq!(c.pool.len(), 1);
}
#[test]
fn const_pool_iterates() {
let mut c = ConstPool::new();
c.insert([0, 1, 2].to_vec());
c.insert([3, 4, 5].to_vec());
let mut iter = c.iter();
assert_eq!(iter.next(), Some(("const0".to_owned(), &vec![0, 1, 2])));
assert_eq!(iter.next(), Some(("const1".to_owned(), &vec![3, 4, 5])));
assert_eq!(iter.next(), None);
}
}

View File

@@ -1,88 +0,0 @@
use std::collections::{hash_map, HashMap, HashSet};
use std::iter::FromIterator;
use crate::cdsl::encodings::Encoding;
use crate::cdsl::types::{LaneType, ValueType};
use crate::cdsl::xform::{TransformGroup, TransformGroupIndex};
pub(crate) struct CpuMode {
pub name: &'static str,
default_legalize: Option<TransformGroupIndex>,
monomorphic_legalize: Option<TransformGroupIndex>,
typed_legalize: HashMap<ValueType, TransformGroupIndex>,
pub encodings: Vec<Encoding>,
}
impl CpuMode {
pub fn new(name: &'static str) -> Self {
Self {
name,
default_legalize: None,
monomorphic_legalize: None,
typed_legalize: HashMap::new(),
encodings: Vec::new(),
}
}
pub fn set_encodings(&mut self, encodings: Vec<Encoding>) {
assert!(self.encodings.is_empty(), "clobbering encodings");
self.encodings = encodings;
}
pub fn legalize_monomorphic(&mut self, group: &TransformGroup) {
assert!(self.monomorphic_legalize.is_none());
self.monomorphic_legalize = Some(group.id);
}
pub fn legalize_default(&mut self, group: &TransformGroup) {
assert!(self.default_legalize.is_none());
self.default_legalize = Some(group.id);
}
pub fn legalize_value_type(&mut self, lane_type: impl Into<ValueType>, group: &TransformGroup) {
assert!(self
.typed_legalize
.insert(lane_type.into(), group.id)
.is_none());
}
pub fn legalize_type(&mut self, lane_type: impl Into<LaneType>, group: &TransformGroup) {
assert!(self
.typed_legalize
.insert(lane_type.into().into(), group.id)
.is_none());
}
pub fn get_default_legalize_code(&self) -> TransformGroupIndex {
self.default_legalize
.expect("a finished CpuMode must have a default legalize code")
}
pub fn get_legalize_code_for(&self, typ: &Option<ValueType>) -> TransformGroupIndex {
match typ {
Some(typ) => self
.typed_legalize
.get(typ)
.copied()
.unwrap_or_else(|| self.get_default_legalize_code()),
None => self
.monomorphic_legalize
.unwrap_or_else(|| self.get_default_legalize_code()),
}
}
pub fn get_legalized_types(&self) -> hash_map::Keys<ValueType, TransformGroupIndex> {
self.typed_legalize.keys()
}
/// Returns a deterministically ordered, deduplicated list of TransformGroupIndex for the directly
/// reachable set of TransformGroup this TargetIsa uses.
pub fn direct_transform_groups(&self) -> Vec<TransformGroupIndex> {
let mut set = HashSet::new();
if let Some(i) = &self.default_legalize {
set.insert(*i);
}
if let Some(i) = &self.monomorphic_legalize {
set.insert(*i);
}
set.extend(self.typed_legalize.values().cloned());
let mut ret = Vec::from_iter(set);
ret.sort();
ret
}
}

View File

@@ -1,178 +0,0 @@
use crate::cdsl::instructions::{
InstSpec, Instruction, InstructionPredicate, InstructionPredicateNode,
InstructionPredicateNumber, InstructionPredicateRegistry, ValueTypeOrAny,
};
use crate::cdsl::recipes::{EncodingRecipeNumber, Recipes};
use crate::cdsl::settings::SettingPredicateNumber;
use crate::cdsl::types::ValueType;
use std::rc::Rc;
use std::string::ToString;
/// Encoding for a concrete instruction.
///
/// An `Encoding` object ties an instruction opcode with concrete type variables together with an
/// encoding recipe and encoding encbits.
///
/// 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.
pub(crate) struct EncodingContent {
/// The `Instruction` or `BoundInstruction` being encoded.
inst: InstSpec,
/// The `EncodingRecipe` to use.
pub recipe: EncodingRecipeNumber,
/// Additional encoding bits to be interpreted by `recipe`.
pub encbits: u16,
/// An instruction predicate that must be true to allow selecting this encoding.
pub inst_predicate: Option<InstructionPredicateNumber>,
/// An ISA predicate that must be true to allow selecting this encoding.
pub isa_predicate: Option<SettingPredicateNumber>,
/// The value type this encoding has been bound to, for encodings of polymorphic instructions.
pub bound_type: Option<ValueType>,
}
impl EncodingContent {
pub fn inst(&self) -> &Instruction {
self.inst.inst()
}
pub fn to_rust_comment(&self, recipes: &Recipes) -> String {
format!("[{}#{:02x}]", recipes[self.recipe].name, self.encbits)
}
}
pub(crate) type Encoding = Rc<EncodingContent>;
pub(crate) struct EncodingBuilder {
inst: InstSpec,
recipe: EncodingRecipeNumber,
encbits: u16,
inst_predicate: Option<InstructionPredicate>,
isa_predicate: Option<SettingPredicateNumber>,
bound_type: Option<ValueType>,
}
impl EncodingBuilder {
pub fn new(inst: InstSpec, recipe: EncodingRecipeNumber, encbits: u16) -> Self {
let (inst_predicate, bound_type) = match &inst {
InstSpec::Bound(inst) => {
let other_typevars = &inst.inst.polymorphic_info.as_ref().unwrap().other_typevars;
assert_eq!(
inst.value_types.len(),
other_typevars.len() + 1,
"partially bound polymorphic instruction"
);
// Add secondary type variables to the instruction predicate.
let value_types = &inst.value_types;
let mut inst_predicate: Option<InstructionPredicate> = None;
for (typevar, value_type) in other_typevars.iter().zip(value_types.iter().skip(1)) {
let value_type = match value_type {
ValueTypeOrAny::Any => continue,
ValueTypeOrAny::ValueType(vt) => vt,
};
let type_predicate =
InstructionPredicate::new_typevar_check(&inst.inst, typevar, value_type);
inst_predicate = Some(type_predicate.into());
}
// Add immediate value predicates
for (immediate_value, immediate_operand) in inst
.immediate_values
.iter()
.zip(inst.inst.operands_in.iter().filter(|o| o.is_immediate()))
{
let immediate_predicate = InstructionPredicate::new_is_field_equal(
&inst.inst.format,
immediate_operand.kind.rust_field_name,
immediate_value.to_string(),
);
inst_predicate = if let Some(type_predicate) = inst_predicate {
Some(type_predicate.and(immediate_predicate))
} else {
Some(immediate_predicate.into())
}
}
let ctrl_type = value_types[0]
.clone()
.expect("Controlling type shouldn't be Any");
(inst_predicate, Some(ctrl_type))
}
InstSpec::Inst(inst) => {
assert!(
inst.polymorphic_info.is_none(),
"unbound polymorphic instruction"
);
(None, None)
}
};
Self {
inst,
recipe,
encbits,
inst_predicate,
isa_predicate: None,
bound_type,
}
}
pub fn inst_predicate(mut self, inst_predicate: InstructionPredicateNode) -> Self {
let inst_predicate = Some(match self.inst_predicate {
Some(node) => node.and(inst_predicate),
None => inst_predicate.into(),
});
self.inst_predicate = inst_predicate;
self
}
pub fn isa_predicate(mut self, isa_predicate: SettingPredicateNumber) -> Self {
assert!(self.isa_predicate.is_none());
self.isa_predicate = Some(isa_predicate);
self
}
pub fn build(
self,
recipes: &Recipes,
inst_pred_reg: &mut InstructionPredicateRegistry,
) -> Encoding {
let inst_predicate = self.inst_predicate.map(|pred| inst_pred_reg.insert(pred));
let inst = self.inst.inst();
assert!(
Rc::ptr_eq(&inst.format, &recipes[self.recipe].format),
"Inst {} and recipe {} must have the same format!",
inst.name,
recipes[self.recipe].name
);
assert_eq!(
inst.is_branch && !inst.is_indirect_branch,
recipes[self.recipe].branch_range.is_some(),
"Inst {}'s is_branch contradicts recipe {} branch_range!",
inst.name,
recipes[self.recipe].name
);
Rc::new(EncodingContent {
inst: self.inst,
recipe: self.recipe,
encbits: self.encbits,
inst_predicate,
isa_predicate: self.isa_predicate,
bound_type: self.bound_type,
})
}
}

View File

@@ -70,18 +70,6 @@ impl fmt::Display for InstructionFormat {
}
impl InstructionFormat {
pub fn imm_by_name(&self, name: &'static str) -> &FormatField {
self.imm_fields
.iter()
.find(|&field| field.member == name)
.unwrap_or_else(|| {
panic!(
"unexpected immediate field named {} in instruction format {}",
name, self.name
)
})
}
/// Returns a tuple that uniquely identifies the structure.
pub fn structure(&self) -> FormatStructure {
FormatStructure {

View File

@@ -1,21 +1,14 @@
use cranelift_codegen_shared::condcodes::IntCC;
use cranelift_entity::{entity_impl, PrimaryMap};
use std::collections::HashMap;
use std::fmt;
use std::fmt::{Display, Error, Formatter};
use std::rc::Rc;
use crate::cdsl::camel_case;
use crate::cdsl::formats::{FormatField, InstructionFormat};
use crate::cdsl::formats::InstructionFormat;
use crate::cdsl::operands::Operand;
use crate::cdsl::type_inference::Constraint;
use crate::cdsl::types::{LaneType, ReferenceType, ValueType, VectorType};
use crate::cdsl::typevar::TypeVar;
use crate::shared::formats::Formats;
use crate::shared::types::{Bool, Float, Int, Reference};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub(crate) struct OpcodeNumber(u32);
entity_impl!(OpcodeNumber);
@@ -24,61 +17,24 @@ pub(crate) type AllInstructions = PrimaryMap<OpcodeNumber, Instruction>;
pub(crate) struct InstructionGroupBuilder<'all_inst> {
all_instructions: &'all_inst mut AllInstructions,
own_instructions: Vec<Instruction>,
}
impl<'all_inst> InstructionGroupBuilder<'all_inst> {
pub fn new(all_instructions: &'all_inst mut AllInstructions) -> Self {
Self {
all_instructions,
own_instructions: Vec::new(),
}
Self { all_instructions }
}
pub fn push(&mut self, builder: InstructionBuilder) {
let opcode_number = OpcodeNumber(self.all_instructions.next_key().as_u32());
let inst = builder.build(opcode_number);
// Note this clone is cheap, since Instruction is a Rc<> wrapper for InstructionContent.
self.own_instructions.push(inst.clone());
self.all_instructions.push(inst);
}
pub fn build(self) -> InstructionGroup {
InstructionGroup {
instructions: self.own_instructions,
}
}
}
/// 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.
pub(crate) struct InstructionGroup {
instructions: Vec<Instruction>,
}
impl InstructionGroup {
pub fn by_name(&self, name: &'static str) -> &Instruction {
self.instructions
.iter()
.find(|inst| inst.name == name)
.unwrap_or_else(|| panic!("instruction with name '{}' does not exist", name))
}
}
/// Instructions can have parameters bound to them to specialize them for more specific encodings
/// (e.g. the encoding for adding two float types may be different than that of adding two
/// integer types)
pub(crate) trait Bindable {
/// Bind a parameter to an instruction
fn bind(&self, parameter: impl Into<BindParameter>) -> BoundInstruction;
}
#[derive(Debug)]
pub(crate) struct PolymorphicInfo {
pub use_typevar_operand: bool,
pub ctrl_typevar: TypeVar,
pub other_typevars: Vec<TypeVar>,
}
#[derive(Debug)]
@@ -95,8 +51,6 @@ pub(crate) struct InstructionContent {
pub operands_in: Vec<Operand>,
/// Output operands. The output operands must be SSA values or `variable_args`.
pub operands_out: Vec<Operand>,
/// Instruction-specific TypeConstraints.
pub constraints: Vec<Constraint>,
/// Instruction format, automatically derived from the input operands.
pub format: Rc<InstructionFormat>,
@@ -146,27 +100,10 @@ impl InstructionContent {
&self.name
}
}
pub fn all_typevars(&self) -> Vec<&TypeVar> {
match &self.polymorphic_info {
Some(poly) => {
let mut result = vec![&poly.ctrl_typevar];
result.extend(&poly.other_typevars);
result
}
None => Vec::new(),
}
}
}
pub(crate) type Instruction = Rc<InstructionContent>;
impl Bindable for Instruction {
fn bind(&self, parameter: impl Into<BindParameter>) -> BoundInstruction {
BoundInstruction::new(self).bind(parameter)
}
}
impl fmt::Display for InstructionContent {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
if !self.operands_out.is_empty() {
@@ -317,11 +254,6 @@ impl InstructionBuilder {
self
}
pub fn clobbers_all_regs(mut self, val: bool) -> Self {
self.clobbers_all_regs = val;
self
}
fn build(self, opcode_number: OpcodeNumber) -> Instruction {
let operands_in = self.operands_in.unwrap_or_else(Vec::new);
let operands_out = self.operands_out.unwrap_or_else(Vec::new);
@@ -361,7 +293,6 @@ impl InstructionBuilder {
doc: self.doc,
operands_in,
operands_out,
constraints: self.constraints.unwrap_or_else(Vec::new),
format: self.format,
polymorphic_info,
value_opnums,
@@ -383,181 +314,6 @@ impl InstructionBuilder {
}
}
/// A thin wrapper like Option<ValueType>, but with more precise semantics.
#[derive(Clone)]
pub(crate) enum ValueTypeOrAny {
ValueType(ValueType),
Any,
}
impl ValueTypeOrAny {
pub fn expect(self, msg: &str) -> ValueType {
match self {
ValueTypeOrAny::ValueType(vt) => vt,
ValueTypeOrAny::Any => panic!("Unexpected Any: {}", msg),
}
}
}
/// The number of bits in the vector
type VectorBitWidth = u64;
/// An parameter used for binding instructions to specific types or values
pub(crate) enum BindParameter {
Any,
Lane(LaneType),
Vector(LaneType, VectorBitWidth),
Reference(ReferenceType),
Immediate(Immediate),
}
/// Constructor for more easily building vector parameters from any lane type
pub(crate) fn vector(parameter: impl Into<LaneType>, vector_size: VectorBitWidth) -> BindParameter {
BindParameter::Vector(parameter.into(), vector_size)
}
impl From<Int> for BindParameter {
fn from(ty: Int) -> Self {
BindParameter::Lane(ty.into())
}
}
impl From<Bool> for BindParameter {
fn from(ty: Bool) -> Self {
BindParameter::Lane(ty.into())
}
}
impl From<Float> for BindParameter {
fn from(ty: Float) -> Self {
BindParameter::Lane(ty.into())
}
}
impl From<LaneType> for BindParameter {
fn from(ty: LaneType) -> Self {
BindParameter::Lane(ty)
}
}
impl From<Reference> for BindParameter {
fn from(ty: Reference) -> Self {
BindParameter::Reference(ty.into())
}
}
impl From<Immediate> for BindParameter {
fn from(imm: Immediate) -> Self {
BindParameter::Immediate(imm)
}
}
#[derive(Clone)]
pub(crate) enum Immediate {
// When needed, this enum should be expanded to include other immediate types (e.g. u8, u128).
IntCC(IntCC),
}
impl Display for Immediate {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
match self {
Immediate::IntCC(x) => write!(f, "IntCC::{:?}", x),
}
}
}
#[derive(Clone)]
pub(crate) struct BoundInstruction {
pub inst: Instruction,
pub value_types: Vec<ValueTypeOrAny>,
pub immediate_values: Vec<Immediate>,
}
impl BoundInstruction {
/// Construct a new bound instruction (with nothing bound yet) from an instruction
fn new(inst: &Instruction) -> Self {
BoundInstruction {
inst: inst.clone(),
value_types: vec![],
immediate_values: vec![],
}
}
/// Verify that the bindings for a BoundInstruction are correct.
fn verify_bindings(&self) -> Result<(), String> {
// Verify that binding types to the instruction does not violate the polymorphic rules.
if !self.value_types.is_empty() {
match &self.inst.polymorphic_info {
Some(poly) => {
if self.value_types.len() > 1 + poly.other_typevars.len() {
return Err(format!(
"trying to bind too many types for {}",
self.inst.name
));
}
}
None => {
return Err(format!(
"trying to bind a type for {} which is not a polymorphic instruction",
self.inst.name
));
}
}
}
// Verify that only the right number of immediates are bound.
let immediate_count = self
.inst
.operands_in
.iter()
.filter(|o| o.is_immediate_or_entityref())
.count();
if self.immediate_values.len() > immediate_count {
return Err(format!(
"trying to bind too many immediates ({}) to instruction {} which only expects {} \
immediates",
self.immediate_values.len(),
self.inst.name,
immediate_count
));
}
Ok(())
}
}
impl Bindable for BoundInstruction {
fn bind(&self, parameter: impl Into<BindParameter>) -> BoundInstruction {
let mut modified = self.clone();
match parameter.into() {
BindParameter::Any => modified.value_types.push(ValueTypeOrAny::Any),
BindParameter::Lane(lane_type) => modified
.value_types
.push(ValueTypeOrAny::ValueType(lane_type.into())),
BindParameter::Vector(lane_type, vector_size_in_bits) => {
let num_lanes = vector_size_in_bits / lane_type.lane_bits();
assert!(
num_lanes >= 2,
"Minimum lane number for bind_vector is 2, found {}.",
num_lanes,
);
let vector_type = ValueType::Vector(VectorType::new(lane_type, num_lanes));
modified
.value_types
.push(ValueTypeOrAny::ValueType(vector_type));
}
BindParameter::Reference(reference_type) => {
modified
.value_types
.push(ValueTypeOrAny::ValueType(reference_type.into()));
}
BindParameter::Immediate(immediate) => modified.immediate_values.push(immediate),
}
modified.verify_bindings().unwrap();
modified
}
}
/// Checks that the input operands actually match the given format.
fn verify_format(inst_name: &str, operands_in: &[Operand], format: &InstructionFormat) {
// A format is defined by:
@@ -644,11 +400,10 @@ fn verify_polymorphic(
|| tv.singleton_type().is_some()
{
match is_ctrl_typevar_candidate(tv, &operands_in, &operands_out) {
Ok(other_typevars) => {
Ok(_other_typevars) => {
return Some(PolymorphicInfo {
use_typevar_operand: true,
ctrl_typevar: tv.clone(),
other_typevars,
});
}
Err(error_message) => {
@@ -679,12 +434,11 @@ fn verify_polymorphic(
// At this point, if the next unwrap() fails, it means the output type couldn't be used as a
// controlling type variable either; panicking is the right behavior.
let other_typevars = is_ctrl_typevar_candidate(tv, &operands_in, &operands_out).unwrap();
is_ctrl_typevar_candidate(tv, &operands_in, &operands_out).unwrap();
Some(PolymorphicInfo {
use_typevar_operand: false,
ctrl_typevar: tv.clone(),
other_typevars,
})
}
@@ -762,634 +516,3 @@ fn is_ctrl_typevar_candidate(
Ok(other_typevars)
}
#[derive(Clone, Hash, PartialEq, Eq)]
pub(crate) enum FormatPredicateKind {
/// Is the field member equal to the expected value (stored here)?
IsEqual(String),
/// Is the immediate instruction format field representable as an n-bit two's complement
/// integer? (with width: first member, scale: second member).
/// The predicate is true if the field is in the range: `-2^(width-1) -- 2^(width-1)-1` and a
/// multiple of `2^scale`.
IsSignedInt(usize, usize),
/// Is the immediate instruction format field representable as an n-bit unsigned integer? (with
/// width: first member, scale: second member).
/// The predicate is true if the field is in the range: `0 -- 2^width - 1` and a multiple of
/// `2^scale`.
IsUnsignedInt(usize, usize),
/// Is the immediate format field member an integer equal to zero?
IsZeroInt,
/// Is the immediate format field member equal to zero? (float32 version)
IsZero32BitFloat,
/// Is the immediate format field member equal to zero? (float64 version)
IsZero64BitFloat,
/// Is the immediate format field member equal zero in all lanes?
IsAllZeroes,
/// Does the immediate format field member have ones in all bits of all lanes?
IsAllOnes,
/// Has the value list (in member_name) the size specified in parameter?
LengthEquals(usize),
/// Is the referenced function colocated?
IsColocatedFunc,
/// Is the referenced data object colocated?
IsColocatedData,
}
#[derive(Clone, Hash, PartialEq, Eq)]
pub(crate) struct FormatPredicateNode {
format_name: &'static str,
member_name: &'static str,
kind: FormatPredicateKind,
}
impl FormatPredicateNode {
fn new(
format: &InstructionFormat,
field_name: &'static str,
kind: FormatPredicateKind,
) -> Self {
let member_name = format.imm_by_name(field_name).member;
Self {
format_name: format.name,
member_name,
kind,
}
}
fn new_raw(
format: &InstructionFormat,
member_name: &'static str,
kind: FormatPredicateKind,
) -> Self {
Self {
format_name: format.name,
member_name,
kind,
}
}
fn destructuring_member_name(&self) -> &'static str {
match &self.kind {
FormatPredicateKind::LengthEquals(_) => {
// Length operates on the argument value list.
assert!(self.member_name == "args");
"ref args"
}
_ => self.member_name,
}
}
fn rust_predicate(&self) -> String {
match &self.kind {
FormatPredicateKind::IsEqual(arg) => {
format!("predicates::is_equal({}, {})", self.member_name, arg)
}
FormatPredicateKind::IsSignedInt(width, scale) => format!(
"predicates::is_signed_int({}, {}, {})",
self.member_name, width, scale
),
FormatPredicateKind::IsUnsignedInt(width, scale) => format!(
"predicates::is_unsigned_int({}, {}, {})",
self.member_name, width, scale
),
FormatPredicateKind::IsZeroInt => {
format!("predicates::is_zero_int({})", self.member_name)
}
FormatPredicateKind::IsZero32BitFloat => {
format!("predicates::is_zero_32_bit_float({})", self.member_name)
}
FormatPredicateKind::IsZero64BitFloat => {
format!("predicates::is_zero_64_bit_float({})", self.member_name)
}
FormatPredicateKind::IsAllZeroes => format!(
"predicates::is_all_zeroes(func.dfg.constants.get({}))",
self.member_name
),
FormatPredicateKind::IsAllOnes => format!(
"predicates::is_all_ones(func.dfg.constants.get({}))",
self.member_name
),
FormatPredicateKind::LengthEquals(num) => format!(
"predicates::has_length_of({}, {}, func)",
self.member_name, num
),
FormatPredicateKind::IsColocatedFunc => {
format!("predicates::is_colocated_func({}, func)", self.member_name,)
}
FormatPredicateKind::IsColocatedData => {
format!("predicates::is_colocated_data({}, func)", self.member_name)
}
}
}
}
#[derive(Clone, Hash, PartialEq, Eq)]
pub(crate) enum TypePredicateNode {
/// Is the value argument (at the index designated by the first member) the same type as the
/// type name (second member)?
TypeVarCheck(usize, String),
/// Is the controlling type variable the same type as the one designated by the type name
/// (only member)?
CtrlTypeVarCheck(String),
}
impl TypePredicateNode {
fn rust_predicate(&self, func_str: &str) -> String {
match self {
TypePredicateNode::TypeVarCheck(index, value_type_name) => format!(
"{}.dfg.value_type(args[{}]) == {}",
func_str, index, value_type_name
),
TypePredicateNode::CtrlTypeVarCheck(value_type_name) => {
format!("{}.dfg.ctrl_typevar(inst) == {}", func_str, value_type_name)
}
}
}
}
/// A basic node in an instruction predicate: either an atom, or an AND of two conditions.
#[derive(Clone, Hash, PartialEq, Eq)]
pub(crate) enum InstructionPredicateNode {
FormatPredicate(FormatPredicateNode),
TypePredicate(TypePredicateNode),
/// An AND-combination of two or more other predicates.
And(Vec<InstructionPredicateNode>),
/// An OR-combination of two or more other predicates.
Or(Vec<InstructionPredicateNode>),
}
impl InstructionPredicateNode {
fn rust_predicate(&self, func_str: &str) -> String {
match self {
InstructionPredicateNode::FormatPredicate(node) => node.rust_predicate(),
InstructionPredicateNode::TypePredicate(node) => node.rust_predicate(func_str),
InstructionPredicateNode::And(nodes) => nodes
.iter()
.map(|x| x.rust_predicate(func_str))
.collect::<Vec<_>>()
.join(" && "),
InstructionPredicateNode::Or(nodes) => nodes
.iter()
.map(|x| x.rust_predicate(func_str))
.collect::<Vec<_>>()
.join(" || "),
}
}
pub fn format_destructuring_member_name(&self) -> &str {
match self {
InstructionPredicateNode::FormatPredicate(format_pred) => {
format_pred.destructuring_member_name()
}
_ => panic!("Only for leaf format predicates"),
}
}
pub fn format_name(&self) -> &str {
match self {
InstructionPredicateNode::FormatPredicate(format_pred) => format_pred.format_name,
_ => panic!("Only for leaf format predicates"),
}
}
pub fn is_type_predicate(&self) -> bool {
match self {
InstructionPredicateNode::FormatPredicate(_)
| InstructionPredicateNode::And(_)
| InstructionPredicateNode::Or(_) => false,
InstructionPredicateNode::TypePredicate(_) => true,
}
}
fn collect_leaves(&self) -> Vec<&InstructionPredicateNode> {
let mut ret = Vec::new();
match self {
InstructionPredicateNode::And(nodes) | InstructionPredicateNode::Or(nodes) => {
for node in nodes {
ret.extend(node.collect_leaves());
}
}
_ => ret.push(self),
}
ret
}
}
#[derive(Clone, Hash, PartialEq, Eq)]
pub(crate) struct InstructionPredicate {
node: Option<InstructionPredicateNode>,
}
impl Into<InstructionPredicate> for InstructionPredicateNode {
fn into(self) -> InstructionPredicate {
InstructionPredicate { node: Some(self) }
}
}
impl InstructionPredicate {
pub fn new() -> Self {
Self { node: None }
}
pub fn unwrap(self) -> InstructionPredicateNode {
self.node.unwrap()
}
pub fn new_typevar_check(
inst: &Instruction,
type_var: &TypeVar,
value_type: &ValueType,
) -> InstructionPredicateNode {
let index = inst
.value_opnums
.iter()
.enumerate()
.find(|(_, &op_num)| inst.operands_in[op_num].type_var().unwrap() == type_var)
.unwrap()
.0;
InstructionPredicateNode::TypePredicate(TypePredicateNode::TypeVarCheck(
index,
value_type.rust_name(),
))
}
pub fn new_ctrl_typevar_check(value_type: &ValueType) -> InstructionPredicateNode {
InstructionPredicateNode::TypePredicate(TypePredicateNode::CtrlTypeVarCheck(
value_type.rust_name(),
))
}
pub fn new_is_field_equal(
format: &InstructionFormat,
field_name: &'static str,
imm_value: String,
) -> InstructionPredicateNode {
InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new(
format,
field_name,
FormatPredicateKind::IsEqual(imm_value),
))
}
/// Used only for the AST module, which directly passes in the format field.
pub fn new_is_field_equal_ast(
format: &InstructionFormat,
field: &FormatField,
imm_value: String,
) -> InstructionPredicateNode {
InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new_raw(
format,
field.member,
FormatPredicateKind::IsEqual(imm_value),
))
}
pub fn new_is_signed_int(
format: &InstructionFormat,
field_name: &'static str,
width: usize,
scale: usize,
) -> InstructionPredicateNode {
InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new(
format,
field_name,
FormatPredicateKind::IsSignedInt(width, scale),
))
}
pub fn new_is_unsigned_int(
format: &InstructionFormat,
field_name: &'static str,
width: usize,
scale: usize,
) -> InstructionPredicateNode {
InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new(
format,
field_name,
FormatPredicateKind::IsUnsignedInt(width, scale),
))
}
pub fn new_is_zero_int(
format: &InstructionFormat,
field_name: &'static str,
) -> InstructionPredicateNode {
InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new(
format,
field_name,
FormatPredicateKind::IsZeroInt,
))
}
pub fn new_is_zero_32bit_float(
format: &InstructionFormat,
field_name: &'static str,
) -> InstructionPredicateNode {
InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new(
format,
field_name,
FormatPredicateKind::IsZero32BitFloat,
))
}
pub fn new_is_zero_64bit_float(
format: &InstructionFormat,
field_name: &'static str,
) -> InstructionPredicateNode {
InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new(
format,
field_name,
FormatPredicateKind::IsZero64BitFloat,
))
}
pub fn new_is_all_zeroes(
format: &InstructionFormat,
field_name: &'static str,
) -> InstructionPredicateNode {
InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new(
format,
field_name,
FormatPredicateKind::IsAllZeroes,
))
}
pub fn new_is_all_ones(
format: &InstructionFormat,
field_name: &'static str,
) -> InstructionPredicateNode {
InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new(
format,
field_name,
FormatPredicateKind::IsAllOnes,
))
}
pub fn new_length_equals(format: &InstructionFormat, size: usize) -> InstructionPredicateNode {
assert!(
format.has_value_list,
"the format must be variadic in number of arguments"
);
InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new_raw(
format,
"args",
FormatPredicateKind::LengthEquals(size),
))
}
pub fn new_is_colocated_func(
format: &InstructionFormat,
field_name: &'static str,
) -> InstructionPredicateNode {
InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new(
format,
field_name,
FormatPredicateKind::IsColocatedFunc,
))
}
pub fn new_is_colocated_data(formats: &Formats) -> InstructionPredicateNode {
let format = &formats.unary_global_value;
InstructionPredicateNode::FormatPredicate(FormatPredicateNode::new(
&*format,
"global_value",
FormatPredicateKind::IsColocatedData,
))
}
pub fn and(mut self, new_node: InstructionPredicateNode) -> Self {
let node = self.node;
let mut and_nodes = match node {
Some(node) => match node {
InstructionPredicateNode::And(nodes) => nodes,
InstructionPredicateNode::Or(_) => {
panic!("Can't mix and/or without implementing operator precedence!")
}
_ => vec![node],
},
_ => Vec::new(),
};
and_nodes.push(new_node);
self.node = Some(InstructionPredicateNode::And(and_nodes));
self
}
pub fn or(mut self, new_node: InstructionPredicateNode) -> Self {
let node = self.node;
let mut or_nodes = match node {
Some(node) => match node {
InstructionPredicateNode::Or(nodes) => nodes,
InstructionPredicateNode::And(_) => {
panic!("Can't mix and/or without implementing operator precedence!")
}
_ => vec![node],
},
_ => Vec::new(),
};
or_nodes.push(new_node);
self.node = Some(InstructionPredicateNode::Or(or_nodes));
self
}
pub fn rust_predicate(&self, func_str: &str) -> Option<String> {
self.node.as_ref().map(|root| root.rust_predicate(func_str))
}
/// Returns the type predicate if this is one, or None otherwise.
pub fn type_predicate(&self, func_str: &str) -> Option<String> {
let node = self.node.as_ref().unwrap();
if node.is_type_predicate() {
Some(node.rust_predicate(func_str))
} else {
None
}
}
/// Returns references to all the nodes that are leaves in the condition (i.e. by flattening
/// AND/OR).
pub fn collect_leaves(&self) -> Vec<&InstructionPredicateNode> {
self.node.as_ref().unwrap().collect_leaves()
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub(crate) struct InstructionPredicateNumber(u32);
entity_impl!(InstructionPredicateNumber);
pub(crate) type InstructionPredicateMap =
PrimaryMap<InstructionPredicateNumber, InstructionPredicate>;
/// A registry of predicates to help deduplicating them, during Encodings construction. When the
/// construction process is over, it needs to be extracted with `extract` and associated to the
/// TargetIsa.
pub(crate) struct InstructionPredicateRegistry {
/// Maps a predicate number to its actual predicate.
map: InstructionPredicateMap,
/// Inverse map: maps a predicate to its predicate number. This is used before inserting a
/// predicate, to check whether it already exists.
inverted_map: HashMap<InstructionPredicate, InstructionPredicateNumber>,
}
impl InstructionPredicateRegistry {
pub fn new() -> Self {
Self {
map: PrimaryMap::new(),
inverted_map: HashMap::new(),
}
}
pub fn insert(&mut self, predicate: InstructionPredicate) -> InstructionPredicateNumber {
match self.inverted_map.get(&predicate) {
Some(&found) => found,
None => {
let key = self.map.push(predicate.clone());
self.inverted_map.insert(predicate, key);
key
}
}
}
pub fn extract(self) -> InstructionPredicateMap {
self.map
}
}
/// An instruction specification, containing an instruction that has bound types or not.
pub(crate) enum InstSpec {
Inst(Instruction),
Bound(BoundInstruction),
}
impl InstSpec {
pub fn inst(&self) -> &Instruction {
match &self {
InstSpec::Inst(inst) => inst,
InstSpec::Bound(bound_inst) => &bound_inst.inst,
}
}
}
impl Bindable for InstSpec {
fn bind(&self, parameter: impl Into<BindParameter>) -> BoundInstruction {
match self {
InstSpec::Inst(inst) => inst.bind(parameter.into()),
InstSpec::Bound(inst) => inst.bind(parameter.into()),
}
}
}
impl Into<InstSpec> for &Instruction {
fn into(self) -> InstSpec {
InstSpec::Inst(self.clone())
}
}
impl Into<InstSpec> for BoundInstruction {
fn into(self) -> InstSpec {
InstSpec::Bound(self)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::cdsl::formats::InstructionFormatBuilder;
use crate::cdsl::operands::{OperandKind, OperandKindFields};
use crate::cdsl::typevar::TypeSetBuilder;
use crate::shared::types::Int::{I32, I64};
fn field_to_operand(index: usize, field: OperandKindFields) -> Operand {
// Pretend the index string is &'static.
let name = Box::leak(index.to_string().into_boxed_str());
// Format's name / rust_type don't matter here.
let kind = OperandKind::new(name, name, field);
let operand = Operand::new(name, kind);
operand
}
fn field_to_operands(types: Vec<OperandKindFields>) -> Vec<Operand> {
types
.iter()
.enumerate()
.map(|(i, f)| field_to_operand(i, f.clone()))
.collect()
}
fn build_fake_instruction(
inputs: Vec<OperandKindFields>,
outputs: Vec<OperandKindFields>,
) -> Instruction {
// Setup a format from the input operands.
let mut format = InstructionFormatBuilder::new("fake");
for (i, f) in inputs.iter().enumerate() {
match f {
OperandKindFields::TypeVar(_) => format = format.value(),
OperandKindFields::ImmValue => {
format = format.imm(&field_to_operand(i, f.clone()).kind)
}
_ => {}
};
}
let format = format.build();
// Create the fake instruction.
InstructionBuilder::new("fake", "A fake instruction for testing.", &format)
.operands_in(field_to_operands(inputs).iter().collect())
.operands_out(field_to_operands(outputs).iter().collect())
.build(OpcodeNumber(42))
}
#[test]
fn ensure_bound_instructions_can_bind_lane_types() {
let type1 = TypeSetBuilder::new().ints(8..64).build();
let in1 = OperandKindFields::TypeVar(TypeVar::new("a", "...", type1));
let inst = build_fake_instruction(vec![in1], vec![]);
inst.bind(LaneType::Int(I32));
}
#[test]
fn ensure_bound_instructions_can_bind_immediates() {
let inst = build_fake_instruction(vec![OperandKindFields::ImmValue], vec![]);
let bound_inst = inst.bind(Immediate::IntCC(IntCC::Equal));
assert!(bound_inst.verify_bindings().is_ok());
}
#[test]
#[should_panic]
fn ensure_instructions_fail_to_bind() {
let inst = build_fake_instruction(vec![], vec![]);
inst.bind(BindParameter::Lane(LaneType::Int(I32)));
// Trying to bind to an instruction with no inputs should fail.
}
#[test]
#[should_panic]
fn ensure_bound_instructions_fail_to_bind_too_many_types() {
let type1 = TypeSetBuilder::new().ints(8..64).build();
let in1 = OperandKindFields::TypeVar(TypeVar::new("a", "...", type1));
let inst = build_fake_instruction(vec![in1], vec![]);
inst.bind(LaneType::Int(I32)).bind(LaneType::Int(I64));
}
#[test]
#[should_panic]
fn ensure_instructions_fail_to_bind_too_many_immediates() {
let inst = build_fake_instruction(vec![OperandKindFields::ImmValue], vec![]);
inst.bind(BindParameter::Immediate(Immediate::IntCC(IntCC::Equal)))
.bind(BindParameter::Immediate(Immediate::IntCC(IntCC::Equal)));
// Trying to bind too many immediates to an instruction should fail; note that the immediate
// values are nonsensical but irrelevant to the purpose of this test.
}
}

View File

@@ -1,96 +1,12 @@
use std::collections::HashSet;
use std::iter::FromIterator;
use crate::cdsl::cpu_modes::CpuMode;
use crate::cdsl::instructions::InstructionPredicateMap;
use crate::cdsl::recipes::Recipes;
use crate::cdsl::regs::IsaRegs;
use crate::cdsl::settings::SettingGroup;
use crate::cdsl::xform::{TransformGroupIndex, TransformGroups};
pub(crate) struct TargetIsa {
pub name: &'static str,
pub settings: SettingGroup,
pub regs: IsaRegs,
pub recipes: Recipes,
pub cpu_modes: Vec<CpuMode>,
pub encodings_predicates: InstructionPredicateMap,
/// TransformGroupIndex are global to all the ISAs, while we want to have indices into the
/// local array of transform groups that are directly used. We use this map to get this
/// information.
pub local_transform_groups: Vec<TransformGroupIndex>,
}
impl TargetIsa {
pub fn new(
name: &'static str,
settings: SettingGroup,
regs: IsaRegs,
recipes: Recipes,
cpu_modes: Vec<CpuMode>,
encodings_predicates: InstructionPredicateMap,
) -> Self {
// Compute the local TransformGroup index.
let mut local_transform_groups = Vec::new();
for cpu_mode in &cpu_modes {
let transform_groups = cpu_mode.direct_transform_groups();
for group_index in transform_groups {
// find() is fine here: the number of transform group is < 5 as of June 2019.
if local_transform_groups
.iter()
.find(|&val| group_index == *val)
.is_none()
{
local_transform_groups.push(group_index);
}
}
}
Self {
name,
settings,
regs,
recipes,
cpu_modes,
encodings_predicates,
local_transform_groups,
}
}
/// Returns a deterministically ordered, deduplicated list of TransformGroupIndex for the
/// transitive set of TransformGroup this TargetIsa uses.
pub fn transitive_transform_groups(
&self,
all_groups: &TransformGroups,
) -> Vec<TransformGroupIndex> {
let mut set = HashSet::new();
for &root in self.local_transform_groups.iter() {
set.insert(root);
let mut base = root;
// Follow the chain of chain_with.
while let Some(chain_with) = &all_groups.get(base).chain_with {
set.insert(*chain_with);
base = *chain_with;
}
}
let mut vec = Vec::from_iter(set);
vec.sort();
vec
}
/// Returns a deterministically ordered, deduplicated list of TransformGroupIndex for the directly
/// reachable set of TransformGroup this TargetIsa uses.
pub fn direct_transform_groups(&self) -> &Vec<TransformGroupIndex> {
&self.local_transform_groups
}
pub fn translate_group_index(&self, group_index: TransformGroupIndex) -> usize {
self.local_transform_groups
.iter()
.position(|&val| val == group_index)
.expect("TransformGroup unused by this TargetIsa!")
pub fn new(name: &'static str, settings: SettingGroup) -> Self {
Self { name, settings }
}
}

View File

@@ -3,21 +3,14 @@
//! This module defines the classes that are used to define Cranelift
//! instructions and other entities.
#[macro_use]
pub mod ast;
pub mod cpu_modes;
pub mod encodings;
pub mod formats;
pub mod instructions;
pub mod isa;
pub mod operands;
pub mod recipes;
pub mod regs;
pub mod settings;
pub mod type_inference;
pub mod types;
pub mod typevar;
pub mod xform;
/// A macro that converts boolean settings into predicates to look more natural.
#[macro_export]

View File

@@ -1,297 +0,0 @@
use std::rc::Rc;
use cranelift_entity::{entity_impl, PrimaryMap};
use crate::cdsl::formats::InstructionFormat;
use crate::cdsl::instructions::InstructionPredicate;
use crate::cdsl::regs::RegClassIndex;
use crate::cdsl::settings::SettingPredicateNumber;
/// 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`.
#[derive(Copy, Clone, Hash, PartialEq, Eq)]
pub(crate) struct Register {
pub regclass: RegClassIndex,
pub unit: u8,
}
impl Register {
pub fn new(regclass: RegClassIndex, unit: u8) -> Self {
Self { regclass, unit }
}
}
/// 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.
#[derive(Copy, Clone, Hash, PartialEq)]
pub(crate) struct Stack {
pub regclass: RegClassIndex,
}
impl Stack {
pub fn new(regclass: RegClassIndex) -> Self {
Self { regclass }
}
pub fn stack_base_mask(self) -> &'static str {
// TODO: Make this configurable instead of just using the SP.
"StackBaseMask(1)"
}
}
#[derive(Clone, Hash, PartialEq)]
pub(crate) struct BranchRange {
pub inst_size: u64,
pub range: u64,
}
#[derive(Copy, Clone, Hash, PartialEq)]
pub(crate) enum OperandConstraint {
RegClass(RegClassIndex),
FixedReg(Register),
TiedInput(usize),
Stack(Stack),
}
impl Into<OperandConstraint> for RegClassIndex {
fn into(self) -> OperandConstraint {
OperandConstraint::RegClass(self)
}
}
impl Into<OperandConstraint> for Register {
fn into(self) -> OperandConstraint {
OperandConstraint::FixedReg(self)
}
}
impl Into<OperandConstraint> for usize {
fn into(self) -> OperandConstraint {
OperandConstraint::TiedInput(self)
}
}
impl Into<OperandConstraint> for Stack {
fn into(self) -> OperandConstraint {
OperandConstraint::Stack(self)
}
}
/// 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 `operands_in` and `operands_out` 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.
#[derive(Clone)]
pub(crate) struct EncodingRecipe {
/// Short mnemonic name for this recipe.
pub name: String,
/// Associated instruction format.
pub format: Rc<InstructionFormat>,
/// Base number of bytes in the binary encoded instruction.
pub base_size: u64,
/// Tuple of register constraints for value operands.
pub operands_in: Vec<OperandConstraint>,
/// Tuple of register constraints for results.
pub operands_out: Vec<OperandConstraint>,
/// Function name to use when computing actual size.
pub compute_size: &'static str,
/// `(origin, bits)` range for branches.
pub branch_range: Option<BranchRange>,
/// This instruction clobbers `iflags` and `fflags`; true by default.
pub clobbers_flags: bool,
/// Instruction predicate.
pub inst_predicate: Option<InstructionPredicate>,
/// ISA predicate.
pub isa_predicate: Option<SettingPredicateNumber>,
/// Rust code for binary emission.
pub emit: Option<String>,
}
// Implement PartialEq ourselves: take all the fields into account but the name.
impl PartialEq for EncodingRecipe {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.format, &other.format)
&& self.base_size == other.base_size
&& self.operands_in == other.operands_in
&& self.operands_out == other.operands_out
&& self.compute_size == other.compute_size
&& self.branch_range == other.branch_range
&& self.clobbers_flags == other.clobbers_flags
&& self.inst_predicate == other.inst_predicate
&& self.isa_predicate == other.isa_predicate
&& self.emit == other.emit
}
}
// To allow using it in a hashmap.
impl Eq for EncodingRecipe {}
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub(crate) struct EncodingRecipeNumber(u32);
entity_impl!(EncodingRecipeNumber);
pub(crate) type Recipes = PrimaryMap<EncodingRecipeNumber, EncodingRecipe>;
#[derive(Clone)]
pub(crate) struct EncodingRecipeBuilder {
pub name: String,
format: Rc<InstructionFormat>,
pub base_size: u64,
pub operands_in: Option<Vec<OperandConstraint>>,
pub operands_out: Option<Vec<OperandConstraint>>,
pub compute_size: Option<&'static str>,
pub branch_range: Option<BranchRange>,
pub emit: Option<String>,
clobbers_flags: Option<bool>,
inst_predicate: Option<InstructionPredicate>,
isa_predicate: Option<SettingPredicateNumber>,
}
impl EncodingRecipeBuilder {
pub fn new(name: impl Into<String>, format: &Rc<InstructionFormat>, base_size: u64) -> Self {
Self {
name: name.into(),
format: format.clone(),
base_size,
operands_in: None,
operands_out: None,
compute_size: None,
branch_range: None,
emit: None,
clobbers_flags: None,
inst_predicate: None,
isa_predicate: None,
}
}
// Setters.
pub fn operands_in(mut self, constraints: Vec<impl Into<OperandConstraint>>) -> Self {
assert!(self.operands_in.is_none());
self.operands_in = Some(
constraints
.into_iter()
.map(|constr| constr.into())
.collect(),
);
self
}
pub fn operands_out(mut self, constraints: Vec<impl Into<OperandConstraint>>) -> Self {
assert!(self.operands_out.is_none());
self.operands_out = Some(
constraints
.into_iter()
.map(|constr| constr.into())
.collect(),
);
self
}
pub fn clobbers_flags(mut self, flag: bool) -> Self {
assert!(self.clobbers_flags.is_none());
self.clobbers_flags = Some(flag);
self
}
pub fn emit(mut self, code: impl Into<String>) -> Self {
assert!(self.emit.is_none());
self.emit = Some(code.into());
self
}
pub fn branch_range(mut self, range: (u64, u64)) -> Self {
assert!(self.branch_range.is_none());
self.branch_range = Some(BranchRange {
inst_size: range.0,
range: range.1,
});
self
}
pub fn isa_predicate(mut self, pred: SettingPredicateNumber) -> Self {
assert!(self.isa_predicate.is_none());
self.isa_predicate = Some(pred);
self
}
pub fn inst_predicate(mut self, inst_predicate: impl Into<InstructionPredicate>) -> Self {
assert!(self.inst_predicate.is_none());
self.inst_predicate = Some(inst_predicate.into());
self
}
pub fn compute_size(mut self, compute_size: &'static str) -> Self {
assert!(self.compute_size.is_none());
self.compute_size = Some(compute_size);
self
}
pub fn build(self) -> EncodingRecipe {
let operands_in = self.operands_in.unwrap_or_default();
let operands_out = self.operands_out.unwrap_or_default();
// The number of input constraints must match the number of format input operands.
if !self.format.has_value_list {
assert!(
operands_in.len() == self.format.num_value_operands,
"missing operand constraints for recipe {} (format {})",
self.name,
self.format.name
);
}
// Ensure tied inputs actually refer to existing inputs.
for constraint in operands_in.iter().chain(operands_out.iter()) {
if let OperandConstraint::TiedInput(n) = *constraint {
assert!(n < operands_in.len());
}
}
let compute_size = match self.compute_size {
Some(compute_size) => compute_size,
None => "base_size",
};
let clobbers_flags = self.clobbers_flags.unwrap_or(true);
EncodingRecipe {
name: self.name,
format: self.format,
base_size: self.base_size,
operands_in,
operands_out,
compute_size,
branch_range: self.branch_range,
clobbers_flags,
inst_predicate: self.inst_predicate,
isa_predicate: self.isa_predicate,
emit: self.emit,
}
}
}

View File

@@ -1,412 +0,0 @@
use cranelift_codegen_shared::constants;
use cranelift_entity::{entity_impl, EntityRef, PrimaryMap};
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub(crate) struct RegBankIndex(u32);
entity_impl!(RegBankIndex);
pub(crate) struct RegBank {
pub name: &'static str,
pub first_unit: u8,
pub units: u8,
pub names: Vec<&'static str>,
pub prefix: &'static str,
pub pressure_tracking: bool,
pub pinned_reg: Option<u16>,
pub toprcs: Vec<RegClassIndex>,
pub classes: Vec<RegClassIndex>,
}
impl RegBank {
pub fn new(
name: &'static str,
first_unit: u8,
units: u8,
names: Vec<&'static str>,
prefix: &'static str,
pressure_tracking: bool,
pinned_reg: Option<u16>,
) -> Self {
RegBank {
name,
first_unit,
units,
names,
prefix,
pressure_tracking,
pinned_reg,
toprcs: Vec::new(),
classes: Vec::new(),
}
}
fn unit_by_name(&self, name: &'static str) -> u8 {
let unit = if let Some(found) = self.names.iter().position(|&reg_name| reg_name == name) {
found
} else {
// Try to match without the bank prefix.
assert!(name.starts_with(self.prefix));
let name_without_prefix = &name[self.prefix.len()..];
if let Some(found) = self
.names
.iter()
.position(|&reg_name| reg_name == name_without_prefix)
{
found
} else {
// Ultimate try: try to parse a number and use this in the array, eg r15 on x86.
if let Ok(as_num) = name_without_prefix.parse::<u8>() {
assert!(
as_num < self.units,
"trying to get {}, but bank only has {} registers!",
name,
self.units
);
as_num as usize
} else {
panic!("invalid register name {}", name);
}
}
};
self.first_unit + (unit as u8)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
pub(crate) struct RegClassIndex(u32);
entity_impl!(RegClassIndex);
pub(crate) struct RegClass {
pub name: &'static str,
pub index: RegClassIndex,
pub width: u8,
pub bank: RegBankIndex,
pub toprc: RegClassIndex,
pub count: u8,
pub start: u8,
pub subclasses: Vec<RegClassIndex>,
}
impl RegClass {
pub fn new(
name: &'static str,
index: RegClassIndex,
width: u8,
bank: RegBankIndex,
toprc: RegClassIndex,
count: u8,
start: u8,
) -> Self {
Self {
name,
index,
width,
bank,
toprc,
count,
start,
subclasses: Vec::new(),
}
}
/// Compute a bit-mask of subclasses, including self.
pub fn subclass_mask(&self) -> u64 {
let mut m = 1 << self.index.index();
for rc in self.subclasses.iter() {
m |= 1 << rc.index();
}
m
}
/// Compute a bit-mask of the register units allocated by this register class.
pub fn mask(&self, bank_first_unit: u8) -> Vec<u32> {
let mut u = (self.start + bank_first_unit) as usize;
let mut out_mask = vec![0, 0, 0];
for _ in 0..self.count {
out_mask[u / 32] |= 1 << (u % 32);
u += self.width as usize;
}
out_mask
}
}
pub(crate) enum RegClassProto {
TopLevel(RegBankIndex),
SubClass(RegClassIndex),
}
pub(crate) struct RegClassBuilder {
pub name: &'static str,
pub width: u8,
pub count: u8,
pub start: u8,
pub proto: RegClassProto,
}
impl RegClassBuilder {
pub fn new_toplevel(name: &'static str, bank: RegBankIndex) -> Self {
Self {
name,
width: 1,
count: 0,
start: 0,
proto: RegClassProto::TopLevel(bank),
}
}
pub fn subclass_of(
name: &'static str,
parent_index: RegClassIndex,
start: u8,
stop: u8,
) -> Self {
assert!(stop >= start);
Self {
name,
width: 0,
count: stop - start,
start,
proto: RegClassProto::SubClass(parent_index),
}
}
pub fn count(mut self, count: u8) -> Self {
self.count = count;
self
}
pub fn width(mut self, width: u8) -> Self {
match self.proto {
RegClassProto::TopLevel(_) => self.width = width,
RegClassProto::SubClass(_) => panic!("Subclasses inherit their parent's width."),
}
self
}
}
pub(crate) struct RegBankBuilder {
pub name: &'static str,
pub units: u8,
pub names: Vec<&'static str>,
pub prefix: &'static str,
pub pressure_tracking: Option<bool>,
pub pinned_reg: Option<u16>,
}
impl RegBankBuilder {
pub fn new(name: &'static str, prefix: &'static str) -> Self {
Self {
name,
units: 0,
names: vec![],
prefix,
pressure_tracking: None,
pinned_reg: None,
}
}
pub fn units(mut self, units: u8) -> Self {
self.units = units;
self
}
pub fn names(mut self, names: Vec<&'static str>) -> Self {
self.names = names;
self
}
pub fn track_pressure(mut self, track: bool) -> Self {
self.pressure_tracking = Some(track);
self
}
pub fn pinned_reg(mut self, unit: u16) -> Self {
assert!(unit < u16::from(self.units));
self.pinned_reg = Some(unit);
self
}
}
pub(crate) struct IsaRegsBuilder {
pub banks: PrimaryMap<RegBankIndex, RegBank>,
pub classes: PrimaryMap<RegClassIndex, RegClass>,
}
impl IsaRegsBuilder {
pub fn new() -> Self {
Self {
banks: PrimaryMap::new(),
classes: PrimaryMap::new(),
}
}
pub fn add_bank(&mut self, builder: RegBankBuilder) -> RegBankIndex {
let first_unit = if self.banks.is_empty() {
0
} else {
let last = &self.banks.last().unwrap();
let first_available_unit = (last.first_unit + last.units) as i8;
let units = builder.units;
let align = if units.is_power_of_two() {
units
} else {
units.next_power_of_two()
} as i8;
(first_available_unit + align - 1) & -align
} as u8;
self.banks.push(RegBank::new(
builder.name,
first_unit,
builder.units,
builder.names,
builder.prefix,
builder
.pressure_tracking
.expect("Pressure tracking must be explicitly set"),
builder.pinned_reg,
))
}
pub fn add_class(&mut self, builder: RegClassBuilder) -> RegClassIndex {
let class_index = self.classes.next_key();
// Finish delayed construction of RegClass.
let (bank, toprc, start, width) = match builder.proto {
RegClassProto::TopLevel(bank_index) => {
self.banks
.get_mut(bank_index)
.unwrap()
.toprcs
.push(class_index);
(bank_index, class_index, builder.start, builder.width)
}
RegClassProto::SubClass(parent_class_index) => {
assert!(builder.width == 0);
let (bank, toprc, start, width) = {
let parent = self.classes.get(parent_class_index).unwrap();
(parent.bank, parent.toprc, parent.start, parent.width)
};
for reg_class in self.classes.values_mut() {
if reg_class.toprc == toprc {
reg_class.subclasses.push(class_index);
}
}
let subclass_start = start + builder.start * width;
(bank, toprc, subclass_start, width)
}
};
let reg_bank_units = self.banks.get(bank).unwrap().units;
assert!(start < reg_bank_units);
let count = if builder.count != 0 {
builder.count
} else {
reg_bank_units / width
};
let reg_class = RegClass::new(builder.name, class_index, width, bank, toprc, count, start);
self.classes.push(reg_class);
let reg_bank = self.banks.get_mut(bank).unwrap();
reg_bank.classes.push(class_index);
class_index
}
/// Checks 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.
pub fn build(self) -> IsaRegs {
for reg_bank in self.banks.values() {
for i1 in reg_bank.classes.iter() {
for i2 in reg_bank.classes.iter() {
if i1 >= i2 {
continue;
}
let rc1 = self.classes.get(*i1).unwrap();
let rc2 = self.classes.get(*i2).unwrap();
let rc1_mask = rc1.mask(0);
let rc2_mask = rc2.mask(0);
assert!(
rc1.width != rc2.width || rc1_mask != rc2_mask,
"no duplicates"
);
if rc1.width != rc2.width {
continue;
}
let mut intersect = Vec::new();
for (a, b) in rc1_mask.iter().zip(rc2_mask.iter()) {
intersect.push(a & b);
}
if intersect == vec![0; intersect.len()] {
continue;
}
// Classes must be topologically ordered, so the intersection can't be the
// superclass.
assert!(intersect != rc1_mask);
// If the intersection is the second one, then it must be a subclass.
if intersect == rc2_mask {
assert!(self
.classes
.get(*i1)
.unwrap()
.subclasses
.iter()
.any(|x| *x == *i2));
}
}
}
}
assert!(
self.classes.len() <= constants::MAX_NUM_REG_CLASSES,
"Too many register classes"
);
let num_toplevel = self
.classes
.values()
.filter(|x| x.toprc == x.index && self.banks.get(x.bank).unwrap().pressure_tracking)
.count();
assert!(
num_toplevel <= constants::MAX_TRACKED_TOP_RCS,
"Too many top-level register classes"
);
IsaRegs::new(self.banks, self.classes)
}
}
pub(crate) struct IsaRegs {
pub banks: PrimaryMap<RegBankIndex, RegBank>,
pub classes: PrimaryMap<RegClassIndex, RegClass>,
}
impl IsaRegs {
fn new(
banks: PrimaryMap<RegBankIndex, RegBank>,
classes: PrimaryMap<RegClassIndex, RegClass>,
) -> Self {
Self { banks, classes }
}
pub fn class_by_name(&self, name: &str) -> RegClassIndex {
self.classes
.values()
.find(|&class| class.name == name)
.unwrap_or_else(|| panic!("register class {} not found", name))
.index
}
pub fn regunit_by_name(&self, class_index: RegClassIndex, name: &'static str) -> u8 {
let bank_index = self.classes.get(class_index).unwrap().bank;
self.banks.get(bank_index).unwrap().unit_by_name(name)
}
}

View File

@@ -150,14 +150,6 @@ impl SettingGroup {
}
panic!("Should have found bool setting by name.");
}
pub fn predicate_by_name(&self, name: &'static str) -> SettingPredicateNumber {
self.predicates
.iter()
.find(|pred| pred.name == name)
.unwrap_or_else(|| panic!("unknown predicate {}", name))
.number
}
}
/// This is the basic information needed to track the specific parts of a setting when building

View File

@@ -1,8 +1,4 @@
use crate::cdsl::ast::{Def, DefIndex, DefPool, Var, VarIndex, VarPool};
use crate::cdsl::typevar::{DerivedFunc, TypeSet, TypeVar};
use std::collections::{HashMap, HashSet};
use std::iter::FromIterator;
use crate::cdsl::typevar::TypeVar;
#[derive(Debug, Hash, PartialEq, Eq)]
pub(crate) enum Constraint {
@@ -11,651 +7,4 @@ pub(crate) enum Constraint {
/// 1) They have the same number of lanes
/// 2) In a lane tv1 has at least as many bits as tv2.
WiderOrEq(TypeVar, TypeVar),
/// Constraint specifying that two derived type vars must have the same runtime type.
Eq(TypeVar, TypeVar),
/// Constraint specifying that a type var must belong to some typeset.
InTypeset(TypeVar, TypeSet),
}
impl Constraint {
fn translate_with<F: Fn(&TypeVar) -> TypeVar>(&self, func: F) -> Constraint {
match self {
Constraint::WiderOrEq(lhs, rhs) => {
let lhs = func(&lhs);
let rhs = func(&rhs);
Constraint::WiderOrEq(lhs, rhs)
}
Constraint::Eq(lhs, rhs) => {
let lhs = func(&lhs);
let rhs = func(&rhs);
Constraint::Eq(lhs, rhs)
}
Constraint::InTypeset(tv, ts) => {
let tv = func(&tv);
Constraint::InTypeset(tv, ts.clone())
}
}
}
/// Creates a new constraint by replacing type vars by their hashmap equivalent.
fn translate_with_map(
&self,
original_to_own_typevar: &HashMap<&TypeVar, TypeVar>,
) -> Constraint {
self.translate_with(|tv| substitute(original_to_own_typevar, tv))
}
/// Creates a new constraint by replacing type vars by their canonical equivalent.
fn translate_with_env(&self, type_env: &TypeEnvironment) -> Constraint {
self.translate_with(|tv| type_env.get_equivalent(tv))
}
fn is_trivial(&self) -> bool {
match self {
Constraint::WiderOrEq(lhs, rhs) => {
// Trivially true.
if lhs == rhs {
return true;
}
let ts1 = lhs.get_typeset();
let ts2 = rhs.get_typeset();
// Trivially true.
if ts1.is_wider_or_equal(&ts2) {
return true;
}
// Trivially false.
if ts1.is_narrower(&ts2) {
return true;
}
// Trivially false.
if (&ts1.lanes & &ts2.lanes).is_empty() {
return true;
}
self.is_concrete()
}
Constraint::Eq(lhs, rhs) => lhs == rhs || self.is_concrete(),
Constraint::InTypeset(_, _) => {
// The way InTypeset are made, they would always be trivial if we were applying the
// same logic as the Python code did, so ignore this.
self.is_concrete()
}
}
}
/// Returns true iff all the referenced type vars are singletons.
fn is_concrete(&self) -> bool {
match self {
Constraint::WiderOrEq(lhs, rhs) => {
lhs.singleton_type().is_some() && rhs.singleton_type().is_some()
}
Constraint::Eq(lhs, rhs) => {
lhs.singleton_type().is_some() && rhs.singleton_type().is_some()
}
Constraint::InTypeset(tv, _) => tv.singleton_type().is_some(),
}
}
fn typevar_args(&self) -> Vec<&TypeVar> {
match self {
Constraint::WiderOrEq(lhs, rhs) => vec![lhs, rhs],
Constraint::Eq(lhs, rhs) => vec![lhs, rhs],
Constraint::InTypeset(tv, _) => vec![tv],
}
}
}
#[derive(Clone, Copy)]
enum TypeEnvRank {
Singleton = 5,
Input = 4,
Intermediate = 3,
Output = 2,
Temp = 1,
Internal = 0,
}
/// Class encapsulating the necessary bookkeeping for type inference.
pub(crate) struct TypeEnvironment {
vars: HashSet<VarIndex>,
ranks: HashMap<TypeVar, TypeEnvRank>,
equivalency_map: HashMap<TypeVar, TypeVar>,
pub constraints: Vec<Constraint>,
}
impl TypeEnvironment {
fn new() -> Self {
TypeEnvironment {
vars: HashSet::new(),
ranks: HashMap::new(),
equivalency_map: HashMap::new(),
constraints: Vec::new(),
}
}
fn register(&mut self, var_index: VarIndex, var: &mut Var) {
self.vars.insert(var_index);
let rank = if var.is_input() {
TypeEnvRank::Input
} else if var.is_intermediate() {
TypeEnvRank::Intermediate
} else if var.is_output() {
TypeEnvRank::Output
} else {
assert!(var.is_temp());
TypeEnvRank::Temp
};
self.ranks.insert(var.get_or_create_typevar(), rank);
}
fn add_constraint(&mut self, constraint: Constraint) {
if self.constraints.iter().any(|item| *item == constraint) {
return;
}
// Check extra conditions for InTypeset constraints.
if let Constraint::InTypeset(tv, _) = &constraint {
assert!(
tv.base.is_none(),
"type variable is {:?}, while expecting none",
tv
);
assert!(
tv.name.starts_with("typeof_"),
"Name \"{}\" should start with \"typeof_\"",
tv.name
);
}
self.constraints.push(constraint);
}
/// Returns the canonical representative of the equivalency class of the given argument, or
/// duplicates it if it's not there yet.
pub fn get_equivalent(&self, tv: &TypeVar) -> TypeVar {
let mut tv = tv;
while let Some(found) = self.equivalency_map.get(tv) {
tv = found;
}
match &tv.base {
Some(parent) => self
.get_equivalent(&parent.type_var)
.derived(parent.derived_func),
None => tv.clone(),
}
}
/// Get the rank of tv in the partial order:
/// - TVs directly associated with a Var get their rank from the Var (see register()).
/// - Internally generated non-derived TVs implicitly get the lowest rank (0).
/// - Derived variables get their rank from their free typevar.
/// - Singletons have the highest rank.
/// - TVs associated with vars in a source pattern have a higher rank than TVs associated with
/// temporary vars.
fn rank(&self, tv: &TypeVar) -> u8 {
let actual_tv = match tv.base {
Some(_) => tv.free_typevar(),
None => Some(tv.clone()),
};
let rank = match actual_tv {
Some(actual_tv) => match self.ranks.get(&actual_tv) {
Some(rank) => Some(*rank),
None => {
assert!(
!actual_tv.name.starts_with("typeof_"),
"variable {} should be explicitly ranked",
actual_tv.name
);
None
}
},
None => None,
};
let rank = match rank {
Some(rank) => rank,
None => {
if tv.singleton_type().is_some() {
TypeEnvRank::Singleton
} else {
TypeEnvRank::Internal
}
}
};
rank as u8
}
/// Record the fact that the free tv1 is part of the same equivalence class as tv2. The
/// canonical representative of the merged class is tv2's canonical representative.
fn record_equivalent(&mut self, tv1: TypeVar, tv2: TypeVar) {
assert!(tv1.base.is_none());
assert!(self.get_equivalent(&tv1) == tv1);
if let Some(tv2_base) = &tv2.base {
// Ensure there are no cycles.
assert!(self.get_equivalent(&tv2_base.type_var) != tv1);
}
self.equivalency_map.insert(tv1, tv2);
}
/// Get the free typevars in the current type environment.
pub fn free_typevars(&self, var_pool: &mut VarPool) -> Vec<TypeVar> {
let mut typevars = Vec::new();
typevars.extend(self.equivalency_map.keys().cloned());
typevars.extend(
self.vars
.iter()
.map(|&var_index| var_pool.get_mut(var_index).get_or_create_typevar()),
);
let set: HashSet<TypeVar> = HashSet::from_iter(
typevars
.iter()
.map(|tv| self.get_equivalent(tv).free_typevar())
.filter(|opt_tv| {
// Filter out singleton types.
opt_tv.is_some()
})
.map(|tv| tv.unwrap()),
);
Vec::from_iter(set)
}
/// Normalize by collapsing any roots that don't correspond to a concrete type var AND have a
/// single type var 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
fn normalize(&mut self, var_pool: &mut VarPool) {
let source_tvs: HashSet<TypeVar> = HashSet::from_iter(
self.vars
.iter()
.map(|&var_index| var_pool.get_mut(var_index).get_or_create_typevar()),
);
let mut children: HashMap<TypeVar, HashSet<TypeVar>> = HashMap::new();
// Insert all the parents found by the derivation relationship.
for type_var in self.equivalency_map.values() {
if type_var.base.is_none() {
continue;
}
let parent_tv = type_var.free_typevar();
if parent_tv.is_none() {
// Ignore this type variable, it's a singleton.
continue;
}
let parent_tv = parent_tv.unwrap();
children
.entry(parent_tv)
.or_insert_with(HashSet::new)
.insert(type_var.clone());
}
// Insert all the explicit equivalency links.
for (equivalent_tv, canon_tv) in self.equivalency_map.iter() {
children
.entry(canon_tv.clone())
.or_insert_with(HashSet::new)
.insert(equivalent_tv.clone());
}
// Remove links that are straight paths up to typevar of variables.
for free_root in self.free_typevars(var_pool) {
let mut root = &free_root;
while !source_tvs.contains(&root)
&& children.contains_key(&root)
&& children.get(&root).unwrap().len() == 1
{
let child = children.get(&root).unwrap().iter().next().unwrap();
assert_eq!(self.equivalency_map[child], root.clone());
self.equivalency_map.remove(child);
root = child;
}
}
}
/// Extract a clean type environment from self, that only mentions type vars associated with
/// real variables.
fn extract(self, var_pool: &mut VarPool) -> TypeEnvironment {
let vars_tv: HashSet<TypeVar> = HashSet::from_iter(
self.vars
.iter()
.map(|&var_index| var_pool.get_mut(var_index).get_or_create_typevar()),
);
let mut new_equivalency_map: HashMap<TypeVar, TypeVar> = HashMap::new();
for tv in &vars_tv {
let canon_tv = self.get_equivalent(tv);
if *tv != canon_tv {
new_equivalency_map.insert(tv.clone(), canon_tv.clone());
}
// Sanity check: the translated type map should only refer to real variables.
assert!(vars_tv.contains(tv));
let canon_free_tv = canon_tv.free_typevar();
assert!(canon_free_tv.is_none() || vars_tv.contains(&canon_free_tv.unwrap()));
}
let mut new_constraints: HashSet<Constraint> = HashSet::new();
for constraint in &self.constraints {
let constraint = constraint.translate_with_env(&self);
if constraint.is_trivial() || new_constraints.contains(&constraint) {
continue;
}
// Sanity check: translated constraints should refer only to real variables.
for arg in constraint.typevar_args() {
let arg_free_tv = arg.free_typevar();
assert!(arg_free_tv.is_none() || vars_tv.contains(&arg_free_tv.unwrap()));
}
new_constraints.insert(constraint);
}
TypeEnvironment {
vars: self.vars,
ranks: self.ranks,
equivalency_map: new_equivalency_map,
constraints: Vec::from_iter(new_constraints),
}
}
}
/// Replaces an external type variable according to the following rules:
/// - if a local copy is present in the map, return it.
/// - or if it's derived, create a local derived one that recursively substitutes the parent.
/// - or return itself.
fn substitute(map: &HashMap<&TypeVar, TypeVar>, external_type_var: &TypeVar) -> TypeVar {
match map.get(&external_type_var) {
Some(own_type_var) => own_type_var.clone(),
None => match &external_type_var.base {
Some(parent) => {
let parent_substitute = substitute(map, &parent.type_var);
TypeVar::derived(&parent_substitute, parent.derived_func)
}
None => external_type_var.clone(),
},
}
}
/// Normalize a (potentially derived) typevar 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
fn canonicalize_derivations(tv: TypeVar) -> TypeVar {
let base = match &tv.base {
Some(base) => base,
None => return tv,
};
let derived_func = base.derived_func;
if let Some(base_base) = &base.type_var.base {
let base_base_tv = &base_base.type_var;
match (derived_func, base_base.derived_func) {
(DerivedFunc::HalfWidth, DerivedFunc::DoubleWidth)
| (DerivedFunc::DoubleWidth, DerivedFunc::HalfWidth)
| (DerivedFunc::HalfVector, DerivedFunc::DoubleVector)
| (DerivedFunc::DoubleVector, DerivedFunc::HalfVector) => {
// Cancelling bijective transformations. This doesn't hide any overflow issues
// since derived type sets are checked upon derivaion, and base typesets are only
// allowed to shrink.
return canonicalize_derivations(base_base_tv.clone());
}
(DerivedFunc::HalfWidth, DerivedFunc::HalfVector)
| (DerivedFunc::HalfWidth, DerivedFunc::DoubleVector)
| (DerivedFunc::DoubleWidth, DerivedFunc::DoubleVector)
| (DerivedFunc::DoubleWidth, DerivedFunc::HalfVector) => {
// Arbitrarily put WIDTH derivations before VECTOR derivations, since they commute.
return canonicalize_derivations(
base_base_tv
.derived(derived_func)
.derived(base_base.derived_func),
);
}
_ => {}
};
}
canonicalize_derivations(base.type_var.clone()).derived(derived_func)
}
/// 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
/// a fixed point is reached.
fn constrain_fixpoint(tv1: &TypeVar, tv2: &TypeVar) {
loop {
let old_tv1_ts = tv1.get_typeset().clone();
tv2.constrain_types(tv1.clone());
if tv1.get_typeset() == old_tv1_ts {
break;
}
}
let old_tv2_ts = tv2.get_typeset();
tv1.constrain_types(tv2.clone());
// The above loop should ensure that all reference cycles have been handled.
assert!(old_tv2_ts == tv2.get_typeset());
}
/// Unify tv1 and tv2 in the given type environment. tv1 must have a rank greater or equal to tv2's
/// one, modulo commutations.
fn unify(tv1: &TypeVar, tv2: &TypeVar, type_env: &mut TypeEnvironment) -> Result<(), String> {
let tv1 = canonicalize_derivations(type_env.get_equivalent(tv1));
let tv2 = canonicalize_derivations(type_env.get_equivalent(tv2));
if tv1 == tv2 {
// Already unified.
return Ok(());
}
if type_env.rank(&tv2) < type_env.rank(&tv1) {
// Make sure tv1 always has the smallest rank, since real variables have the higher rank
// and we want them to be the canonical representatives of their equivalency classes.
return unify(&tv2, &tv1, type_env);
}
constrain_fixpoint(&tv1, &tv2);
if tv1.get_typeset().size() == 0 || tv2.get_typeset().size() == 0 {
return Err(format!(
"Error: empty type created when unifying {} and {}",
tv1.name, tv2.name
));
}
let base = match &tv1.base {
Some(base) => base,
None => {
type_env.record_equivalent(tv1, tv2);
return Ok(());
}
};
if let Some(inverse) = base.derived_func.inverse() {
return unify(&base.type_var, &tv2.derived(inverse), type_env);
}
type_env.add_constraint(Constraint::Eq(tv1, tv2));
Ok(())
}
/// Perform type inference on one Def in the current type environment 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 typevar with the corresponding actual typevar.
fn infer_definition(
def: &Def,
var_pool: &mut VarPool,
type_env: TypeEnvironment,
last_type_index: &mut usize,
) -> Result<TypeEnvironment, String> {
let apply = &def.apply;
let inst = &apply.inst;
let mut type_env = type_env;
let free_formal_tvs = inst.all_typevars();
let mut original_to_own_typevar: HashMap<&TypeVar, TypeVar> = HashMap::new();
for &tv in &free_formal_tvs {
assert!(original_to_own_typevar
.insert(
tv,
TypeVar::copy_from(tv, format!("own_{}", last_type_index))
)
.is_none());
*last_type_index += 1;
}
// Update the mapping with any explicity bound type vars:
for (i, value_type) in apply.value_types.iter().enumerate() {
let singleton = TypeVar::new_singleton(value_type.clone());
assert!(original_to_own_typevar
.insert(free_formal_tvs[i], singleton)
.is_some());
}
// Get fresh copies for each typevar in the signature (both free and derived).
let mut formal_tvs = Vec::new();
formal_tvs.extend(inst.value_results.iter().map(|&i| {
substitute(
&original_to_own_typevar,
inst.operands_out[i].type_var().unwrap(),
)
}));
formal_tvs.extend(inst.value_opnums.iter().map(|&i| {
substitute(
&original_to_own_typevar,
inst.operands_in[i].type_var().unwrap(),
)
}));
// Get the list of actual vars.
let mut actual_vars = Vec::new();
actual_vars.extend(inst.value_results.iter().map(|&i| def.defined_vars[i]));
actual_vars.extend(
inst.value_opnums
.iter()
.map(|&i| apply.args[i].unwrap_var()),
);
// Get the list of the actual TypeVars.
let mut actual_tvs = Vec::new();
for var_index in actual_vars {
let var = var_pool.get_mut(var_index);
type_env.register(var_index, var);
actual_tvs.push(var.get_or_create_typevar());
}
// Make sure we start unifying with the control type variable first, by putting it at the
// front of both vectors.
if let Some(poly) = &inst.polymorphic_info {
let own_ctrl_tv = &original_to_own_typevar[&poly.ctrl_typevar];
let ctrl_index = formal_tvs.iter().position(|tv| tv == own_ctrl_tv).unwrap();
if ctrl_index != 0 {
formal_tvs.swap(0, ctrl_index);
actual_tvs.swap(0, ctrl_index);
}
}
// Unify each actual type variable with the corresponding formal type variable.
for (actual_tv, formal_tv) in actual_tvs.iter().zip(&formal_tvs) {
if let Err(msg) = unify(actual_tv, formal_tv, &mut type_env) {
return Err(format!(
"fail ti on {} <: {}: {}",
actual_tv.name, formal_tv.name, msg
));
}
}
// Add any instruction specific constraints.
for constraint in &inst.constraints {
type_env.add_constraint(constraint.translate_with_map(&original_to_own_typevar));
}
Ok(type_env)
}
/// Perform type inference on an transformation. Return an updated type environment or error.
pub(crate) fn infer_transform(
src: DefIndex,
dst: &[DefIndex],
def_pool: &DefPool,
var_pool: &mut VarPool,
) -> Result<TypeEnvironment, String> {
let mut type_env = TypeEnvironment::new();
let mut last_type_index = 0;
// Execute type inference on the source pattern.
type_env = infer_definition(def_pool.get(src), var_pool, type_env, &mut last_type_index)
.map_err(|err| format!("In src pattern: {}", err))?;
// Collect the type sets once after applying the source patterm; we'll compare the typesets
// after we've also considered the destination pattern, and will emit supplementary InTypeset
// checks if they don't match.
let src_typesets = type_env
.vars
.iter()
.map(|&var_index| {
let var = var_pool.get_mut(var_index);
let tv = type_env.get_equivalent(&var.get_or_create_typevar());
(var_index, tv.get_typeset())
})
.collect::<Vec<_>>();
// Execute type inference on the destination pattern.
for (i, &def_index) in dst.iter().enumerate() {
let def = def_pool.get(def_index);
type_env = infer_definition(def, var_pool, type_env, &mut last_type_index)
.map_err(|err| format!("line {}: {}", i, err))?;
}
for (var_index, src_typeset) in src_typesets {
let var = var_pool.get(var_index);
if !var.has_free_typevar() {
continue;
}
let tv = type_env.get_equivalent(&var.get_typevar().unwrap());
let new_typeset = tv.get_typeset();
assert!(
new_typeset.is_subset(&src_typeset),
"type sets can only get narrower"
);
if new_typeset != src_typeset {
type_env.add_constraint(Constraint::InTypeset(tv.clone(), new_typeset.clone()));
}
}
type_env.normalize(var_pool);
Ok(type_env.extract(var_pool))
}

View File

@@ -71,12 +71,12 @@ impl ValueType {
}
/// Find the unique number associated with this type.
pub fn number(&self) -> Option<u8> {
pub fn number(&self) -> u8 {
match *self {
ValueType::Lane(l) => Some(l.number()),
ValueType::Reference(r) => Some(r.number()),
ValueType::Special(s) => Some(s.number()),
ValueType::Vector(ref v) => Some(v.number()),
ValueType::Lane(l) => l.number(),
ValueType::Reference(r) => r.number(),
ValueType::Special(s) => s.number(),
ValueType::Vector(ref v) => v.number(),
}
}
@@ -237,20 +237,6 @@ impl LaneType {
ValueType::Vector(VectorType::new(self, lanes.into()))
}
}
pub fn is_float(self) -> bool {
match self {
LaneType::Float(_) => true,
_ => false,
}
}
pub fn is_int(self) -> bool {
match self {
LaneType::Int(_) => true,
_ => false,
}
}
}
impl fmt::Display for LaneType {
@@ -407,8 +393,6 @@ impl fmt::Debug for VectorType {
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) enum SpecialType {
Flag(shared_types::Flag),
// FIXME remove once the old style backends are removed.
StructArgument,
}
impl SpecialType {
@@ -423,9 +407,6 @@ impl SpecialType {
"CPU flags representing the result of a floating point comparison. These
flags can be tested with a :type:`floatcc` condition code.",
),
SpecialType::StructArgument => {
String::from("After legalization sarg_t arguments will get this type.")
}
}
}
@@ -433,7 +414,6 @@ impl SpecialType {
pub fn lane_bits(self) -> u64 {
match self {
SpecialType::Flag(_) => 0,
SpecialType::StructArgument => 0,
}
}
@@ -442,7 +422,6 @@ impl SpecialType {
match self {
SpecialType::Flag(shared_types::Flag::IFlags) => 1,
SpecialType::Flag(shared_types::Flag::FFlags) => 2,
SpecialType::StructArgument => 3,
}
}
}
@@ -452,7 +431,6 @@ impl fmt::Display for SpecialType {
match *self {
SpecialType::Flag(shared_types::Flag::IFlags) => write!(f, "iflags"),
SpecialType::Flag(shared_types::Flag::FFlags) => write!(f, "fflags"),
SpecialType::StructArgument => write!(f, "sarg_t"),
}
}
}
@@ -464,7 +442,6 @@ impl fmt::Debug for SpecialType {
"{}",
match *self {
SpecialType::Flag(_) => format!("FlagsType({})", self),
SpecialType::StructArgument => format!("StructArgument"),
}
)
}
@@ -478,14 +455,12 @@ impl From<shared_types::Flag> for SpecialType {
pub(crate) struct SpecialTypeIterator {
flag_iter: shared_types::FlagIterator,
done: bool,
}
impl SpecialTypeIterator {
fn new() -> Self {
Self {
flag_iter: shared_types::FlagIterator::new(),
done: false,
}
}
}
@@ -493,16 +468,7 @@ impl SpecialTypeIterator {
impl Iterator for SpecialTypeIterator {
type Item = SpecialType;
fn next(&mut self) -> Option<Self::Item> {
if let Some(f) = self.flag_iter.next() {
Some(SpecialType::from(f))
} else {
if !self.done {
self.done = true;
Some(SpecialType::StructArgument)
} else {
None
}
}
self.flag_iter.next().map(SpecialType::from)
}
}

View File

@@ -1,5 +1,5 @@
use std::cell::RefCell;
use std::collections::{BTreeSet, HashSet};
use std::collections::BTreeSet;
use std::fmt;
use std::hash;
use std::iter::FromIterator;
@@ -269,52 +269,6 @@ impl TypeVar {
pub fn merge_lanes(&self) -> TypeVar {
self.derived(DerivedFunc::MergeLanes)
}
/// Constrain the range of types this variable can assume to a subset of those in the typeset
/// ts.
/// May mutate itself if it's not derived, or its parent if it is.
pub fn constrain_types_by_ts(&self, type_set: TypeSet) {
match &self.base {
Some(base) => {
base.type_var
.constrain_types_by_ts(type_set.preimage(base.derived_func));
}
None => {
self.content
.borrow_mut()
.type_set
.inplace_intersect_with(&type_set);
}
}
}
/// Constrain the range of types this variable can assume to a subset of those `other` can
/// assume.
/// May mutate itself if it's not derived, or its parent if it is.
pub fn constrain_types(&self, other: TypeVar) {
if self == &other {
return;
}
self.constrain_types_by_ts(other.get_typeset());
}
/// Get a Rust expression that computes the type of this type variable.
pub fn to_rust_code(&self) -> String {
match &self.base {
Some(base) => format!(
"{}.{}().unwrap()",
base.type_var.to_rust_code(),
base.derived_func.name()
),
None => {
if let Some(singleton) = self.singleton_type() {
singleton.rust_name()
} else {
self.name.clone()
}
}
}
}
}
impl Into<TypeVar> for &TypeVar {
@@ -392,19 +346,6 @@ impl DerivedFunc {
DerivedFunc::MergeLanes => "merge_lanes",
}
}
/// Returns the inverse function of this one, if it is a bijection.
pub fn inverse(self) -> Option<DerivedFunc> {
match self {
DerivedFunc::HalfWidth => Some(DerivedFunc::DoubleWidth),
DerivedFunc::DoubleWidth => Some(DerivedFunc::HalfWidth),
DerivedFunc::HalfVector => Some(DerivedFunc::DoubleVector),
DerivedFunc::DoubleVector => Some(DerivedFunc::HalfVector),
DerivedFunc::MergeLanes => Some(DerivedFunc::SplitLanes),
DerivedFunc::SplitLanes => Some(DerivedFunc::MergeLanes),
_ => None,
}
}
}
#[derive(Debug, Hash)]
@@ -594,94 +535,6 @@ impl TypeSet {
assert_eq!(types.len(), 1);
types.remove(0)
}
/// Return the inverse image of self across the derived function func.
fn preimage(&self, func: DerivedFunc) -> TypeSet {
if self.size() == 0 {
// The inverse of the empty set is itself.
return self.clone();
}
match func {
DerivedFunc::LaneOf => {
let mut copy = self.clone();
copy.lanes =
NumSet::from_iter((0..=MAX_LANES.trailing_zeros()).map(|i| u16::pow(2, i)));
copy
}
DerivedFunc::AsBool => {
let mut copy = self.clone();
if self.bools.contains(&1) {
copy.ints = NumSet::from_iter(vec![8, 16, 32, 64, 128]);
copy.floats = NumSet::from_iter(vec![32, 64]);
} else {
copy.ints = &self.bools - &NumSet::from_iter(vec![1]);
copy.floats = &self.bools & &NumSet::from_iter(vec![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.
copy.lanes = &self.lanes - &NumSet::from_iter(vec![1]);
}
copy
}
DerivedFunc::HalfWidth => self.double_width(),
DerivedFunc::DoubleWidth => self.half_width(),
DerivedFunc::HalfVector => self.double_vector(),
DerivedFunc::DoubleVector => self.half_vector(),
DerivedFunc::SplitLanes => self.double_width().half_vector(),
DerivedFunc::MergeLanes => self.half_width().double_vector(),
}
}
pub fn inplace_intersect_with(&mut self, other: &TypeSet) {
self.lanes = &self.lanes & &other.lanes;
self.ints = &self.ints & &other.ints;
self.floats = &self.floats & &other.floats;
self.bools = &self.bools & &other.bools;
self.refs = &self.refs & &other.refs;
let mut new_specials = Vec::new();
for spec in &self.specials {
if let Some(spec) = other.specials.iter().find(|&other_spec| other_spec == spec) {
new_specials.push(*spec);
}
}
self.specials = new_specials;
}
pub fn is_subset(&self, other: &TypeSet) -> bool {
self.lanes.is_subset(&other.lanes)
&& self.ints.is_subset(&other.ints)
&& self.floats.is_subset(&other.floats)
&& self.bools.is_subset(&other.bools)
&& self.refs.is_subset(&other.refs)
&& {
let specials: HashSet<SpecialType> = HashSet::from_iter(self.specials.clone());
let other_specials = HashSet::from_iter(other.specials.clone());
specials.is_subset(&other_specials)
}
}
pub fn is_wider_or_equal(&self, other: &TypeSet) -> bool {
set_wider_or_equal(&self.ints, &other.ints)
&& set_wider_or_equal(&self.floats, &other.floats)
&& set_wider_or_equal(&self.bools, &other.bools)
&& set_wider_or_equal(&self.refs, &other.refs)
}
pub fn is_narrower(&self, other: &TypeSet) -> bool {
set_narrower(&self.ints, &other.ints)
&& set_narrower(&self.floats, &other.floats)
&& set_narrower(&self.bools, &other.bools)
&& set_narrower(&self.refs, &other.refs)
}
}
fn set_wider_or_equal(s1: &NumSet, s2: &NumSet) -> bool {
!s1.is_empty() && !s2.is_empty() && s1.iter().min() >= s2.iter().max()
}
fn set_narrower(s1: &NumSet, s2: &NumSet) -> bool {
!s1.is_empty() && !s2.is_empty() && s1.iter().min() < s2.iter().max()
}
impl fmt::Debug for TypeSet {
@@ -806,18 +659,6 @@ impl TypeSetBuilder {
self.specials,
)
}
pub fn all() -> TypeSet {
TypeSetBuilder::new()
.ints(Interval::All)
.floats(Interval::All)
.bools(Interval::All)
.refs(Interval::All)
.simd_lanes(Interval::All)
.specials(ValueType::all_special_types().collect())
.includes_scalars(true)
.build()
}
}
#[derive(PartialEq)]
@@ -1054,136 +895,6 @@ fn test_forward_images() {
);
}
#[test]
fn test_backward_images() {
let empty_set = TypeSetBuilder::new().build();
// LaneOf.
assert_eq!(
TypeSetBuilder::new()
.simd_lanes(1..1)
.ints(8..8)
.floats(32..32)
.build()
.preimage(DerivedFunc::LaneOf),
TypeSetBuilder::new()
.simd_lanes(Interval::All)
.ints(8..8)
.floats(32..32)
.build()
);
assert_eq!(empty_set.preimage(DerivedFunc::LaneOf), empty_set);
// AsBool.
assert_eq!(
TypeSetBuilder::new()
.simd_lanes(1..4)
.bools(1..128)
.build()
.preimage(DerivedFunc::AsBool),
TypeSetBuilder::new()
.simd_lanes(1..4)
.ints(Interval::All)
.bools(Interval::All)
.floats(Interval::All)
.build()
);
// Double vector.
assert_eq!(
TypeSetBuilder::new()
.simd_lanes(1..1)
.ints(8..8)
.build()
.preimage(DerivedFunc::DoubleVector)
.size(),
0
);
assert_eq!(
TypeSetBuilder::new()
.simd_lanes(1..16)
.ints(8..16)
.floats(32..32)
.build()
.preimage(DerivedFunc::DoubleVector),
TypeSetBuilder::new()
.simd_lanes(1..8)
.ints(8..16)
.floats(32..32)
.build(),
);
// Half vector.
assert_eq!(
TypeSetBuilder::new()
.simd_lanes(256..256)
.ints(8..8)
.build()
.preimage(DerivedFunc::HalfVector)
.size(),
0
);
assert_eq!(
TypeSetBuilder::new()
.simd_lanes(64..128)
.bools(1..32)
.build()
.preimage(DerivedFunc::HalfVector),
TypeSetBuilder::new()
.simd_lanes(128..256)
.bools(1..32)
.build(),
);
// Half width.
assert_eq!(
TypeSetBuilder::new()
.ints(128..128)
.floats(64..64)
.bools(128..128)
.build()
.preimage(DerivedFunc::HalfWidth)
.size(),
0
);
assert_eq!(
TypeSetBuilder::new()
.simd_lanes(64..256)
.bools(1..64)
.build()
.preimage(DerivedFunc::HalfWidth),
TypeSetBuilder::new()
.simd_lanes(64..256)
.bools(16..128)
.build(),
);
// Double width.
assert_eq!(
TypeSetBuilder::new()
.ints(8..8)
.floats(32..32)
.bools(1..8)
.build()
.preimage(DerivedFunc::DoubleWidth)
.size(),
0
);
assert_eq!(
TypeSetBuilder::new()
.simd_lanes(1..16)
.ints(8..16)
.floats(32..64)
.build()
.preimage(DerivedFunc::DoubleWidth),
TypeSetBuilder::new()
.simd_lanes(1..16)
.ints(8..8)
.floats(32..32)
.build()
);
}
#[test]
#[should_panic]
fn test_typeset_singleton_panic_nonsingleton_types() {

View File

@@ -1,484 +0,0 @@
use crate::cdsl::ast::{
Apply, BlockPool, ConstPool, DefIndex, DefPool, DummyDef, DummyExpr, Expr, PatternPosition,
VarIndex, VarPool,
};
use crate::cdsl::instructions::Instruction;
use crate::cdsl::type_inference::{infer_transform, TypeEnvironment};
use crate::cdsl::typevar::TypeVar;
use cranelift_entity::{entity_impl, PrimaryMap};
use std::collections::{HashMap, HashSet};
use std::iter::FromIterator;
/// An instruction transformation consists of a source and destination pattern.
///
/// Patterns are expressed in *register transfer language* as tuples of Def or Expr nodes. A
/// pattern may optionally have a sequence of TypeConstraints, that additionally limit the set of
/// cases when it applies.
///
/// The source pattern can contain only a single instruction.
pub(crate) struct Transform {
pub src: DefIndex,
pub dst: Vec<DefIndex>,
pub var_pool: VarPool,
pub def_pool: DefPool,
pub block_pool: BlockPool,
pub const_pool: ConstPool,
pub type_env: TypeEnvironment,
}
type SymbolTable = HashMap<String, VarIndex>;
impl Transform {
fn new(src: DummyDef, dst: Vec<DummyDef>) -> Self {
let mut var_pool = VarPool::new();
let mut def_pool = DefPool::new();
let mut block_pool = BlockPool::new();
let mut const_pool = ConstPool::new();
let mut input_vars: Vec<VarIndex> = Vec::new();
let mut defined_vars: Vec<VarIndex> = Vec::new();
// Maps variable names to our own Var copies.
let mut symbol_table: SymbolTable = SymbolTable::new();
// Rewrite variables in src and dst using our own copies.
let src = rewrite_def_list(
PatternPosition::Source,
vec![src],
&mut symbol_table,
&mut input_vars,
&mut defined_vars,
&mut var_pool,
&mut def_pool,
&mut block_pool,
&mut const_pool,
)[0];
let num_src_inputs = input_vars.len();
let dst = rewrite_def_list(
PatternPosition::Destination,
dst,
&mut symbol_table,
&mut input_vars,
&mut defined_vars,
&mut var_pool,
&mut def_pool,
&mut block_pool,
&mut const_pool,
);
// Sanity checks.
for &var_index in &input_vars {
assert!(
var_pool.get(var_index).is_input(),
"'{:?}' used as both input and def",
var_pool.get(var_index)
);
}
assert!(
input_vars.len() == num_src_inputs,
"extra input vars in dst pattern: {:?}",
input_vars
.iter()
.map(|&i| var_pool.get(i))
.skip(num_src_inputs)
.collect::<Vec<_>>()
);
// Perform type inference and cleanup.
let type_env = infer_transform(src, &dst, &def_pool, &mut var_pool).unwrap();
// Sanity check: the set of inferred free type variables should be a subset of the type
// variables corresponding to Vars appearing in the source pattern.
{
let free_typevars: HashSet<TypeVar> =
HashSet::from_iter(type_env.free_typevars(&mut var_pool));
let src_tvs = HashSet::from_iter(
input_vars
.clone()
.iter()
.chain(
defined_vars
.iter()
.filter(|&&var_index| !var_pool.get(var_index).is_temp()),
)
.map(|&var_index| var_pool.get(var_index).get_typevar())
.filter(|maybe_var| maybe_var.is_some())
.map(|var| var.unwrap()),
);
if !free_typevars.is_subset(&src_tvs) {
let missing_tvs = (&free_typevars - &src_tvs)
.iter()
.map(|tv| tv.name.clone())
.collect::<Vec<_>>()
.join(", ");
panic!("Some free vars don't appear in src: {}", missing_tvs);
}
}
for &var_index in input_vars.iter().chain(defined_vars.iter()) {
let var = var_pool.get_mut(var_index);
let canon_tv = type_env.get_equivalent(&var.get_or_create_typevar());
var.set_typevar(canon_tv);
}
Self {
src,
dst,
var_pool,
def_pool,
block_pool,
const_pool,
type_env,
}
}
fn verify_legalize(&self) {
let def = self.def_pool.get(self.src);
for &var_index in def.defined_vars.iter() {
let defined_var = self.var_pool.get(var_index);
assert!(
defined_var.is_output(),
"{:?} not defined in the destination pattern",
defined_var
);
}
}
}
/// Inserts, if not present, a name in the `symbol_table`. Then returns its index in the variable
/// pool `var_pool`. If the variable was not present in the symbol table, then add it to the list of
/// `defined_vars`.
fn var_index(
name: &str,
symbol_table: &mut SymbolTable,
defined_vars: &mut Vec<VarIndex>,
var_pool: &mut VarPool,
) -> VarIndex {
let name = name.to_string();
match symbol_table.get(&name) {
Some(&existing_var) => existing_var,
None => {
// Materialize the variable.
let new_var = var_pool.create(name.clone());
symbol_table.insert(name, new_var);
defined_vars.push(new_var);
new_var
}
}
}
/// Given a list of symbols defined in a Def, rewrite them to local symbols. Yield the new locals.
fn rewrite_defined_vars(
position: PatternPosition,
dummy_def: &DummyDef,
def_index: DefIndex,
symbol_table: &mut SymbolTable,
defined_vars: &mut Vec<VarIndex>,
var_pool: &mut VarPool,
) -> Vec<VarIndex> {
let mut new_defined_vars = Vec::new();
for var in &dummy_def.defined_vars {
let own_var = var_index(&var.name, symbol_table, defined_vars, var_pool);
var_pool.get_mut(own_var).set_def(position, def_index);
new_defined_vars.push(own_var);
}
new_defined_vars
}
/// Find all uses of variables in `expr` and replace them with our own local symbols.
fn rewrite_expr(
position: PatternPosition,
dummy_expr: DummyExpr,
symbol_table: &mut SymbolTable,
input_vars: &mut Vec<VarIndex>,
var_pool: &mut VarPool,
const_pool: &mut ConstPool,
) -> Apply {
let (apply_target, dummy_args) = if let DummyExpr::Apply(apply_target, dummy_args) = dummy_expr
{
(apply_target, dummy_args)
} else {
panic!("we only rewrite apply expressions");
};
assert_eq!(
apply_target.inst().operands_in.len(),
dummy_args.len(),
"number of arguments in instruction {} is incorrect\nexpected: {:?}",
apply_target.inst().name,
apply_target
.inst()
.operands_in
.iter()
.map(|operand| format!("{}: {}", operand.name, operand.kind.rust_type))
.collect::<Vec<_>>(),
);
let mut args = Vec::new();
for (i, arg) in dummy_args.into_iter().enumerate() {
match arg {
DummyExpr::Var(var) => {
let own_var = var_index(&var.name, symbol_table, input_vars, var_pool);
let var = var_pool.get(own_var);
assert!(
var.is_input() || var.get_def(position).is_some(),
"{:?} used as both input and def",
var
);
args.push(Expr::Var(own_var));
}
DummyExpr::Literal(literal) => {
assert!(!apply_target.inst().operands_in[i].is_value());
args.push(Expr::Literal(literal));
}
DummyExpr::Constant(constant) => {
let const_name = const_pool.insert(constant.0);
// Here we abuse var_index by passing an empty, immediately-dropped vector to
// `defined_vars`; the reason for this is that unlike the `Var` case above,
// constants will create a variable that is not an input variable (it is tracked
// instead by ConstPool).
let const_var = var_index(&const_name, symbol_table, &mut vec![], var_pool);
args.push(Expr::Var(const_var));
}
DummyExpr::Apply(..) => {
panic!("Recursive apply is not allowed.");
}
DummyExpr::Block(_block) => {
panic!("Blocks are not valid arguments.");
}
}
}
Apply::new(apply_target, args)
}
#[allow(clippy::too_many_arguments)]
fn rewrite_def_list(
position: PatternPosition,
dummy_defs: Vec<DummyDef>,
symbol_table: &mut SymbolTable,
input_vars: &mut Vec<VarIndex>,
defined_vars: &mut Vec<VarIndex>,
var_pool: &mut VarPool,
def_pool: &mut DefPool,
block_pool: &mut BlockPool,
const_pool: &mut ConstPool,
) -> Vec<DefIndex> {
let mut new_defs = Vec::new();
// Register variable names of new blocks first as a block name can be used to jump forward. Thus
// the name has to be registered first to avoid misinterpreting it as an input-var.
for dummy_def in dummy_defs.iter() {
if let DummyExpr::Block(ref var) = dummy_def.expr {
var_index(&var.name, symbol_table, defined_vars, var_pool);
}
}
// Iterate over the definitions and blocks, to map variables names to inputs or outputs.
for dummy_def in dummy_defs {
let def_index = def_pool.next_index();
let new_defined_vars = rewrite_defined_vars(
position,
&dummy_def,
def_index,
symbol_table,
defined_vars,
var_pool,
);
if let DummyExpr::Block(var) = dummy_def.expr {
let var_index = *symbol_table
.get(&var.name)
.or_else(|| {
panic!(
"Block {} was not registered during the first visit",
var.name
)
})
.unwrap();
var_pool.get_mut(var_index).set_def(position, def_index);
block_pool.create_block(var_index, def_index);
} else {
let new_apply = rewrite_expr(
position,
dummy_def.expr,
symbol_table,
input_vars,
var_pool,
const_pool,
);
assert!(
def_pool.next_index() == def_index,
"shouldn't have created new defs in the meanwhile"
);
assert_eq!(
new_apply.inst.value_results.len(),
new_defined_vars.len(),
"number of Var results in instruction is incorrect"
);
new_defs.push(def_pool.create_inst(new_apply, new_defined_vars));
}
}
new_defs
}
/// A group of related transformations.
pub(crate) struct TransformGroup {
pub name: &'static str,
pub doc: &'static str,
pub chain_with: Option<TransformGroupIndex>,
pub isa_name: Option<&'static str>,
pub id: TransformGroupIndex,
/// Maps Instruction camel_case names to custom legalization functions names.
pub custom_legalizes: HashMap<String, &'static str>,
pub transforms: Vec<Transform>,
}
impl TransformGroup {
pub fn rust_name(&self) -> String {
match self.isa_name {
Some(_) => {
// This is a function in the same module as the LEGALIZE_ACTIONS table referring to
// it.
self.name.to_string()
}
None => format!("crate::legalizer::{}", self.name),
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub(crate) struct TransformGroupIndex(u32);
entity_impl!(TransformGroupIndex);
pub(crate) struct TransformGroupBuilder {
name: &'static str,
doc: &'static str,
chain_with: Option<TransformGroupIndex>,
isa_name: Option<&'static str>,
pub custom_legalizes: HashMap<String, &'static str>,
pub transforms: Vec<Transform>,
}
impl TransformGroupBuilder {
pub fn new(name: &'static str, doc: &'static str) -> Self {
Self {
name,
doc,
chain_with: None,
isa_name: None,
custom_legalizes: HashMap::new(),
transforms: Vec::new(),
}
}
pub fn chain_with(mut self, next_id: TransformGroupIndex) -> Self {
assert!(self.chain_with.is_none());
self.chain_with = Some(next_id);
self
}
pub fn isa(mut self, isa_name: &'static str) -> Self {
assert!(self.isa_name.is_none());
self.isa_name = Some(isa_name);
self
}
/// Add a custom legalization action for `inst`.
///
/// The `func_name` parameter is the fully qualified name of a Rust function which takes the
/// same arguments as the `isa::Legalize` actions.
///
/// The custom function will be called to legalize `inst` and any return value is ignored.
pub fn custom_legalize(&mut self, inst: &Instruction, func_name: &'static str) {
assert!(
self.custom_legalizes
.insert(inst.camel_name.clone(), func_name)
.is_none(),
"custom legalization action for {} inserted twice",
inst.name
);
}
/// Add a legalization pattern to this group.
pub fn legalize(&mut self, src: DummyDef, dst: Vec<DummyDef>) {
let transform = Transform::new(src, dst);
transform.verify_legalize();
self.transforms.push(transform);
}
pub fn build_and_add_to(self, owner: &mut TransformGroups) -> TransformGroupIndex {
let next_id = owner.next_key();
owner.add(TransformGroup {
name: self.name,
doc: self.doc,
isa_name: self.isa_name,
id: next_id,
chain_with: self.chain_with,
custom_legalizes: self.custom_legalizes,
transforms: self.transforms,
})
}
}
pub(crate) struct TransformGroups {
groups: PrimaryMap<TransformGroupIndex, TransformGroup>,
}
impl TransformGroups {
pub fn new() -> Self {
Self {
groups: PrimaryMap::new(),
}
}
pub fn add(&mut self, new_group: TransformGroup) -> TransformGroupIndex {
for group in self.groups.values() {
assert!(
group.name != new_group.name,
"trying to insert {} for the second time",
new_group.name
);
}
self.groups.push(new_group)
}
pub fn get(&self, id: TransformGroupIndex) -> &TransformGroup {
&self.groups[id]
}
fn next_key(&self) -> TransformGroupIndex {
self.groups.next_key()
}
pub fn by_name(&self, name: &'static str) -> &TransformGroup {
for group in self.groups.values() {
if group.name == name {
return group;
}
}
panic!("transform group with name {} not found", name);
}
}
#[test]
#[should_panic]
fn test_double_custom_legalization() {
use crate::cdsl::formats::InstructionFormatBuilder;
use crate::cdsl::instructions::{AllInstructions, InstructionBuilder, InstructionGroupBuilder};
let nullary = InstructionFormatBuilder::new("nullary").build();
let mut dummy_all = AllInstructions::new();
let mut inst_group = InstructionGroupBuilder::new(&mut dummy_all);
inst_group.push(InstructionBuilder::new("dummy", "doc", &nullary));
let inst_group = inst_group.build();
let dummy_inst = inst_group.by_name("dummy");
let mut transform_group = TransformGroupBuilder::new("test", "doc");
transform_group.custom_legalize(&dummy_inst, "custom 1");
transform_group.custom_legalize(&dummy_inst, "custom 2");
}

View File

@@ -1,224 +0,0 @@
//! Generate binary emission code for each ISA.
use cranelift_entity::EntityRef;
use crate::error;
use crate::srcgen::Formatter;
use crate::cdsl::recipes::{EncodingRecipe, OperandConstraint, Recipes};
/// 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.
fn gen_recipe(recipe: &EncodingRecipe, fmt: &mut Formatter) {
let inst_format = &recipe.format;
let num_value_ops = inst_format.num_value_operands;
// TODO: Set want_args to true for only MultiAry instructions instead of all formats with value
// list.
let want_args = inst_format.has_value_list
|| recipe.operands_in.iter().any(|c| match c {
OperandConstraint::RegClass(_) | OperandConstraint::Stack(_) => true,
OperandConstraint::FixedReg(_) | OperandConstraint::TiedInput(_) => false,
});
assert!(!want_args || num_value_ops > 0 || inst_format.has_value_list);
let want_outs = recipe.operands_out.iter().any(|c| match c {
OperandConstraint::RegClass(_) | OperandConstraint::Stack(_) => true,
OperandConstraint::FixedReg(_) | OperandConstraint::TiedInput(_) => false,
});
let is_regmove = ["RegMove", "RegSpill", "RegFill"].contains(&inst_format.name);
// Unpack the instruction data.
fmtln!(fmt, "if let InstructionData::{} {{", inst_format.name);
fmt.indent(|fmt| {
fmt.line("opcode,");
for f in &inst_format.imm_fields {
fmtln!(fmt, "{},", f.member);
}
if want_args {
if inst_format.has_value_list || num_value_ops > 1 {
fmt.line("ref args,");
} else {
fmt.line("arg,");
}
}
fmt.line("..");
fmt.outdented_line("} = *inst_data {");
// Pass recipe arguments in this order: inputs, imm_fields, outputs.
let mut args = String::new();
if want_args && !is_regmove {
if inst_format.has_value_list {
fmt.line("let args = args.as_slice(&func.dfg.value_lists);");
} else if num_value_ops == 1 {
fmt.line("let args = [arg];");
}
args += &unwrap_values(&recipe.operands_in, "in", "args", fmt);
}
for f in &inst_format.imm_fields {
args += &format!(", {}", f.member);
}
// Unwrap interesting output arguments.
if want_outs {
if recipe.operands_out.len() == 1 {
fmt.line("let results = [func.dfg.first_result(inst)];")
} else {
fmt.line("let results = func.dfg.inst_results(inst);");
}
args += &unwrap_values(&recipe.operands_out, "out", "results", fmt);
}
// Optimization: Only update the register diversion tracker for regmove instructions.
if is_regmove {
fmt.line("divert.apply(inst_data);")
}
match &recipe.emit {
Some(emit) => {
fmt.multi_line(emit);
fmt.line("return;");
}
None => {
fmtln!(
fmt,
"return recipe_{}(func, inst, sink, bits{});",
recipe.name.to_lowercase(),
args
);
}
}
});
fmt.line("}");
}
/// Emit code that unwraps values living in registers or stack slots.
///
/// :param args: Input or output constraints.
/// :param prefix: Prefix to be used for the generated local variables.
/// :param values: Name of slice containing the values to be unwrapped.
/// :returns: Comma separated list of the generated variables
fn unwrap_values(
args: &[OperandConstraint],
prefix: &str,
values_slice: &str,
fmt: &mut Formatter,
) -> String {
let mut varlist = String::new();
for (i, cst) in args.iter().enumerate() {
match cst {
OperandConstraint::RegClass(_reg_class) => {
let v = format!("{}_reg{}", prefix, i);
varlist += &format!(", {}", v);
fmtln!(
fmt,
"let {} = divert.reg({}[{}], &func.locations);",
v,
values_slice,
i
);
}
OperandConstraint::Stack(stack) => {
let v = format!("{}_stk{}", prefix, i);
varlist += &format!(", {}", v);
fmtln!(fmt, "let {} = StackRef::masked(", v);
fmt.indent(|fmt| {
fmtln!(
fmt,
"divert.stack({}[{}], &func.locations),",
values_slice,
i
);
fmt.line(format!("{},", stack.stack_base_mask()));
fmt.line("&func.stack_slots,");
});
fmt.line(").unwrap();");
}
_ => {}
}
}
varlist
}
fn gen_isa(isa_name: &str, recipes: &Recipes, fmt: &mut Formatter) {
fmt.doc_comment(format!(
"Emit binary machine code for `inst` for the {} ISA.",
isa_name
));
if recipes.is_empty() {
fmt.line("pub fn emit_inst<CS: CodeSink + ?Sized>(");
fmt.indent(|fmt| {
fmt.line("func: &Function,");
fmt.line("inst: Inst,");
fmt.line("_divert: &mut RegDiversions,");
fmt.line("_sink: &mut CS,");
fmt.line("_isa: &dyn TargetIsa,");
});
fmt.line(") {");
fmt.indent(|fmt| {
// No encoding recipes: Emit a stub.
fmt.line("bad_encoding(func, inst)");
});
fmt.line("}");
return;
}
fmt.line("#[allow(unused_variables, unreachable_code)]");
fmt.line("pub fn emit_inst<CS: CodeSink + ?Sized>(");
fmt.indent(|fmt| {
fmt.line("func: &Function,");
fmt.line("inst: Inst,");
fmt.line("divert: &mut RegDiversions,");
fmt.line("sink: &mut CS,");
fmt.line("isa: &dyn TargetIsa,")
});
fmt.line(") {");
fmt.indent(|fmt| {
fmt.line("let encoding = func.encodings[inst];");
fmt.line("let bits = encoding.bits();");
fmt.line("let inst_data = &func.dfg[inst];");
fmt.line("match encoding.recipe() {");
fmt.indent(|fmt| {
for (i, recipe) in recipes.iter() {
fmt.comment(format!("Recipe {}", recipe.name));
fmtln!(fmt, "{} => {{", i.index());
fmt.indent(|fmt| {
gen_recipe(recipe, fmt);
});
fmt.line("}");
}
fmt.line("_ => {},");
});
fmt.line("}");
// Allow for unencoded ghost instructions. The verifier will check details.
fmt.line("if encoding.is_legal() {");
fmt.indent(|fmt| {
fmt.line("bad_encoding(func, inst);");
});
fmt.line("}");
});
fmt.line("}");
}
pub(crate) fn generate(
isa_name: &str,
recipes: &Recipes,
binemit_filename: &str,
out_dir: &str,
) -> Result<(), error::Error> {
let mut fmt = Formatter::new();
gen_isa(isa_name, recipes, &mut fmt);
fmt.update_file(binemit_filename, out_dir)?;
Ok(())
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,734 +0,0 @@
//! Generate transformations to legalize instructions without encodings.
use crate::cdsl::ast::{Def, DefPool, Expr, VarPool};
use crate::cdsl::isa::TargetIsa;
use crate::cdsl::operands::Operand;
use crate::cdsl::type_inference::Constraint;
use crate::cdsl::typevar::{TypeSet, TypeVar};
use crate::cdsl::xform::{Transform, TransformGroup, TransformGroups};
use crate::error;
use crate::gen_inst::gen_typesets_table;
use crate::srcgen::Formatter;
use crate::unique_table::UniqueTable;
use std::collections::{HashMap, HashSet};
use std::iter::FromIterator;
/// Given a `Def` node, emit code that extracts all the instruction fields from
/// `pos.func.dfg[iref]`.
///
/// Create local variables named after the `Var` instances in `node`.
///
/// Also create a local variable named `predicate` with the value of the evaluated instruction
/// predicate, or `true` if the node has no predicate.
fn unwrap_inst(transform: &Transform, fmt: &mut Formatter) -> bool {
let var_pool = &transform.var_pool;
let def_pool = &transform.def_pool;
let def = def_pool.get(transform.src);
let apply = &def.apply;
let inst = &apply.inst;
let iform = &inst.format;
fmt.comment(format!(
"Unwrap fields from instruction format {}",
def.to_comment_string(&transform.var_pool)
));
// Extract the Var arguments.
let arg_names = apply
.args
.iter()
.enumerate()
.filter(|(arg_num, _)| {
// Variable args are specially handled after extracting args.
!inst.operands_in[*arg_num].is_varargs()
})
.map(|(arg_num, arg)| match &arg {
Expr::Var(var_index) => var_pool.get(*var_index).name.as_ref(),
Expr::Literal(_) => {
let n = inst.imm_opnums.iter().position(|&i| i == arg_num).unwrap();
iform.imm_fields[n].member
}
})
.collect::<Vec<_>>()
.join(", ");
// May we need "args" in the values consumed by predicates?
let emit_args = iform.num_value_operands >= 1 || iform.has_value_list;
// We need a tuple:
// - if there's at least one value operand, then we emit a variable for the value, and the
// value list as args.
// - otherwise, if there's the count of immediate operands added to the presence of a value list exceeds one.
let need_tuple = if iform.num_value_operands >= 1 {
true
} else {
let mut imm_and_varargs = inst
.operands_in
.iter()
.filter(|op| op.is_immediate_or_entityref())
.count();
if iform.has_value_list {
imm_and_varargs += 1;
}
imm_and_varargs > 1
};
let maybe_args = if emit_args { ", args" } else { "" };
let defined_values = format!("{}{}", arg_names, maybe_args);
let tuple_or_value = if need_tuple {
format!("({})", defined_values)
} else {
defined_values
};
fmtln!(
fmt,
"let {} = if let ir::InstructionData::{} {{",
tuple_or_value,
iform.name
);
fmt.indent(|fmt| {
// Fields are encoded directly.
for field in &iform.imm_fields {
fmtln!(fmt, "{},", field.member);
}
if iform.has_value_list || iform.num_value_operands > 1 {
fmt.line("ref args,");
} else if iform.num_value_operands == 1 {
fmt.line("arg,");
}
fmt.line("..");
fmt.outdented_line("} = pos.func.dfg[inst] {");
if iform.has_value_list {
fmt.line("let args = args.as_slice(&pos.func.dfg.value_lists);");
} else if iform.num_value_operands == 1 {
fmt.line("let args = [arg];")
}
// Generate the values for the tuple.
let emit_one_value =
|fmt: &mut Formatter, needs_comma: bool, op_num: usize, op: &Operand| {
let comma = if needs_comma { "," } else { "" };
if op.is_immediate_or_entityref() {
let n = inst.imm_opnums.iter().position(|&i| i == op_num).unwrap();
fmtln!(fmt, "{}{}", iform.imm_fields[n].member, comma);
} else if op.is_value() {
let n = inst.value_opnums.iter().position(|&i| i == op_num).unwrap();
fmtln!(fmt, "pos.func.dfg.resolve_aliases(args[{}]),", n);
} else {
// This is a value list argument or a varargs.
assert!(iform.has_value_list || op.is_varargs());
}
};
if need_tuple {
fmt.line("(");
fmt.indent(|fmt| {
for (op_num, op) in inst.operands_in.iter().enumerate() {
let needs_comma = emit_args || op_num + 1 < inst.operands_in.len();
emit_one_value(fmt, needs_comma, op_num, op);
}
if emit_args {
fmt.line("args");
}
});
fmt.line(")");
} else {
// Only one of these can be true at the same time, otherwise we'd need a tuple.
emit_one_value(fmt, false, 0, &inst.operands_in[0]);
if emit_args {
fmt.line("args");
}
}
fmt.outdented_line("} else {");
fmt.line(r#"unreachable!("bad instruction format")"#);
});
fmtln!(fmt, "};");
fmt.empty_line();
assert_eq!(inst.operands_in.len(), apply.args.len());
for (i, op) in inst.operands_in.iter().enumerate() {
if op.is_varargs() {
let name = &var_pool
.get(apply.args[i].maybe_var().expect("vararg without name"))
.name;
let n = inst
.imm_opnums
.iter()
.chain(inst.value_opnums.iter())
.max()
.copied()
.unwrap_or(0);
fmtln!(fmt, "let {} = &Vec::from(&args[{}..]);", name, n);
}
}
for &op_num in &inst.value_opnums {
let arg = &apply.args[op_num];
if let Some(var_index) = arg.maybe_var() {
let var = var_pool.get(var_index);
if var.has_free_typevar() {
fmtln!(
fmt,
"let typeof_{} = pos.func.dfg.value_type({});",
var.name,
var.name
);
}
}
}
// If the definition creates results, detach the values and place them in locals.
let mut replace_inst = false;
if !def.defined_vars.is_empty() {
if def.defined_vars
== def_pool
.get(var_pool.get(def.defined_vars[0]).dst_def.unwrap())
.defined_vars
{
// Special case: The instruction replacing node defines the exact same values.
fmt.comment(format!(
"Results handled by {}.",
def_pool
.get(var_pool.get(def.defined_vars[0]).dst_def.unwrap())
.to_comment_string(var_pool)
));
fmt.line("let r = pos.func.dfg.inst_results(inst);");
for (i, &var_index) in def.defined_vars.iter().enumerate() {
let var = var_pool.get(var_index);
fmtln!(fmt, "let {} = &r[{}];", var.name, i);
fmtln!(
fmt,
"let typeof_{} = pos.func.dfg.value_type(*{});",
var.name,
var.name
);
}
replace_inst = true;
} else {
// Boring case: Detach the result values, capture them in locals.
for &var_index in &def.defined_vars {
fmtln!(fmt, "let {};", var_pool.get(var_index).name);
}
fmt.line("{");
fmt.indent(|fmt| {
fmt.line("let r = pos.func.dfg.inst_results(inst);");
for i in 0..def.defined_vars.len() {
let var = var_pool.get(def.defined_vars[i]);
fmtln!(fmt, "{} = r[{}];", var.name, i);
}
});
fmt.line("}");
for &var_index in &def.defined_vars {
let var = var_pool.get(var_index);
if var.has_free_typevar() {
fmtln!(
fmt,
"let typeof_{} = pos.func.dfg.value_type({});",
var.name,
var.name
);
}
}
}
}
replace_inst
}
fn build_derived_expr(tv: &TypeVar) -> String {
let base = match &tv.base {
Some(base) => base,
None => {
assert!(tv.name.starts_with("typeof_"));
return format!("Some({})", tv.name);
}
};
let base_expr = build_derived_expr(&base.type_var);
format!(
"{}.map(|t: crate::ir::Type| t.{}())",
base_expr,
base.derived_func.name()
)
}
/// Emit rust code for the given check.
///
/// The emitted code is a statement redefining the `predicate` variable like this:
/// let predicate = predicate && ...
fn emit_runtime_typecheck<'a>(
constraint: &'a Constraint,
type_sets: &mut UniqueTable<'a, TypeSet>,
fmt: &mut Formatter,
) {
match constraint {
Constraint::InTypeset(tv, ts) => {
let ts_index = type_sets.add(&ts);
fmt.comment(format!(
"{} must belong to {:?}",
tv.name,
type_sets.get(ts_index)
));
fmtln!(
fmt,
"let predicate = predicate && TYPE_SETS[{}].contains({});",
ts_index,
tv.name
);
}
Constraint::Eq(tv1, tv2) => {
fmtln!(
fmt,
"let predicate = predicate && match ({}, {}) {{",
build_derived_expr(tv1),
build_derived_expr(tv2)
);
fmt.indent(|fmt| {
fmt.line("(Some(a), Some(b)) => a == b,");
fmt.comment("On overflow, constraint doesn\'t apply");
fmt.line("_ => false,");
});
fmtln!(fmt, "};");
}
Constraint::WiderOrEq(tv1, tv2) => {
fmtln!(
fmt,
"let predicate = predicate && match ({}, {}) {{",
build_derived_expr(tv1),
build_derived_expr(tv2)
);
fmt.indent(|fmt| {
fmt.line("(Some(a), Some(b)) => a.wider_or_equal(b),");
fmt.comment("On overflow, constraint doesn\'t apply");
fmt.line("_ => false,");
});
fmtln!(fmt, "};");
}
}
}
/// Determine if `node` represents one of the value splitting instructions: `isplit` or `vsplit.
/// These instructions are lowered specially by the `legalize::split` module.
fn is_value_split(def: &Def) -> bool {
let name = &def.apply.inst.name;
name == "isplit" || name == "vsplit"
}
fn emit_dst_inst(def: &Def, def_pool: &DefPool, var_pool: &VarPool, fmt: &mut Formatter) {
let defined_vars = {
let vars = def
.defined_vars
.iter()
.map(|&var_index| var_pool.get(var_index).name.as_ref())
.collect::<Vec<&str>>();
if vars.len() == 1 {
vars[0].to_string()
} else {
format!("({})", vars.join(", "))
}
};
if is_value_split(def) {
// Split instructions are not emitted with the builder, but by calling special functions in
// the `legalizer::split` module. These functions will eliminate concat-split patterns.
fmt.line("let curpos = pos.position();");
fmt.line("let srcloc = pos.srcloc();");
fmtln!(
fmt,
"let {} = split::{}(pos.func, cfg, curpos, srcloc, {});",
defined_vars,
def.apply.inst.snake_name(),
def.apply.args[0].to_rust_code(var_pool)
);
return;
}
if def.defined_vars.is_empty() {
// This node doesn't define any values, so just insert the new instruction.
fmtln!(
fmt,
"pos.ins().{};",
def.apply.rust_builder(&def.defined_vars, var_pool)
);
return;
}
if let Some(src_def0) = var_pool.get(def.defined_vars[0]).src_def {
if def.defined_vars == def_pool.get(src_def0).defined_vars {
// The replacement instruction defines the exact same values as the source pattern.
// Unwrapping would have left the results intact. Replace the whole instruction.
fmtln!(
fmt,
"let {} = pos.func.dfg.replace(inst).{};",
defined_vars,
def.apply.rust_builder(&def.defined_vars, var_pool)
);
// We need to bump the cursor so following instructions are inserted *after* the
// replaced instruction.
fmt.line("if pos.current_inst() == Some(inst) {");
fmt.indent(|fmt| {
fmt.line("pos.next_inst();");
});
fmt.line("}");
return;
}
}
// Insert a new instruction.
let mut builder = format!("let {} = pos.ins()", defined_vars);
if def.defined_vars.len() == 1 && var_pool.get(def.defined_vars[0]).is_output() {
// Reuse the single source result value.
builder = format!(
"{}.with_result({})",
builder,
var_pool.get(def.defined_vars[0]).to_rust_code()
);
} else if def
.defined_vars
.iter()
.any(|&var_index| var_pool.get(var_index).is_output())
{
// There are more than one output values that can be reused.
let array = def
.defined_vars
.iter()
.map(|&var_index| {
let var = var_pool.get(var_index);
if var.is_output() {
format!("Some({})", var.name)
} else {
"None".into()
}
})
.collect::<Vec<_>>()
.join(", ");
builder = format!("{}.with_results([{}])", builder, array);
}
fmtln!(
fmt,
"{}.{};",
builder,
def.apply.rust_builder(&def.defined_vars, var_pool)
);
}
/// Emit code for `transform`, assuming that the opcode of transform'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.
fn gen_transform<'a>(
replace_inst: bool,
transform: &'a Transform,
type_sets: &mut UniqueTable<'a, TypeSet>,
fmt: &mut Formatter,
) {
// Evaluate the instruction predicate if any.
let apply = &transform.def_pool.get(transform.src).apply;
let inst_predicate = apply
.inst_predicate_with_ctrl_typevar(&transform.var_pool)
.rust_predicate("pos.func");
let has_extra_constraints = !transform.type_env.constraints.is_empty();
if has_extra_constraints {
// Extra constraints rely on the predicate being a variable that we can rebind as we add
// more constraint predicates.
if let Some(pred) = &inst_predicate {
fmt.multi_line(&format!("let predicate = {};", pred));
} else {
fmt.line("let predicate = true;");
}
}
// Emit any runtime checks; these will rebind `predicate` emitted right above.
for constraint in &transform.type_env.constraints {
emit_runtime_typecheck(constraint, type_sets, fmt);
}
let do_expand = |fmt: &mut Formatter| {
// Emit any constants that must be created before use.
for (name, value) in transform.const_pool.iter() {
fmtln!(
fmt,
"let {} = pos.func.dfg.constants.insert(vec!{:?}.into());",
name,
value
);
}
// If we are adding some blocks, we need to recall the original block, such that we can
// recompute it.
if !transform.block_pool.is_empty() {
fmt.line("let orig_block = pos.current_block().unwrap();");
}
// If we're going to delete `inst`, we need to detach its results first so they can be
// reattached during pattern expansion.
if !replace_inst {
fmt.line("pos.func.dfg.clear_results(inst);");
}
// Emit new block creation.
for block in &transform.block_pool {
let var = transform.var_pool.get(block.name);
fmtln!(fmt, "let {} = pos.func.dfg.make_block();", var.name);
}
// Emit the destination pattern.
for &def_index in &transform.dst {
if let Some(block) = transform.block_pool.get(def_index) {
let var = transform.var_pool.get(block.name);
fmtln!(fmt, "pos.insert_block({});", var.name);
}
emit_dst_inst(
transform.def_pool.get(def_index),
&transform.def_pool,
&transform.var_pool,
fmt,
);
}
// Insert a new block after the last instruction, if needed.
let def_next_index = transform.def_pool.next_index();
if let Some(block) = transform.block_pool.get(def_next_index) {
let var = transform.var_pool.get(block.name);
fmtln!(fmt, "pos.insert_block({});", var.name);
}
// Delete the original instruction if we didn't have an opportunity to replace it.
if !replace_inst {
fmt.line("let removed = pos.remove_inst();");
fmt.line("debug_assert_eq!(removed, inst);");
}
if transform.block_pool.is_empty() {
if transform.def_pool.get(transform.src).apply.inst.is_branch {
// A branch might have been legalized into multiple branches, so we need to recompute
// the cfg.
fmt.line("cfg.recompute_block(pos.func, pos.current_block().unwrap());");
}
} else {
// Update CFG for the new blocks.
fmt.line("cfg.recompute_block(pos.func, orig_block);");
for block in &transform.block_pool {
let var = transform.var_pool.get(block.name);
fmtln!(fmt, "cfg.recompute_block(pos.func, {});", var.name);
}
}
fmt.line("return true;");
};
// Guard the actual expansion by `predicate`.
if has_extra_constraints {
fmt.line("if predicate {");
fmt.indent(|fmt| {
do_expand(fmt);
});
fmt.line("}");
} else if let Some(pred) = &inst_predicate {
fmt.multi_line(&format!("if {} {{", pred));
fmt.indent(|fmt| {
do_expand(fmt);
});
fmt.line("}");
} else {
// Unconditional transform (there was no predicate), just emit it.
do_expand(fmt);
}
}
fn gen_transform_group<'a>(
group: &'a TransformGroup,
transform_groups: &TransformGroups,
type_sets: &mut UniqueTable<'a, TypeSet>,
fmt: &mut Formatter,
) {
fmt.doc_comment(group.doc);
fmt.line("#[allow(unused_variables,unused_assignments,unused_imports,non_snake_case)]");
// Function arguments.
fmtln!(fmt, "pub fn {}(", group.name);
fmt.indent(|fmt| {
fmt.line("inst: crate::ir::Inst,");
fmt.line("func: &mut crate::ir::Function,");
fmt.line("cfg: &mut crate::flowgraph::ControlFlowGraph,");
fmt.line("isa: &dyn crate::isa::TargetIsa,");
});
fmtln!(fmt, ") -> bool {");
// Function body.
fmt.indent(|fmt| {
fmt.line("use crate::ir::InstBuilder;");
fmt.line("use crate::cursor::{Cursor, FuncCursor};");
fmt.line("let mut pos = FuncCursor::new(func).at_inst(inst);");
fmt.line("pos.use_srcloc(inst);");
// Group the transforms by opcode so we can generate a big switch.
// Preserve ordering.
let mut inst_to_transforms = HashMap::new();
for transform in &group.transforms {
let def_index = transform.src;
let inst = &transform.def_pool.get(def_index).apply.inst;
inst_to_transforms
.entry(inst.camel_name.clone())
.or_insert_with(Vec::new)
.push(transform);
}
let mut sorted_inst_names = Vec::from_iter(inst_to_transforms.keys());
sorted_inst_names.sort();
fmt.line("{");
fmt.indent(|fmt| {
fmt.line("match pos.func.dfg[inst].opcode() {");
fmt.indent(|fmt| {
for camel_name in sorted_inst_names {
fmtln!(fmt, "ir::Opcode::{} => {{", camel_name);
fmt.indent(|fmt| {
let transforms = inst_to_transforms.get(camel_name).unwrap();
// Unwrap the source instruction, create local variables for the input variables.
let replace_inst = unwrap_inst(&transforms[0], fmt);
fmt.empty_line();
for (i, transform) in transforms.iter().enumerate() {
if i > 0 {
fmt.empty_line();
}
gen_transform(replace_inst, transform, type_sets, fmt);
}
});
fmtln!(fmt, "}");
fmt.empty_line();
}
// Emit the custom transforms. The Rust compiler will complain about any overlap with
// the normal transforms.
let mut sorted_custom_legalizes = Vec::from_iter(&group.custom_legalizes);
sorted_custom_legalizes.sort();
for (inst_camel_name, func_name) in sorted_custom_legalizes {
fmtln!(fmt, "ir::Opcode::{} => {{", inst_camel_name);
fmt.indent(|fmt| {
fmtln!(fmt, "{}(inst, func, cfg, isa);", func_name);
fmt.line("return true;");
});
fmtln!(fmt, "}");
fmt.empty_line();
}
// We'll assume there are uncovered opcodes.
fmt.line("_ => {},");
});
fmt.line("}");
});
fmt.line("}");
// If we fall through, nothing was expanded; call the chain if any.
match &group.chain_with {
Some(group_id) => fmtln!(
fmt,
"{}(inst, func, cfg, isa)",
transform_groups.get(*group_id).rust_name()
),
None => fmt.line("false"),
};
});
fmtln!(fmt, "}");
fmt.empty_line();
}
/// Generate legalization functions for `isa` and add any shared `TransformGroup`s
/// encountered to `shared_groups`.
///
/// Generate `TYPE_SETS` and `LEGALIZE_ACTIONS` tables.
fn gen_isa(
isa: &TargetIsa,
transform_groups: &TransformGroups,
shared_group_names: &mut HashSet<&'static str>,
fmt: &mut Formatter,
) {
let mut type_sets = UniqueTable::new();
for group_index in isa.transitive_transform_groups(transform_groups) {
let group = transform_groups.get(group_index);
match group.isa_name {
Some(isa_name) => {
assert!(
isa_name == isa.name,
"ISA-specific legalizations must be used by the same ISA"
);
gen_transform_group(group, transform_groups, &mut type_sets, fmt);
}
None => {
shared_group_names.insert(group.name);
}
}
}
gen_typesets_table(&type_sets, fmt);
let direct_groups = isa.direct_transform_groups();
fmtln!(
fmt,
"pub static LEGALIZE_ACTIONS: [isa::Legalize; {}] = [",
direct_groups.len()
);
fmt.indent(|fmt| {
for &group_index in direct_groups {
fmtln!(fmt, "{},", transform_groups.get(group_index).rust_name());
}
});
fmtln!(fmt, "];");
}
/// Generate the legalizer files.
pub(crate) fn generate(
isas: &[TargetIsa],
transform_groups: &TransformGroups,
extra_legalization_groups: &[&'static str],
filename_prefix: &str,
out_dir: &str,
) -> Result<(), error::Error> {
let mut shared_group_names = HashSet::new();
for isa in isas {
let mut fmt = Formatter::new();
gen_isa(isa, transform_groups, &mut shared_group_names, &mut fmt);
fmt.update_file(format!("{}-{}.rs", filename_prefix, isa.name), out_dir)?;
}
// Add extra legalization groups that were explicitly requested.
for group in extra_legalization_groups {
shared_group_names.insert(group);
}
// Generate shared legalize groups.
let mut fmt = Formatter::new();
// Generate shared legalize groups.
let mut type_sets = UniqueTable::new();
let mut sorted_shared_group_names = Vec::from_iter(shared_group_names);
sorted_shared_group_names.sort();
for group_name in &sorted_shared_group_names {
let group = transform_groups.by_name(group_name);
gen_transform_group(group, transform_groups, &mut type_sets, &mut fmt);
}
gen_typesets_table(&type_sets, &mut fmt);
fmt.update_file(format!("{}r.rs", filename_prefix), out_dir)?;
Ok(())
}

View File

@@ -1,148 +0,0 @@
//! Generate the ISA-specific registers.
use crate::cdsl::isa::TargetIsa;
use crate::cdsl::regs::{RegBank, RegClass};
use crate::error;
use crate::srcgen::Formatter;
use cranelift_entity::EntityRef;
fn gen_regbank(fmt: &mut Formatter, reg_bank: &RegBank) {
let names = if !reg_bank.names.is_empty() {
format!(r#""{}""#, reg_bank.names.join(r#"", ""#))
} else {
"".to_string()
};
fmtln!(fmt, "RegBank {");
fmt.indent(|fmt| {
fmtln!(fmt, r#"name: "{}","#, reg_bank.name);
fmtln!(fmt, "first_unit: {},", reg_bank.first_unit);
fmtln!(fmt, "units: {},", reg_bank.units);
fmtln!(fmt, "names: &[{}],", names);
fmtln!(fmt, r#"prefix: "{}","#, reg_bank.prefix);
fmtln!(fmt, "first_toprc: {},", reg_bank.toprcs[0].index());
fmtln!(fmt, "num_toprcs: {},", reg_bank.toprcs.len());
fmtln!(
fmt,
"pressure_tracking: {},",
if reg_bank.pressure_tracking {
"true"
} else {
"false"
}
);
});
fmtln!(fmt, "},");
}
fn gen_regclass(isa: &TargetIsa, reg_class: &RegClass, fmt: &mut Formatter) {
let reg_bank = isa.regs.banks.get(reg_class.bank).unwrap();
let mask: Vec<String> = reg_class
.mask(reg_bank.first_unit)
.iter()
.map(|x| format!("0x{:08x}", x))
.collect();
let mask = mask.join(", ");
fmtln!(
fmt,
"pub static {}_DATA: RegClassData = RegClassData {{",
reg_class.name
);
fmt.indent(|fmt| {
fmtln!(fmt, r#"name: "{}","#, reg_class.name);
fmtln!(fmt, "index: {},", reg_class.index.index());
fmtln!(fmt, "width: {},", reg_class.width);
fmtln!(fmt, "bank: {},", reg_class.bank.index());
fmtln!(fmt, "toprc: {},", reg_class.toprc.index());
fmtln!(fmt, "first: {},", reg_bank.first_unit + reg_class.start);
fmtln!(fmt, "subclasses: {:#x},", reg_class.subclass_mask());
fmtln!(fmt, "mask: [{}],", mask);
fmtln!(
fmt,
"pinned_reg: {:?},",
reg_bank
.pinned_reg
.map(|index| index + reg_bank.first_unit as u16 + reg_class.start as u16)
);
fmtln!(fmt, "info: &INFO,");
});
fmtln!(fmt, "};");
fmtln!(fmt, "#[allow(dead_code)]");
fmtln!(
fmt,
"pub static {}: RegClass = &{}_DATA;",
reg_class.name,
reg_class.name
);
}
fn gen_regbank_units(reg_bank: &RegBank, fmt: &mut Formatter) {
for unit in 0..reg_bank.units {
let v = unit + reg_bank.first_unit;
if (unit as usize) < reg_bank.names.len() {
fmtln!(fmt, "{} = {},", reg_bank.names[unit as usize], v);
continue;
}
fmtln!(fmt, "{}{} = {},", reg_bank.prefix, unit, v);
}
}
fn gen_isa(isa: &TargetIsa, fmt: &mut Formatter) {
// Emit RegInfo.
fmtln!(fmt, "pub static INFO: RegInfo = RegInfo {");
fmt.indent(|fmt| {
fmtln!(fmt, "banks: &[");
// Bank descriptors.
fmt.indent(|fmt| {
for reg_bank in isa.regs.banks.values() {
gen_regbank(fmt, &reg_bank);
}
});
fmtln!(fmt, "],");
// References to register classes.
fmtln!(fmt, "classes: &[");
fmt.indent(|fmt| {
for reg_class in isa.regs.classes.values() {
fmtln!(fmt, "&{}_DATA,", reg_class.name);
}
});
fmtln!(fmt, "],");
});
fmtln!(fmt, "};");
// Register class descriptors.
for rc in isa.regs.classes.values() {
gen_regclass(&isa, rc, fmt);
}
// Emit constants for all the register units.
fmtln!(fmt, "#[allow(dead_code, non_camel_case_types)]");
fmtln!(fmt, "#[derive(Clone, Copy)]");
fmtln!(fmt, "pub enum RU {");
fmt.indent(|fmt| {
for reg_bank in isa.regs.banks.values() {
gen_regbank_units(reg_bank, fmt);
}
});
fmtln!(fmt, "}");
// Emit Into conversion for the RU class.
fmtln!(fmt, "impl Into<RegUnit> for RU {");
fmt.indent(|fmt| {
fmtln!(fmt, "fn into(self) -> RegUnit {");
fmt.indent(|fmt| {
fmtln!(fmt, "self as RegUnit");
});
fmtln!(fmt, "}");
});
fmtln!(fmt, "}");
}
pub(crate) fn generate(isa: &TargetIsa, filename: &str, out_dir: &str) -> Result<(), error::Error> {
let mut fmt = Formatter::new();
gen_isa(&isa, &mut fmt);
fmt.update_file(filename, out_dir)?;
Ok(())
}

View File

@@ -12,23 +12,16 @@ use crate::error;
use crate::srcgen;
/// Emit a constant definition of a single value type.
fn emit_type(ty: &cdsl_types::ValueType, fmt: &mut srcgen::Formatter) -> Result<(), error::Error> {
fn emit_type(ty: &cdsl_types::ValueType, fmt: &mut srcgen::Formatter) {
let name = ty.to_string().to_uppercase();
let number = ty.number().ok_or_else(|| {
error::Error::with_msg(format!(
"Could not emit type `{}` which has no number.",
name
))
})?;
let number = ty.number();
fmt.doc_comment(&ty.doc());
fmtln!(fmt, "pub const {}: Type = Type({:#x});\n", name, number);
Ok(())
}
/// Emit definition for all vector types with `bits` total size.
fn emit_vectors(bits: u64, fmt: &mut srcgen::Formatter) -> Result<(), error::Error> {
fn emit_vectors(bits: u64, fmt: &mut srcgen::Formatter) {
let vec_size: u64 = bits / 8;
for vec in cdsl_types::ValueType::all_lane_types()
.map(|ty| (ty, cdsl_types::ValueType::from(ty).membytes()))
@@ -36,41 +29,37 @@ fn emit_vectors(bits: u64, fmt: &mut srcgen::Formatter) -> Result<(), error::Err
.map(|(ty, lane_size)| (ty, vec_size / lane_size))
.map(|(ty, lanes)| cdsl_types::VectorType::new(ty, lanes))
{
emit_type(&cdsl_types::ValueType::from(vec), fmt)?;
emit_type(&cdsl_types::ValueType::from(vec), fmt);
}
Ok(())
}
/// Emit types using the given formatter object.
fn emit_types(fmt: &mut srcgen::Formatter) -> Result<(), error::Error> {
fn emit_types(fmt: &mut srcgen::Formatter) {
// Emit all of the special types, such as types for CPU flags.
for spec in cdsl_types::ValueType::all_special_types().map(cdsl_types::ValueType::from) {
emit_type(&spec, fmt)?;
emit_type(&spec, fmt);
}
// Emit all of the lane types, such integers, floats, and booleans.
for ty in cdsl_types::ValueType::all_lane_types().map(cdsl_types::ValueType::from) {
emit_type(&ty, fmt)?;
emit_type(&ty, fmt);
}
// Emit all reference types.
for ty in cdsl_types::ValueType::all_reference_types().map(cdsl_types::ValueType::from) {
emit_type(&ty, fmt)?;
emit_type(&ty, fmt);
}
// Emit vector definitions for common SIMD sizes.
for vec_size in &[64_u64, 128, 256, 512] {
emit_vectors(*vec_size, fmt)?;
emit_vectors(*vec_size, fmt);
}
Ok(())
}
/// Generate the types file.
pub(crate) fn generate(filename: &str, out_dir: &str) -> Result<(), error::Error> {
let mut fmt = srcgen::Formatter::new();
emit_types(&mut fmt)?;
emit_types(&mut fmt);
fmt.update_file(filename, out_dir)?;
Ok(())
}

View File

@@ -0,0 +1,15 @@
use crate::cdsl::isa::TargetIsa;
use crate::cdsl::settings::{SettingGroup, SettingGroupBuilder};
use crate::shared::Definitions as SharedDefinitions;
fn define_settings(_shared: &SettingGroup) -> SettingGroup {
let setting = SettingGroupBuilder::new("arm32");
setting.build()
}
pub(crate) fn define(shared_defs: &mut SharedDefinitions) -> TargetIsa {
let settings = define_settings(&shared_defs.settings);
TargetIsa::new("arm32", settings)
}

View File

@@ -1,71 +0,0 @@
use crate::cdsl::instructions::InstructionPredicateMap;
use crate::cdsl::isa::TargetIsa;
use crate::cdsl::recipes::Recipes;
use crate::cdsl::regs::{IsaRegs, IsaRegsBuilder, RegBankBuilder, RegClassBuilder};
use crate::cdsl::settings::{SettingGroup, SettingGroupBuilder};
use crate::shared::Definitions as SharedDefinitions;
fn define_settings(_shared: &SettingGroup) -> SettingGroup {
let setting = SettingGroupBuilder::new("arm32");
setting.build()
}
fn define_regs() -> IsaRegs {
let mut regs = IsaRegsBuilder::new();
let builder = RegBankBuilder::new("FloatRegs", "s")
.units(64)
.track_pressure(true);
let float_regs = regs.add_bank(builder);
let builder = RegBankBuilder::new("IntRegs", "r")
.units(16)
.track_pressure(true);
let int_regs = regs.add_bank(builder);
let builder = RegBankBuilder::new("FlagRegs", "")
.units(1)
.names(vec!["nzcv"])
.track_pressure(false);
let flag_reg = regs.add_bank(builder);
let builder = RegClassBuilder::new_toplevel("S", float_regs).count(32);
regs.add_class(builder);
let builder = RegClassBuilder::new_toplevel("D", float_regs).width(2);
regs.add_class(builder);
let builder = RegClassBuilder::new_toplevel("Q", float_regs).width(4);
regs.add_class(builder);
let builder = RegClassBuilder::new_toplevel("GPR", int_regs);
regs.add_class(builder);
let builder = RegClassBuilder::new_toplevel("FLAG", flag_reg);
regs.add_class(builder);
regs.build()
}
pub(crate) fn define(shared_defs: &mut SharedDefinitions) -> TargetIsa {
let settings = define_settings(&shared_defs.settings);
let regs = define_regs();
let cpu_modes = vec![];
// TODO implement arm32 recipes.
let recipes = Recipes::new();
// TODO implement arm32 encodings and predicates.
let encodings_predicates = InstructionPredicateMap::new();
TargetIsa::new(
"arm32",
settings,
regs,
recipes,
cpu_modes,
encodings_predicates,
)
}

View File

@@ -0,0 +1,18 @@
use crate::cdsl::isa::TargetIsa;
use crate::cdsl::settings::{SettingGroup, SettingGroupBuilder};
use crate::shared::Definitions as SharedDefinitions;
fn define_settings(_shared: &SettingGroup) -> SettingGroup {
let mut setting = SettingGroupBuilder::new("arm64");
let has_lse = setting.add_bool("has_lse", "Has Large System Extensions support.", "", false);
setting.add_predicate("use_lse", predicate!(has_lse));
setting.build()
}
pub(crate) fn define(shared_defs: &mut SharedDefinitions) -> TargetIsa {
let settings = define_settings(&shared_defs.settings);
TargetIsa::new("arm64", settings)
}

View File

@@ -1,70 +0,0 @@
use crate::cdsl::instructions::InstructionPredicateMap;
use crate::cdsl::isa::TargetIsa;
use crate::cdsl::recipes::Recipes;
use crate::cdsl::regs::{IsaRegs, IsaRegsBuilder, RegBankBuilder, RegClassBuilder};
use crate::cdsl::settings::{SettingGroup, SettingGroupBuilder};
use crate::shared::Definitions as SharedDefinitions;
fn define_settings(_shared: &SettingGroup) -> SettingGroup {
let mut setting = SettingGroupBuilder::new("arm64");
let has_lse = setting.add_bool("has_lse", "Has Large System Extensions support.", "", false);
setting.add_predicate("use_lse", predicate!(has_lse));
setting.build()
}
fn define_registers() -> IsaRegs {
let mut regs = IsaRegsBuilder::new();
// The `x31` regunit serves as the stack pointer / zero register depending on context. We
// reserve it and don't model the difference.
let builder = RegBankBuilder::new("IntRegs", "x")
.units(32)
.track_pressure(true);
let int_regs = regs.add_bank(builder);
let builder = RegBankBuilder::new("FloatRegs", "v")
.units(32)
.track_pressure(true);
let float_regs = regs.add_bank(builder);
let builder = RegBankBuilder::new("FlagRegs", "")
.units(1)
.names(vec!["nzcv"])
.track_pressure(false);
let flag_reg = regs.add_bank(builder);
let builder = RegClassBuilder::new_toplevel("GPR", int_regs);
regs.add_class(builder);
let builder = RegClassBuilder::new_toplevel("FPR", float_regs);
regs.add_class(builder);
let builder = RegClassBuilder::new_toplevel("FLAG", flag_reg);
regs.add_class(builder);
regs.build()
}
pub(crate) fn define(shared_defs: &mut SharedDefinitions) -> TargetIsa {
let settings = define_settings(&shared_defs.settings);
let regs = define_registers();
let cpu_modes = vec![];
// TODO implement arm64 recipes.
let recipes = Recipes::new();
// TODO implement arm64 encodings and predicates.
let encodings_predicates = InstructionPredicateMap::new();
TargetIsa::new(
"arm64",
settings,
regs,
recipes,
cpu_modes,
encodings_predicates,
)
}

View File

@@ -5,14 +5,12 @@ use std::fmt;
mod arm32;
mod arm64;
mod riscv;
mod s390x;
pub(crate) mod x86;
/// Represents known ISA target.
#[derive(PartialEq, Copy, Clone)]
pub enum Isa {
Riscv,
X86,
Arm32,
Arm64,
@@ -31,7 +29,6 @@ impl Isa {
/// Creates isa target from arch.
pub fn from_arch(arch: &str) -> Option<Self> {
match arch {
"riscv" => Some(Isa::Riscv),
"aarch64" => Some(Isa::Arm64),
"s390x" => Some(Isa::S390x),
x if ["x86_64", "i386", "i586", "i686"].contains(&x) => Some(Isa::X86),
@@ -42,7 +39,7 @@ impl Isa {
/// Returns all supported isa targets.
pub fn all() -> &'static [Isa] {
&[Isa::Riscv, Isa::X86, Isa::Arm32, Isa::Arm64, Isa::S390x]
&[Isa::X86, Isa::Arm32, Isa::Arm64, Isa::S390x]
}
}
@@ -50,7 +47,6 @@ impl fmt::Display for Isa {
// These names should be kept in sync with the crate features.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Isa::Riscv => write!(f, "riscv"),
Isa::X86 => write!(f, "x86"),
Isa::Arm32 => write!(f, "arm32"),
Isa::Arm64 => write!(f, "arm64"),
@@ -62,7 +58,6 @@ impl fmt::Display for Isa {
pub(crate) fn define(isas: &[Isa], shared_defs: &mut SharedDefinitions) -> Vec<TargetIsa> {
isas.iter()
.map(|isa| match isa {
Isa::Riscv => riscv::define(shared_defs),
Isa::X86 => x86::define(shared_defs),
Isa::Arm32 => arm32::define(shared_defs),
Isa::Arm64 => arm64::define(shared_defs),

View File

@@ -1,431 +0,0 @@
use crate::cdsl::ast::{Apply, Expr, Literal, VarPool};
use crate::cdsl::encodings::{Encoding, EncodingBuilder};
use crate::cdsl::instructions::{
Bindable, BoundInstruction, InstSpec, InstructionPredicateNode, InstructionPredicateRegistry,
};
use crate::cdsl::recipes::{EncodingRecipeNumber, Recipes};
use crate::cdsl::settings::SettingGroup;
use crate::shared::types::Bool::B1;
use crate::shared::types::Float::{F32, F64};
use crate::shared::types::Int::{I16, I32, I64, I8};
use crate::shared::types::Reference::{R32, R64};
use crate::shared::Definitions as SharedDefinitions;
use super::recipes::RecipeGroup;
pub(crate) struct PerCpuModeEncodings<'defs> {
pub inst_pred_reg: InstructionPredicateRegistry,
pub enc32: Vec<Encoding>,
pub enc64: Vec<Encoding>,
recipes: &'defs Recipes,
}
impl<'defs> PerCpuModeEncodings<'defs> {
fn new(recipes: &'defs Recipes) -> Self {
Self {
inst_pred_reg: InstructionPredicateRegistry::new(),
enc32: Vec::new(),
enc64: Vec::new(),
recipes,
}
}
fn enc(
&self,
inst: impl Into<InstSpec>,
recipe: EncodingRecipeNumber,
bits: u16,
) -> EncodingBuilder {
EncodingBuilder::new(inst.into(), recipe, bits)
}
fn add32(&mut self, encoding: EncodingBuilder) {
self.enc32
.push(encoding.build(self.recipes, &mut self.inst_pred_reg));
}
fn add64(&mut self, encoding: EncodingBuilder) {
self.enc64
.push(encoding.build(self.recipes, &mut self.inst_pred_reg));
}
}
// 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.
fn load_bits(funct3: u16) -> u16 {
assert!(funct3 <= 0b111);
funct3 << 5
}
fn store_bits(funct3: u16) -> u16 {
assert!(funct3 <= 0b111);
0b01000 | (funct3 << 5)
}
fn branch_bits(funct3: u16) -> u16 {
assert!(funct3 <= 0b111);
0b11000 | (funct3 << 5)
}
fn jalr_bits() -> u16 {
// This was previously accepting an argument funct3 of 3 bits and used the following formula:
//0b11001 | (funct3 << 5)
0b11001
}
fn jal_bits() -> u16 {
0b11011
}
fn opimm_bits(funct3: u16, funct7: u16) -> u16 {
assert!(funct3 <= 0b111);
0b00100 | (funct3 << 5) | (funct7 << 8)
}
fn opimm32_bits(funct3: u16, funct7: u16) -> u16 {
assert!(funct3 <= 0b111);
0b00110 | (funct3 << 5) | (funct7 << 8)
}
fn op_bits(funct3: u16, funct7: u16) -> u16 {
assert!(funct3 <= 0b111);
assert!(funct7 <= 0b111_1111);
0b01100 | (funct3 << 5) | (funct7 << 8)
}
fn op32_bits(funct3: u16, funct7: u16) -> u16 {
assert!(funct3 <= 0b111);
assert!(funct7 <= 0b111_1111);
0b01110 | (funct3 << 5) | (funct7 << 8)
}
fn lui_bits() -> u16 {
0b01101
}
pub(crate) fn define<'defs>(
shared_defs: &'defs SharedDefinitions,
isa_settings: &SettingGroup,
recipes: &'defs RecipeGroup,
) -> PerCpuModeEncodings<'defs> {
// Instructions shorthands.
let shared = &shared_defs.instructions;
let band = shared.by_name("band");
let band_imm = shared.by_name("band_imm");
let bor = shared.by_name("bor");
let bor_imm = shared.by_name("bor_imm");
let br_icmp = shared.by_name("br_icmp");
let brz = shared.by_name("brz");
let brnz = shared.by_name("brnz");
let bxor = shared.by_name("bxor");
let bxor_imm = shared.by_name("bxor_imm");
let call = shared.by_name("call");
let call_indirect = shared.by_name("call_indirect");
let copy = shared.by_name("copy");
let copy_nop = shared.by_name("copy_nop");
let copy_to_ssa = shared.by_name("copy_to_ssa");
let fill = shared.by_name("fill");
let fill_nop = shared.by_name("fill_nop");
let iadd = shared.by_name("iadd");
let iadd_imm = shared.by_name("iadd_imm");
let iconst = shared.by_name("iconst");
let icmp = shared.by_name("icmp");
let icmp_imm = shared.by_name("icmp_imm");
let imul = shared.by_name("imul");
let ishl = shared.by_name("ishl");
let ishl_imm = shared.by_name("ishl_imm");
let isub = shared.by_name("isub");
let jump = shared.by_name("jump");
let regmove = shared.by_name("regmove");
let spill = shared.by_name("spill");
let sshr = shared.by_name("sshr");
let sshr_imm = shared.by_name("sshr_imm");
let ushr = shared.by_name("ushr");
let ushr_imm = shared.by_name("ushr_imm");
let return_ = shared.by_name("return");
// Recipes shorthands, prefixed with r_.
let r_copytossa = recipes.by_name("copytossa");
let r_fillnull = recipes.by_name("fillnull");
let r_icall = recipes.by_name("Icall");
let r_icopy = recipes.by_name("Icopy");
let r_ii = recipes.by_name("Ii");
let r_iicmp = recipes.by_name("Iicmp");
let r_iret = recipes.by_name("Iret");
let r_irmov = recipes.by_name("Irmov");
let r_iz = recipes.by_name("Iz");
let r_gp_sp = recipes.by_name("GPsp");
let r_gp_fi = recipes.by_name("GPfi");
let r_r = recipes.by_name("R");
let r_ricmp = recipes.by_name("Ricmp");
let r_rshamt = recipes.by_name("Rshamt");
let r_sb = recipes.by_name("SB");
let r_sb_zero = recipes.by_name("SBzero");
let r_stacknull = recipes.by_name("stacknull");
let r_u = recipes.by_name("U");
let r_uj = recipes.by_name("UJ");
let r_uj_call = recipes.by_name("UJcall");
// Predicates shorthands.
let use_m = isa_settings.predicate_by_name("use_m");
// Definitions.
let mut e = PerCpuModeEncodings::new(&recipes.recipes);
// Basic arithmetic binary instructions are encoded in an R-type instruction.
for &(inst, inst_imm, f3, f7) in &[
(iadd, Some(iadd_imm), 0b000, 0b000_0000),
(isub, None, 0b000, 0b010_0000),
(bxor, Some(bxor_imm), 0b100, 0b000_0000),
(bor, Some(bor_imm), 0b110, 0b000_0000),
(band, Some(band_imm), 0b111, 0b000_0000),
] {
e.add32(e.enc(inst.bind(I32), r_r, op_bits(f3, f7)));
e.add64(e.enc(inst.bind(I64), r_r, op_bits(f3, f7)));
// Immediate versions for add/xor/or/and.
if let Some(inst_imm) = inst_imm {
e.add32(e.enc(inst_imm.bind(I32), r_ii, opimm_bits(f3, 0)));
e.add64(e.enc(inst_imm.bind(I64), r_ii, opimm_bits(f3, 0)));
}
}
// 32-bit ops in RV64.
e.add64(e.enc(iadd.bind(I32), r_r, op32_bits(0b000, 0b000_0000)));
e.add64(e.enc(isub.bind(I32), r_r, op32_bits(0b000, 0b010_0000)));
// There are no andiw/oriw/xoriw variations.
e.add64(e.enc(iadd_imm.bind(I32), r_ii, opimm32_bits(0b000, 0)));
// Use iadd_imm with %x0 to materialize constants.
e.add32(e.enc(iconst.bind(I32), r_iz, opimm_bits(0b0, 0)));
e.add64(e.enc(iconst.bind(I32), r_iz, opimm_bits(0b0, 0)));
e.add64(e.enc(iconst.bind(I64), r_iz, opimm_bits(0b0, 0)));
// Dynamic shifts have the same masking semantics as the clif base instructions.
for &(inst, inst_imm, f3, f7) in &[
(ishl, ishl_imm, 0b1, 0b0),
(ushr, ushr_imm, 0b101, 0b0),
(sshr, sshr_imm, 0b101, 0b10_0000),
] {
e.add32(e.enc(inst.bind(I32).bind(I32), r_r, op_bits(f3, f7)));
e.add64(e.enc(inst.bind(I64).bind(I64), r_r, op_bits(f3, f7)));
e.add64(e.enc(inst.bind(I32).bind(I32), r_r, op32_bits(f3, f7)));
// Allow i32 shift amounts in 64-bit shifts.
e.add64(e.enc(inst.bind(I64).bind(I32), r_r, op_bits(f3, f7)));
e.add64(e.enc(inst.bind(I32).bind(I64), r_r, op32_bits(f3, f7)));
// Immediate shifts.
e.add32(e.enc(inst_imm.bind(I32), r_rshamt, opimm_bits(f3, f7)));
e.add64(e.enc(inst_imm.bind(I64), r_rshamt, opimm_bits(f3, f7)));
e.add64(e.enc(inst_imm.bind(I32), r_rshamt, opimm32_bits(f3, f7)));
}
// Signed and unsigned integer 'less than'. There are no 'w' variants for comparing 32-bit
// numbers in RV64.
{
let mut var_pool = VarPool::new();
// Helper that creates an instruction predicate for an instruction in the icmp family.
let mut icmp_instp = |bound_inst: &BoundInstruction,
intcc_field: &'static str|
-> InstructionPredicateNode {
let x = var_pool.create("x");
let y = var_pool.create("y");
let cc = Literal::enumerator_for(&shared_defs.imm.intcc, intcc_field);
Apply::new(
bound_inst.clone().into(),
vec![Expr::Literal(cc), Expr::Var(x), Expr::Var(y)],
)
.inst_predicate(&var_pool)
.unwrap()
};
let icmp_i32 = icmp.bind(I32);
let icmp_i64 = icmp.bind(I64);
e.add32(
e.enc(icmp_i32.clone(), r_ricmp, op_bits(0b010, 0b000_0000))
.inst_predicate(icmp_instp(&icmp_i32, "slt")),
);
e.add64(
e.enc(icmp_i64.clone(), r_ricmp, op_bits(0b010, 0b000_0000))
.inst_predicate(icmp_instp(&icmp_i64, "slt")),
);
e.add32(
e.enc(icmp_i32.clone(), r_ricmp, op_bits(0b011, 0b000_0000))
.inst_predicate(icmp_instp(&icmp_i32, "ult")),
);
e.add64(
e.enc(icmp_i64.clone(), r_ricmp, op_bits(0b011, 0b000_0000))
.inst_predicate(icmp_instp(&icmp_i64, "ult")),
);
// Immediate variants.
let icmp_i32 = icmp_imm.bind(I32);
let icmp_i64 = icmp_imm.bind(I64);
e.add32(
e.enc(icmp_i32.clone(), r_iicmp, opimm_bits(0b010, 0))
.inst_predicate(icmp_instp(&icmp_i32, "slt")),
);
e.add64(
e.enc(icmp_i64.clone(), r_iicmp, opimm_bits(0b010, 0))
.inst_predicate(icmp_instp(&icmp_i64, "slt")),
);
e.add32(
e.enc(icmp_i32.clone(), r_iicmp, opimm_bits(0b011, 0))
.inst_predicate(icmp_instp(&icmp_i32, "ult")),
);
e.add64(
e.enc(icmp_i64.clone(), r_iicmp, opimm_bits(0b011, 0))
.inst_predicate(icmp_instp(&icmp_i64, "ult")),
);
}
// Integer constants with the low 12 bits clear are materialized by lui.
e.add32(e.enc(iconst.bind(I32), r_u, lui_bits()));
e.add64(e.enc(iconst.bind(I32), r_u, lui_bits()));
e.add64(e.enc(iconst.bind(I64), r_u, lui_bits()));
// "M" Standard Extension for Integer Multiplication and Division.
// Gated by the `use_m` flag.
e.add32(
e.enc(imul.bind(I32), r_r, op_bits(0b000, 0b0000_0001))
.isa_predicate(use_m),
);
e.add64(
e.enc(imul.bind(I64), r_r, op_bits(0b000, 0b0000_0001))
.isa_predicate(use_m),
);
e.add64(
e.enc(imul.bind(I32), r_r, op32_bits(0b000, 0b0000_0001))
.isa_predicate(use_m),
);
// Control flow.
// Unconditional branches.
e.add32(e.enc(jump, r_uj, jal_bits()));
e.add64(e.enc(jump, r_uj, jal_bits()));
e.add32(e.enc(call, r_uj_call, jal_bits()));
e.add64(e.enc(call, r_uj_call, jal_bits()));
// Conditional branches.
{
let mut var_pool = VarPool::new();
// Helper that creates an instruction predicate for an instruction in the icmp family.
let mut br_icmp_instp = |bound_inst: &BoundInstruction,
intcc_field: &'static str|
-> InstructionPredicateNode {
let x = var_pool.create("x");
let y = var_pool.create("y");
let dest = var_pool.create("dest");
let args = var_pool.create("args");
let cc = Literal::enumerator_for(&shared_defs.imm.intcc, intcc_field);
Apply::new(
bound_inst.clone().into(),
vec![
Expr::Literal(cc),
Expr::Var(x),
Expr::Var(y),
Expr::Var(dest),
Expr::Var(args),
],
)
.inst_predicate(&var_pool)
.unwrap()
};
let br_icmp_i32 = br_icmp.bind(I32);
let br_icmp_i64 = br_icmp.bind(I64);
for &(cond, f3) in &[
("eq", 0b000),
("ne", 0b001),
("slt", 0b100),
("sge", 0b101),
("ult", 0b110),
("uge", 0b111),
] {
e.add32(
e.enc(br_icmp_i32.clone(), r_sb, branch_bits(f3))
.inst_predicate(br_icmp_instp(&br_icmp_i32, cond)),
);
e.add64(
e.enc(br_icmp_i64.clone(), r_sb, branch_bits(f3))
.inst_predicate(br_icmp_instp(&br_icmp_i64, cond)),
);
}
}
for &(inst, f3) in &[(brz, 0b000), (brnz, 0b001)] {
e.add32(e.enc(inst.bind(I32), r_sb_zero, branch_bits(f3)));
e.add64(e.enc(inst.bind(I64), r_sb_zero, branch_bits(f3)));
e.add32(e.enc(inst.bind(B1), r_sb_zero, branch_bits(f3)));
e.add64(e.enc(inst.bind(B1), r_sb_zero, branch_bits(f3)));
}
// Returns are a special case of jalr_bits 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().
e.add32(e.enc(return_, r_iret, jalr_bits()));
e.add64(e.enc(return_, r_iret, jalr_bits()));
e.add32(e.enc(call_indirect.bind(I32), r_icall, jalr_bits()));
e.add64(e.enc(call_indirect.bind(I64), r_icall, jalr_bits()));
// Spill and fill.
e.add32(e.enc(spill.bind(I32), r_gp_sp, store_bits(0b010)));
e.add64(e.enc(spill.bind(I32), r_gp_sp, store_bits(0b010)));
e.add64(e.enc(spill.bind(I64), r_gp_sp, store_bits(0b011)));
e.add32(e.enc(fill.bind(I32), r_gp_fi, load_bits(0b010)));
e.add64(e.enc(fill.bind(I32), r_gp_fi, load_bits(0b010)));
e.add64(e.enc(fill.bind(I64), r_gp_fi, load_bits(0b011)));
// No-op fills, created by late-stage redundant-fill removal.
for &ty in &[I64, I32] {
e.add64(e.enc(fill_nop.bind(ty), r_fillnull, 0));
e.add32(e.enc(fill_nop.bind(ty), r_fillnull, 0));
}
e.add64(e.enc(fill_nop.bind(B1), r_fillnull, 0));
e.add32(e.enc(fill_nop.bind(B1), r_fillnull, 0));
// Register copies.
e.add32(e.enc(copy.bind(I32), r_icopy, opimm_bits(0b000, 0)));
e.add64(e.enc(copy.bind(I64), r_icopy, opimm_bits(0b000, 0)));
e.add64(e.enc(copy.bind(I32), r_icopy, opimm32_bits(0b000, 0)));
e.add32(e.enc(regmove.bind(I32), r_irmov, opimm_bits(0b000, 0)));
e.add64(e.enc(regmove.bind(I64), r_irmov, opimm_bits(0b000, 0)));
e.add64(e.enc(regmove.bind(I32), r_irmov, opimm32_bits(0b000, 0)));
e.add32(e.enc(copy.bind(B1), r_icopy, opimm_bits(0b000, 0)));
e.add64(e.enc(copy.bind(B1), r_icopy, opimm_bits(0b000, 0)));
e.add32(e.enc(regmove.bind(B1), r_irmov, opimm_bits(0b000, 0)));
e.add64(e.enc(regmove.bind(B1), r_irmov, opimm_bits(0b000, 0)));
// Stack-slot-to-the-same-stack-slot copy, which is guaranteed to turn
// into a no-op.
// The same encoding is generated for both the 64- and 32-bit architectures.
for &ty in &[I64, I32, I16, I8] {
e.add32(e.enc(copy_nop.bind(ty), r_stacknull, 0));
e.add64(e.enc(copy_nop.bind(ty), r_stacknull, 0));
}
for &ty in &[F64, F32] {
e.add32(e.enc(copy_nop.bind(ty), r_stacknull, 0));
e.add64(e.enc(copy_nop.bind(ty), r_stacknull, 0));
}
// Copy-to-SSA
e.add32(e.enc(copy_to_ssa.bind(I32), r_copytossa, opimm_bits(0b000, 0)));
e.add64(e.enc(copy_to_ssa.bind(I64), r_copytossa, opimm_bits(0b000, 0)));
e.add64(e.enc(copy_to_ssa.bind(I32), r_copytossa, opimm32_bits(0b000, 0)));
e.add32(e.enc(copy_to_ssa.bind(B1), r_copytossa, opimm_bits(0b000, 0)));
e.add64(e.enc(copy_to_ssa.bind(B1), r_copytossa, opimm_bits(0b000, 0)));
e.add32(e.enc(copy_to_ssa.bind(R32), r_copytossa, opimm_bits(0b000, 0)));
e.add64(e.enc(copy_to_ssa.bind(R64), r_copytossa, opimm_bits(0b000, 0)));
e
}

View File

@@ -1,136 +0,0 @@
use crate::cdsl::cpu_modes::CpuMode;
use crate::cdsl::isa::TargetIsa;
use crate::cdsl::regs::{IsaRegs, IsaRegsBuilder, RegBankBuilder, RegClassBuilder};
use crate::cdsl::settings::{PredicateNode, SettingGroup, SettingGroupBuilder};
use crate::shared::types::Float::{F32, F64};
use crate::shared::types::Int::{I32, I64};
use crate::shared::Definitions as SharedDefinitions;
mod encodings;
mod recipes;
fn define_settings(shared: &SettingGroup) -> SettingGroup {
let mut setting = SettingGroupBuilder::new("riscv");
let supports_m = setting.add_bool(
"supports_m",
"CPU supports the 'M' extension (mul/div)",
"",
false,
);
let supports_a = setting.add_bool(
"supports_a",
"CPU supports the 'A' extension (atomics)",
"",
false,
);
let supports_f = setting.add_bool(
"supports_f",
"CPU supports the 'F' extension (float)",
"",
false,
);
let supports_d = setting.add_bool(
"supports_d",
"CPU supports the 'D' extension (double)",
"",
false,
);
let enable_m = setting.add_bool(
"enable_m",
"Enable the use of 'M' instructions if available",
"",
true,
);
setting.add_bool(
"enable_e",
"Enable the 'RV32E' instruction set with only 16 registers",
"",
false,
);
let shared_enable_atomics = shared.get_bool("enable_atomics");
let shared_enable_float = shared.get_bool("enable_float");
let shared_enable_simd = shared.get_bool("enable_simd");
setting.add_predicate("use_m", predicate!(supports_m && enable_m));
setting.add_predicate("use_a", predicate!(supports_a && shared_enable_atomics));
setting.add_predicate("use_f", predicate!(supports_f && shared_enable_float));
setting.add_predicate("use_d", predicate!(supports_d && shared_enable_float));
setting.add_predicate(
"full_float",
predicate!(shared_enable_simd && supports_f && supports_d),
);
setting.build()
}
fn define_registers() -> IsaRegs {
let mut regs = IsaRegsBuilder::new();
let builder = RegBankBuilder::new("IntRegs", "x")
.units(32)
.track_pressure(true);
let int_regs = regs.add_bank(builder);
let builder = RegBankBuilder::new("FloatRegs", "f")
.units(32)
.track_pressure(true);
let float_regs = regs.add_bank(builder);
let builder = RegClassBuilder::new_toplevel("GPR", int_regs);
regs.add_class(builder);
let builder = RegClassBuilder::new_toplevel("FPR", float_regs);
regs.add_class(builder);
regs.build()
}
pub(crate) fn define(shared_defs: &mut SharedDefinitions) -> TargetIsa {
let settings = define_settings(&shared_defs.settings);
let regs = define_registers();
// CPU modes for 32-bit and 64-bit operation.
let mut rv_32 = CpuMode::new("RV32");
let mut rv_64 = CpuMode::new("RV64");
let expand = shared_defs.transform_groups.by_name("expand");
let narrow_no_flags = shared_defs.transform_groups.by_name("narrow_no_flags");
rv_32.legalize_monomorphic(expand);
rv_32.legalize_default(narrow_no_flags);
rv_32.legalize_type(I32, expand);
rv_32.legalize_type(F32, expand);
rv_32.legalize_type(F64, expand);
rv_64.legalize_monomorphic(expand);
rv_64.legalize_default(narrow_no_flags);
rv_64.legalize_type(I32, expand);
rv_64.legalize_type(I64, expand);
rv_64.legalize_type(F32, expand);
rv_64.legalize_type(F64, expand);
let recipes = recipes::define(shared_defs, &regs);
let encodings = encodings::define(shared_defs, &settings, &recipes);
rv_32.set_encodings(encodings.enc32);
rv_64.set_encodings(encodings.enc64);
let encodings_predicates = encodings.inst_pred_reg.extract();
let recipes = recipes.collect();
let cpu_modes = vec![rv_32, rv_64];
TargetIsa::new(
"riscv",
settings,
regs,
recipes,
cpu_modes,
encodings_predicates,
)
}

View File

@@ -1,280 +0,0 @@
use std::collections::HashMap;
use crate::cdsl::instructions::InstructionPredicate;
use crate::cdsl::recipes::{EncodingRecipeBuilder, EncodingRecipeNumber, Recipes, Stack};
use crate::cdsl::regs::IsaRegs;
use crate::shared::Definitions as SharedDefinitions;
/// An helper to create recipes and use them when defining the RISCV encodings.
pub(crate) struct RecipeGroup {
/// The actualy list of recipes explicitly created in this file.
pub recipes: Recipes,
/// Provides fast lookup from a name to an encoding recipe.
name_to_recipe: HashMap<String, EncodingRecipeNumber>,
}
impl RecipeGroup {
fn new() -> Self {
Self {
recipes: Recipes::new(),
name_to_recipe: HashMap::new(),
}
}
fn push(&mut self, builder: EncodingRecipeBuilder) {
assert!(
self.name_to_recipe.get(&builder.name).is_none(),
"riscv recipe '{}' created twice",
builder.name
);
let name = builder.name.clone();
let number = self.recipes.push(builder.build());
self.name_to_recipe.insert(name, number);
}
pub fn by_name(&self, name: &str) -> EncodingRecipeNumber {
*self
.name_to_recipe
.get(name)
.unwrap_or_else(|| panic!("unknown riscv recipe name {}", name))
}
pub fn collect(self) -> Recipes {
self.recipes
}
}
pub(crate) fn define(shared_defs: &SharedDefinitions, regs: &IsaRegs) -> RecipeGroup {
let formats = &shared_defs.formats;
// Register classes shorthands.
let gpr = regs.class_by_name("GPR");
// Definitions.
let mut recipes = RecipeGroup::new();
// R-type 32-bit instructions: These are mostly binary arithmetic instructions.
// The encbits are `opcode[6:2] | (funct3 << 5) | (funct7 << 8)
recipes.push(
EncodingRecipeBuilder::new("R", &formats.binary, 4)
.operands_in(vec![gpr, gpr])
.operands_out(vec![gpr])
.emit("put_r(bits, in_reg0, in_reg1, out_reg0, sink);"),
);
// R-type with an immediate shift amount instead of rs2.
recipes.push(
EncodingRecipeBuilder::new("Rshamt", &formats.binary_imm64, 4)
.operands_in(vec![gpr])
.operands_out(vec![gpr])
.emit("put_rshamt(bits, in_reg0, imm.into(), out_reg0, sink);"),
);
// R-type encoding of an integer comparison.
recipes.push(
EncodingRecipeBuilder::new("Ricmp", &formats.int_compare, 4)
.operands_in(vec![gpr, gpr])
.operands_out(vec![gpr])
.emit("put_r(bits, in_reg0, in_reg1, out_reg0, sink);"),
);
recipes.push(
EncodingRecipeBuilder::new("Ii", &formats.binary_imm64, 4)
.operands_in(vec![gpr])
.operands_out(vec![gpr])
.inst_predicate(InstructionPredicate::new_is_signed_int(
&*formats.binary_imm64,
"imm",
12,
0,
))
.emit("put_i(bits, in_reg0, imm.into(), out_reg0, sink);"),
);
// I-type instruction with a hardcoded %x0 rs1.
recipes.push(
EncodingRecipeBuilder::new("Iz", &formats.unary_imm, 4)
.operands_out(vec![gpr])
.inst_predicate(InstructionPredicate::new_is_signed_int(
&formats.unary_imm,
"imm",
12,
0,
))
.emit("put_i(bits, 0, imm.into(), out_reg0, sink);"),
);
// I-type encoding of an integer comparison.
recipes.push(
EncodingRecipeBuilder::new("Iicmp", &formats.int_compare_imm, 4)
.operands_in(vec![gpr])
.operands_out(vec![gpr])
.inst_predicate(InstructionPredicate::new_is_signed_int(
&formats.int_compare_imm,
"imm",
12,
0,
))
.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.
recipes.push(
EncodingRecipeBuilder::new("Iret", &formats.multiary, 4).emit(
r#"
// Return instructions are always a jalr to %x1.
// The return address is provided as a special-purpose link argument.
put_i(
bits,
1, // rs1 = %x1
0, // no offset.
0, // rd = %x0: no address written.
sink,
);
"#,
),
);
// I-type encoding for `jalr` as a call_indirect.
recipes.push(
EncodingRecipeBuilder::new("Icall", &formats.call_indirect, 4)
.operands_in(vec![gpr])
.emit(
r#"
// call_indirect instructions are jalr with rd=%x1.
put_i(
bits,
in_reg0,
0, // no offset.
1, // rd = %x1: link register.
sink,
);
"#,
),
);
// Copy of a GPR is implemented as addi x, 0.
recipes.push(
EncodingRecipeBuilder::new("Icopy", &formats.unary, 4)
.operands_in(vec![gpr])
.operands_out(vec![gpr])
.emit("put_i(bits, in_reg0, 0, out_reg0, sink);"),
);
// Same for a GPR regmove.
recipes.push(
EncodingRecipeBuilder::new("Irmov", &formats.reg_move, 4)
.operands_in(vec![gpr])
.emit("put_i(bits, src, 0, dst, sink);"),
);
// Same for copy-to-SSA -- GPR regmove.
recipes.push(
EncodingRecipeBuilder::new("copytossa", &formats.copy_to_ssa, 4)
// No operands_in to mention, because a source register is specified directly.
.operands_out(vec![gpr])
.emit("put_i(bits, src, 0, out_reg0, sink);"),
);
// U-type instructions have a 20-bit immediate that targets bits 12-31.
recipes.push(
EncodingRecipeBuilder::new("U", &formats.unary_imm, 4)
.operands_out(vec![gpr])
.inst_predicate(InstructionPredicate::new_is_signed_int(
&formats.unary_imm,
"imm",
32,
12,
))
.emit("put_u(bits, imm.into(), out_reg0, sink);"),
);
// UJ-type unconditional branch instructions.
recipes.push(
EncodingRecipeBuilder::new("UJ", &formats.jump, 4)
.branch_range((0, 21))
.emit(
r#"
let dest = i64::from(func.offsets[destination]);
let disp = dest - i64::from(sink.offset());
put_uj(bits, disp, 0, sink);
"#,
),
);
recipes.push(EncodingRecipeBuilder::new("UJcall", &formats.call, 4).emit(
r#"
sink.reloc_external(func.srclocs[inst],
Reloc::RiscvCall,
&func.dfg.ext_funcs[func_ref].name,
0);
// rd=%x1 is the standard link register.
put_uj(bits, 0, 1, sink);
"#,
));
// SB-type branch instructions.
recipes.push(
EncodingRecipeBuilder::new("SB", &formats.branch_icmp, 4)
.operands_in(vec![gpr, gpr])
.branch_range((0, 13))
.emit(
r#"
let dest = i64::from(func.offsets[destination]);
let disp = dest - i64::from(sink.offset());
put_sb(bits, disp, in_reg0, in_reg1, sink);
"#,
),
);
// SB-type branch instruction with rs2 fixed to zero.
recipes.push(
EncodingRecipeBuilder::new("SBzero", &formats.branch, 4)
.operands_in(vec![gpr])
.branch_range((0, 13))
.emit(
r#"
let dest = i64::from(func.offsets[destination]);
let disp = dest - i64::from(sink.offset());
put_sb(bits, disp, in_reg0, 0, sink);
"#,
),
);
// Spill of a GPR.
recipes.push(
EncodingRecipeBuilder::new("GPsp", &formats.unary, 4)
.operands_in(vec![gpr])
.operands_out(vec![Stack::new(gpr)])
.emit("unimplemented!();"),
);
// Fill of a GPR.
recipes.push(
EncodingRecipeBuilder::new("GPfi", &formats.unary, 4)
.operands_in(vec![Stack::new(gpr)])
.operands_out(vec![gpr])
.emit("unimplemented!();"),
);
// Stack-slot to same stack-slot copy, which is guaranteed to turn into a no-op.
recipes.push(
EncodingRecipeBuilder::new("stacknull", &formats.unary, 0)
.operands_in(vec![Stack::new(gpr)])
.operands_out(vec![Stack::new(gpr)])
.emit(""),
);
// No-op fills, created by late-stage redundant-fill removal.
recipes.push(
EncodingRecipeBuilder::new("fillnull", &formats.unary, 0)
.operands_in(vec![Stack::new(gpr)])
.operands_out(vec![gpr])
.clobbers_flags(false)
.emit(""),
);
recipes
}

View File

@@ -1,7 +1,4 @@
use crate::cdsl::instructions::InstructionPredicateMap;
use crate::cdsl::isa::TargetIsa;
use crate::cdsl::recipes::Recipes;
use crate::cdsl::regs::IsaRegsBuilder;
use crate::cdsl::settings::{SettingGroup, SettingGroupBuilder};
use crate::shared::Definitions as SharedDefinitions;
@@ -45,18 +42,6 @@ fn define_settings(_shared: &SettingGroup) -> SettingGroup {
pub(crate) fn define(shared_defs: &mut SharedDefinitions) -> TargetIsa {
let settings = define_settings(&shared_defs.settings);
let regs = IsaRegsBuilder::new().build();
let recipes = Recipes::new();
let encodings_predicates = InstructionPredicateMap::new();
let cpu_modes = vec![];
TargetIsa::new(
"s390x",
settings,
regs,
recipes,
cpu_modes,
encodings_predicates,
)
TargetIsa::new("s390x", settings)
}

View File

@@ -1,6 +1,15 @@
use crate::cdsl::isa::TargetIsa;
use crate::cdsl::settings::{PredicateNode, SettingGroup, SettingGroupBuilder};
pub(crate) fn define(shared: &SettingGroup) -> SettingGroup {
use crate::shared::Definitions as SharedDefinitions;
pub(crate) fn define(shared_defs: &mut SharedDefinitions) -> TargetIsa {
let settings = define_settings(&shared_defs.settings);
TargetIsa::new("x86", settings)
}
fn define_settings(shared: &SettingGroup) -> SettingGroup {
let mut settings = SettingGroupBuilder::new("x86");
// CPUID.01H:ECX

File diff suppressed because it is too large Load Diff

View File

@@ -1,723 +0,0 @@
#![allow(non_snake_case)]
use crate::cdsl::instructions::{
AllInstructions, InstructionBuilder as Inst, InstructionGroup, InstructionGroupBuilder,
};
use crate::cdsl::operands::Operand;
use crate::cdsl::types::ValueType;
use crate::cdsl::typevar::{Interval, TypeSetBuilder, TypeVar};
use crate::shared::entities::EntityRefs;
use crate::shared::formats::Formats;
use crate::shared::immediates::Immediates;
use crate::shared::types;
#[allow(clippy::many_single_char_names)]
pub(crate) fn define(
mut all_instructions: &mut AllInstructions,
formats: &Formats,
immediates: &Immediates,
entities: &EntityRefs,
) -> InstructionGroup {
let mut ig = InstructionGroupBuilder::new(&mut all_instructions);
let iflags: &TypeVar = &ValueType::Special(types::Flag::IFlags.into()).into();
let iWord = &TypeVar::new(
"iWord",
"A scalar integer machine word",
TypeSetBuilder::new().ints(32..64).build(),
);
let nlo = &Operand::new("nlo", iWord).with_doc("Low part of numerator");
let nhi = &Operand::new("nhi", iWord).with_doc("High part of numerator");
let d = &Operand::new("d", iWord).with_doc("Denominator");
let q = &Operand::new("q", iWord).with_doc("Quotient");
let r = &Operand::new("r", iWord).with_doc("Remainder");
ig.push(
Inst::new(
"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.
"#,
&formats.ternary,
)
.operands_in(vec![nlo, nhi, d])
.operands_out(vec![q, r])
.can_trap(true),
);
ig.push(
Inst::new(
"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.
"#,
&formats.ternary,
)
.operands_in(vec![nlo, nhi, d])
.operands_out(vec![q, r])
.can_trap(true),
);
let argL = &Operand::new("argL", iWord);
let argR = &Operand::new("argR", iWord);
let resLo = &Operand::new("resLo", iWord);
let resHi = &Operand::new("resHi", iWord);
ig.push(
Inst::new(
"x86_umulx",
r#"
Unsigned integer multiplication, producing a double-length result.
Polymorphic over all scalar integer types, but does not support vector
types.
"#,
&formats.binary,
)
.operands_in(vec![argL, argR])
.operands_out(vec![resLo, resHi]),
);
ig.push(
Inst::new(
"x86_smulx",
r#"
Signed integer multiplication, producing a double-length result.
Polymorphic over all scalar integer types, but does not support vector
types.
"#,
&formats.binary,
)
.operands_in(vec![argL, argR])
.operands_out(vec![resLo, resHi]),
);
let Float = &TypeVar::new(
"Float",
"A scalar or vector floating point number",
TypeSetBuilder::new()
.floats(Interval::All)
.simd_lanes(Interval::All)
.build(),
);
let IntTo = &TypeVar::new(
"IntTo",
"An integer type with the same number of lanes",
TypeSetBuilder::new()
.ints(32..64)
.simd_lanes(Interval::All)
.build(),
);
let x = &Operand::new("x", Float);
let a = &Operand::new("a", IntTo);
ig.push(
Inst::new(
"x86_cvtt2si",
r#"
Convert with truncation floating point to signed integer.
The source floating point operand is converted to a signed integer by
rounding towards zero. If the result can't be represented in the output
type, returns the smallest signed value the output type can represent.
This instruction does not trap.
"#,
&formats.unary,
)
.operands_in(vec![x])
.operands_out(vec![a]),
);
let f32x4 = &TypeVar::new(
"f32x4",
"A floating point number",
TypeSetBuilder::new()
.floats(32..32)
.simd_lanes(4..4)
.build(),
);
let i32x4 = &TypeVar::new(
"i32x4",
"An integer type with the same number of lanes",
TypeSetBuilder::new().ints(32..32).simd_lanes(4..4).build(),
);
let x = &Operand::new("x", i32x4);
let a = &Operand::new("a", f32x4);
ig.push(
Inst::new(
"x86_vcvtudq2ps",
r#"
Convert unsigned integer to floating point.
Convert packed doubleword unsigned integers to packed single-precision floating-point
values. This instruction does not trap.
"#,
&formats.unary,
)
.operands_in(vec![x])
.operands_out(vec![a]),
);
let x = &Operand::new("x", Float);
let a = &Operand::new("a", Float);
let y = &Operand::new("y", Float);
ig.push(
Inst::new(
"x86_fmin",
r#"
Floating point minimum with x86 semantics.
This is equivalent to the C ternary operator `x < y ? x : y` which
differs from `fmin` when either operand is NaN or when comparing
+0.0 to -0.0.
When the two operands don't compare as LT, `y` is returned unchanged,
even if it is a signalling NaN.
"#,
&formats.binary,
)
.operands_in(vec![x, y])
.operands_out(vec![a]),
);
ig.push(
Inst::new(
"x86_fmax",
r#"
Floating point maximum with x86 semantics.
This is equivalent to the C ternary operator `x > y ? x : y` which
differs from `fmax` when either operand is NaN or when comparing
+0.0 to -0.0.
When the two operands don't compare as GT, `y` is returned unchanged,
even if it is a signalling NaN.
"#,
&formats.binary,
)
.operands_in(vec![x, y])
.operands_out(vec![a]),
);
let x = &Operand::new("x", iWord);
ig.push(
Inst::new(
"x86_push",
r#"
Pushes a value onto the stack.
Decrements the stack pointer and stores the specified value on to the top.
This is polymorphic in i32 and i64. However, it is only implemented for i64
in 64-bit mode, and only for i32 in 32-bit mode.
"#,
&formats.unary,
)
.operands_in(vec![x])
.other_side_effects(true)
.can_store(true),
);
ig.push(
Inst::new(
"x86_pop",
r#"
Pops a value from the stack.
Loads a value from the top of the stack and then increments the stack
pointer.
This is polymorphic in i32 and i64. However, it is only implemented for i64
in 64-bit mode, and only for i32 in 32-bit mode.
"#,
&formats.nullary,
)
.operands_out(vec![x])
.other_side_effects(true)
.can_load(true),
);
let y = &Operand::new("y", iWord);
let rflags = &Operand::new("rflags", iflags);
ig.push(
Inst::new(
"x86_bsr",
r#"
Bit Scan Reverse -- returns the bit-index of the most significant 1
in the word. Result is undefined if the argument is zero. However, it
sets the Z flag depending on the argument, so it is at least easy to
detect and handle that case.
This is polymorphic in i32 and i64. It is implemented for both i64 and
i32 in 64-bit mode, and only for i32 in 32-bit mode.
"#,
&formats.unary,
)
.operands_in(vec![x])
.operands_out(vec![y, rflags]),
);
ig.push(
Inst::new(
"x86_bsf",
r#"
Bit Scan Forwards -- returns the bit-index of the least significant 1
in the word. Is otherwise identical to 'bsr', just above.
"#,
&formats.unary,
)
.operands_in(vec![x])
.operands_out(vec![y, rflags]),
);
let uimm8 = &immediates.uimm8;
let TxN = &TypeVar::new(
"TxN",
"A SIMD vector type",
TypeSetBuilder::new()
.ints(Interval::All)
.floats(Interval::All)
.bools(Interval::All)
.simd_lanes(Interval::All)
.includes_scalars(false)
.build(),
);
let a = &Operand::new("a", TxN).with_doc("A vector value (i.e. held in an XMM register)");
let b = &Operand::new("b", TxN).with_doc("A vector value (i.e. held in an XMM register)");
let i = &Operand::new("i", uimm8).with_doc("An ordering operand controlling the copying of data from the source to the destination; see PSHUFD in Intel manual for details");
ig.push(
Inst::new(
"x86_pshufd",
r#"
Packed Shuffle Doublewords -- copies data from either memory or lanes in an extended
register and re-orders the data according to the passed immediate byte.
"#,
&formats.binary_imm8,
)
.operands_in(vec![a, i]) // TODO allow copying from memory here (need more permissive type than TxN)
.operands_out(vec![a]),
);
ig.push(
Inst::new(
"x86_pshufb",
r#"
Packed Shuffle Bytes -- re-orders data in an extended register using a shuffle
mask from either memory or another extended register
"#,
&formats.binary,
)
.operands_in(vec![a, b]) // TODO allow re-ordering from memory here (need more permissive type than TxN)
.operands_out(vec![a]),
);
let mask = &Operand::new("mask", uimm8).with_doc("mask to select lanes from b");
ig.push(
Inst::new(
"x86_pblendw",
r#"
Blend packed words using an immediate mask. Each bit of the 8-bit immediate corresponds to a
lane in ``b``: if the bit is set, the lane is copied into ``a``.
"#,
&formats.ternary_imm8,
)
.operands_in(vec![a, b, mask])
.operands_out(vec![a]),
);
let Idx = &Operand::new("Idx", uimm8).with_doc("Lane index");
let x = &Operand::new("x", TxN);
let a = &Operand::new("a", &TxN.lane_of());
ig.push(
Inst::new(
"x86_pextr",
r#"
Extract lane ``Idx`` from ``x``.
The lane index, ``Idx``, is an immediate value, not an SSA value. It
must indicate a valid lane index for the type of ``x``.
"#,
&formats.binary_imm8,
)
.operands_in(vec![x, Idx])
.operands_out(vec![a]),
);
let IBxN = &TypeVar::new(
"IBxN",
"A SIMD vector type containing only booleans and integers",
TypeSetBuilder::new()
.ints(Interval::All)
.bools(Interval::All)
.simd_lanes(Interval::All)
.includes_scalars(false)
.build(),
);
let x = &Operand::new("x", IBxN);
let y = &Operand::new("y", &IBxN.lane_of()).with_doc("New lane value");
let a = &Operand::new("a", IBxN);
ig.push(
Inst::new(
"x86_pinsr",
r#"
Insert ``y`` into ``x`` at lane ``Idx``.
The lane index, ``Idx``, is an immediate value, not an SSA value. It
must indicate a valid lane index for the type of ``x``.
"#,
&formats.ternary_imm8,
)
.operands_in(vec![x, y, Idx])
.operands_out(vec![a]),
);
let FxN = &TypeVar::new(
"FxN",
"A SIMD vector type containing floats",
TypeSetBuilder::new()
.floats(Interval::All)
.simd_lanes(Interval::All)
.includes_scalars(false)
.build(),
);
let x = &Operand::new("x", FxN);
let y = &Operand::new("y", &FxN.lane_of()).with_doc("New lane value");
let a = &Operand::new("a", FxN);
ig.push(
Inst::new(
"x86_insertps",
r#"
Insert a lane of ``y`` into ``x`` at using ``Idx`` to encode both which lane the value is
extracted from and which it is inserted to. This is similar to x86_pinsr but inserts
floats, which are already stored in an XMM register.
"#,
&formats.ternary_imm8,
)
.operands_in(vec![x, y, Idx])
.operands_out(vec![a]),
);
let x = &Operand::new("x", TxN);
let y = &Operand::new("y", TxN);
let a = &Operand::new("a", TxN);
ig.push(
Inst::new(
"x86_punpckh",
r#"
Unpack the high-order lanes of ``x`` and ``y`` and interleave into ``a``. With notional
i8x4 vectors, where ``x = [x3, x2, x1, x0]`` and ``y = [y3, y2, y1, y0]``, this operation
would result in ``a = [y3, x3, y2, x2]`` (using the Intel manual's right-to-left lane
ordering).
"#,
&formats.binary,
)
.operands_in(vec![x, y])
.operands_out(vec![a]),
);
ig.push(
Inst::new(
"x86_punpckl",
r#"
Unpack the low-order lanes of ``x`` and ``y`` and interleave into ``a``. With notional
i8x4 vectors, where ``x = [x3, x2, x1, x0]`` and ``y = [y3, y2, y1, y0]``, this operation
would result in ``a = [y1, x1, y0, x0]`` (using the Intel manual's right-to-left lane
ordering).
"#,
&formats.binary,
)
.operands_in(vec![x, y])
.operands_out(vec![a]),
);
let x = &Operand::new("x", FxN);
let y = &Operand::new("y", FxN);
let a = &Operand::new("a", FxN);
ig.push(
Inst::new(
"x86_movsd",
r#"
Move the low 64 bits of the float vector ``y`` to the low 64 bits of float vector ``x``
"#,
&formats.binary,
)
.operands_in(vec![x, y])
.operands_out(vec![a]),
);
ig.push(
Inst::new(
"x86_movlhps",
r#"
Move the low 64 bits of the float vector ``y`` to the high 64 bits of float vector ``x``
"#,
&formats.binary,
)
.operands_in(vec![x, y])
.operands_out(vec![a]),
);
let IxN = &TypeVar::new(
"IxN",
"A SIMD vector type containing integers",
TypeSetBuilder::new()
.ints(Interval::All)
.simd_lanes(Interval::All)
.includes_scalars(false)
.build(),
);
let I128 = &TypeVar::new(
"I128",
"A SIMD vector type containing one large integer (due to Cranelift type constraints, \
this uses the Cranelift I64X2 type but should be understood as one large value, i.e., the \
upper lane is concatenated with the lower lane to form the integer)",
TypeSetBuilder::new()
.ints(64..64)
.simd_lanes(2..2)
.includes_scalars(false)
.build(),
);
let x = &Operand::new("x", IxN).with_doc("Vector value to shift");
let y = &Operand::new("y", I128).with_doc("Number of bits to shift");
let a = &Operand::new("a", IxN);
ig.push(
Inst::new(
"x86_psll",
r#"
Shift Packed Data Left Logical -- This implements the behavior of the shared instruction
``ishl`` but alters the shift operand to live in an XMM register as expected by the PSLL*
family of instructions.
"#,
&formats.binary,
)
.operands_in(vec![x, y])
.operands_out(vec![a]),
);
ig.push(
Inst::new(
"x86_psrl",
r#"
Shift Packed Data Right Logical -- This implements the behavior of the shared instruction
``ushr`` but alters the shift operand to live in an XMM register as expected by the PSRL*
family of instructions.
"#,
&formats.binary,
)
.operands_in(vec![x, y])
.operands_out(vec![a]),
);
ig.push(
Inst::new(
"x86_psra",
r#"
Shift Packed Data Right Arithmetic -- This implements the behavior of the shared
instruction ``sshr`` but alters the shift operand to live in an XMM register as expected by
the PSRA* family of instructions.
"#,
&formats.binary,
)
.operands_in(vec![x, y])
.operands_out(vec![a]),
);
let I64x2 = &TypeVar::new(
"I64x2",
"A SIMD vector type containing two 64-bit integers",
TypeSetBuilder::new()
.ints(64..64)
.simd_lanes(2..2)
.includes_scalars(false)
.build(),
);
let x = &Operand::new("x", I64x2);
let y = &Operand::new("y", I64x2);
let a = &Operand::new("a", I64x2);
ig.push(
Inst::new(
"x86_pmullq",
r#"
Multiply Packed Integers -- Multiply two 64x2 integers and receive a 64x2 result with
lane-wise wrapping if the result overflows. This instruction is necessary to add distinct
encodings for CPUs with newer vector features.
"#,
&formats.binary,
)
.operands_in(vec![x, y])
.operands_out(vec![a]),
);
ig.push(
Inst::new(
"x86_pmuludq",
r#"
Multiply Packed Integers -- Using only the bottom 32 bits in each lane, multiply two 64x2
unsigned integers and receive a 64x2 result. This instruction avoids the need for handling
overflow as in `x86_pmullq`.
"#,
&formats.binary,
)
.operands_in(vec![x, y])
.operands_out(vec![a]),
);
let x = &Operand::new("x", TxN);
let y = &Operand::new("y", TxN);
let f = &Operand::new("f", iflags);
ig.push(
Inst::new(
"x86_ptest",
r#"
Logical Compare -- PTEST will set the ZF flag if all bits in the result are 0 of the
bitwise AND of the first source operand (first operand) and the second source operand
(second operand). PTEST sets the CF flag if all bits in the result are 0 of the bitwise
AND of the second source operand (second operand) and the logical NOT of the destination
operand (first operand).
"#,
&formats.binary,
)
.operands_in(vec![x, y])
.operands_out(vec![f]),
);
let x = &Operand::new("x", IxN);
let y = &Operand::new("y", IxN);
let a = &Operand::new("a", IxN);
ig.push(
Inst::new(
"x86_pmaxs",
r#"
Maximum of Packed Signed Integers -- Compare signed integers in the first and second
operand and return the maximum values.
"#,
&formats.binary,
)
.operands_in(vec![x, y])
.operands_out(vec![a]),
);
ig.push(
Inst::new(
"x86_pmaxu",
r#"
Maximum of Packed Unsigned Integers -- Compare unsigned integers in the first and second
operand and return the maximum values.
"#,
&formats.binary,
)
.operands_in(vec![x, y])
.operands_out(vec![a]),
);
ig.push(
Inst::new(
"x86_pmins",
r#"
Minimum of Packed Signed Integers -- Compare signed integers in the first and second
operand and return the minimum values.
"#,
&formats.binary,
)
.operands_in(vec![x, y])
.operands_out(vec![a]),
);
ig.push(
Inst::new(
"x86_pminu",
r#"
Minimum of Packed Unsigned Integers -- Compare unsigned integers in the first and second
operand and return the minimum values.
"#,
&formats.binary,
)
.operands_in(vec![x, y])
.operands_out(vec![a]),
);
let c = &Operand::new("c", uimm8)
.with_doc("The number of bytes to shift right; see PALIGNR in Intel manual for details");
ig.push(
Inst::new(
"x86_palignr",
r#"
Concatenate destination and source operands, extracting a byte-aligned result shifted to
the right by `c`.
"#,
&formats.ternary_imm8,
)
.operands_in(vec![x, y, c])
.operands_out(vec![a]),
);
let i64_t = &TypeVar::new(
"i64_t",
"A scalar 64bit integer",
TypeSetBuilder::new().ints(64..64).build(),
);
let GV = &Operand::new("GV", &entities.global_value);
let addr = &Operand::new("addr", i64_t);
ig.push(
Inst::new(
"x86_elf_tls_get_addr",
r#"
Elf tls get addr -- This implements the GD TLS model for ELF. The clobber output should
not be used.
"#,
&formats.unary_global_value,
)
// This is a bit overly broad to mark as clobbering *all* the registers, because it should
// only preserve caller-saved registers. There's no way to indicate this to register
// allocation yet, though, so mark as clobbering all registers instead.
.clobbers_all_regs(true)
.operands_in(vec![GV])
.operands_out(vec![addr]),
);
ig.push(
Inst::new(
"x86_macho_tls_get_addr",
r#"
Mach-O tls get addr -- This implements TLS access for Mach-O. The clobber output should
not be used.
"#,
&formats.unary_global_value,
)
// See above comment for x86_elf_tls_get_addr.
.clobbers_all_regs(true)
.operands_in(vec![GV])
.operands_out(vec![addr]),
);
ig.build()
}

View File

@@ -1,827 +0,0 @@
use crate::cdsl::ast::{constant, var, ExprBuilder, Literal};
use crate::cdsl::instructions::{vector, Bindable, InstructionGroup};
use crate::cdsl::types::{LaneType, ValueType};
use crate::cdsl::xform::TransformGroupBuilder;
use crate::shared::types::Float::{F32, F64};
use crate::shared::types::Int::{I16, I32, I64, I8};
use crate::shared::Definitions as SharedDefinitions;
#[allow(clippy::many_single_char_names)]
pub(crate) fn define(shared: &mut SharedDefinitions, x86_instructions: &InstructionGroup) {
let mut expand = TransformGroupBuilder::new(
"x86_expand",
r#"
Legalize instructions by expansion.
Use x86-specific instructions if needed."#,
)
.isa("x86")
.chain_with(shared.transform_groups.by_name("expand_flags").id);
let mut narrow = TransformGroupBuilder::new(
"x86_narrow",
r#"
Legalize instructions by narrowing.
Use x86-specific instructions if needed."#,
)
.isa("x86")
.chain_with(shared.transform_groups.by_name("narrow_flags").id);
let mut narrow_avx = TransformGroupBuilder::new(
"x86_narrow_avx",
r#"
Legalize instructions by narrowing with CPU feature checks.
This special case converts using x86 AVX instructions where available."#,
)
.isa("x86");
// We cannot chain with the x86_narrow group until this group is built, see bottom of this
// function for where this is chained.
let mut widen = TransformGroupBuilder::new(
"x86_widen",
r#"
Legalize instructions by widening.
Use x86-specific instructions if needed."#,
)
.isa("x86")
.chain_with(shared.transform_groups.by_name("widen").id);
// List of instructions.
let insts = &shared.instructions;
let band = insts.by_name("band");
let bor = insts.by_name("bor");
let clz = insts.by_name("clz");
let ctz = insts.by_name("ctz");
let fcmp = insts.by_name("fcmp");
let fcvt_from_uint = insts.by_name("fcvt_from_uint");
let fcvt_to_sint = insts.by_name("fcvt_to_sint");
let fcvt_to_uint = insts.by_name("fcvt_to_uint");
let fcvt_to_sint_sat = insts.by_name("fcvt_to_sint_sat");
let fcvt_to_uint_sat = insts.by_name("fcvt_to_uint_sat");
let fmax = insts.by_name("fmax");
let fmin = insts.by_name("fmin");
let iadd = insts.by_name("iadd");
let iconst = insts.by_name("iconst");
let imul = insts.by_name("imul");
let ineg = insts.by_name("ineg");
let isub = insts.by_name("isub");
let ishl = insts.by_name("ishl");
let ireduce = insts.by_name("ireduce");
let popcnt = insts.by_name("popcnt");
let sdiv = insts.by_name("sdiv");
let selectif = insts.by_name("selectif");
let smulhi = insts.by_name("smulhi");
let srem = insts.by_name("srem");
let tls_value = insts.by_name("tls_value");
let udiv = insts.by_name("udiv");
let umulhi = insts.by_name("umulhi");
let ushr = insts.by_name("ushr");
let ushr_imm = insts.by_name("ushr_imm");
let urem = insts.by_name("urem");
let x86_bsf = x86_instructions.by_name("x86_bsf");
let x86_bsr = x86_instructions.by_name("x86_bsr");
let x86_umulx = x86_instructions.by_name("x86_umulx");
let x86_smulx = x86_instructions.by_name("x86_smulx");
let imm = &shared.imm;
// Shift by a 64-bit amount is equivalent to a shift by that amount mod 32, so we can reduce
// the size of the shift amount. This is useful for x86_32, where an I64 shift amount is
// not encodable.
let a = var("a");
let x = var("x");
let y = var("y");
let z = var("z");
for &ty in &[I8, I16, I32] {
let ishl_by_i64 = ishl.bind(ty).bind(I64);
let ireduce = ireduce.bind(I32);
expand.legalize(
def!(a = ishl_by_i64(x, y)),
vec![def!(z = ireduce(y)), def!(a = ishl(x, z))],
);
}
for &ty in &[I8, I16, I32] {
let ushr_by_i64 = ushr.bind(ty).bind(I64);
let ireduce = ireduce.bind(I32);
expand.legalize(
def!(a = ushr_by_i64(x, y)),
vec![def!(z = ireduce(y)), def!(a = ishl(x, z))],
);
}
// Division and remainder.
//
// The srem expansion requires custom code because srem INT_MIN, -1 is not
// allowed to trap. The other ops need to check avoid_div_traps.
expand.custom_legalize(sdiv, "expand_sdivrem");
expand.custom_legalize(srem, "expand_sdivrem");
expand.custom_legalize(udiv, "expand_udivrem");
expand.custom_legalize(urem, "expand_udivrem");
// Double length (widening) multiplication.
let a = var("a");
let x = var("x");
let y = var("y");
let a1 = var("a1");
let a2 = var("a2");
let res_lo = var("res_lo");
let res_hi = var("res_hi");
expand.legalize(
def!(res_hi = umulhi(x, y)),
vec![def!((res_lo, res_hi) = x86_umulx(x, y))],
);
expand.legalize(
def!(res_hi = smulhi(x, y)),
vec![def!((res_lo, res_hi) = x86_smulx(x, y))],
);
// Floating point condition codes.
//
// The 8 condition codes in `supported_floatccs` are directly supported by a
// `ucomiss` or `ucomisd` instruction. The remaining codes need legalization
// patterns.
let floatcc_eq = Literal::enumerator_for(&imm.floatcc, "eq");
let floatcc_ord = Literal::enumerator_for(&imm.floatcc, "ord");
let floatcc_ueq = Literal::enumerator_for(&imm.floatcc, "ueq");
let floatcc_ne = Literal::enumerator_for(&imm.floatcc, "ne");
let floatcc_uno = Literal::enumerator_for(&imm.floatcc, "uno");
let floatcc_one = Literal::enumerator_for(&imm.floatcc, "one");
// Equality needs an explicit `ord` test which checks the parity bit.
expand.legalize(
def!(a = fcmp(floatcc_eq, x, y)),
vec![
def!(a1 = fcmp(floatcc_ord, x, y)),
def!(a2 = fcmp(floatcc_ueq, x, y)),
def!(a = band(a1, a2)),
],
);
expand.legalize(
def!(a = fcmp(floatcc_ne, x, y)),
vec![
def!(a1 = fcmp(floatcc_uno, x, y)),
def!(a2 = fcmp(floatcc_one, x, y)),
def!(a = bor(a1, a2)),
],
);
let floatcc_lt = &Literal::enumerator_for(&imm.floatcc, "lt");
let floatcc_gt = &Literal::enumerator_for(&imm.floatcc, "gt");
let floatcc_le = &Literal::enumerator_for(&imm.floatcc, "le");
let floatcc_ge = &Literal::enumerator_for(&imm.floatcc, "ge");
let floatcc_ugt = &Literal::enumerator_for(&imm.floatcc, "ugt");
let floatcc_ult = &Literal::enumerator_for(&imm.floatcc, "ult");
let floatcc_uge = &Literal::enumerator_for(&imm.floatcc, "uge");
let floatcc_ule = &Literal::enumerator_for(&imm.floatcc, "ule");
// Inequalities that need to be reversed.
for &(cc, rev_cc) in &[
(floatcc_lt, floatcc_gt),
(floatcc_le, floatcc_ge),
(floatcc_ugt, floatcc_ult),
(floatcc_uge, floatcc_ule),
] {
expand.legalize(def!(a = fcmp(cc, x, y)), vec![def!(a = fcmp(rev_cc, y, x))]);
}
// We need to modify the CFG for min/max legalization.
expand.custom_legalize(fmin, "expand_minmax");
expand.custom_legalize(fmax, "expand_minmax");
// Conversions from unsigned need special handling.
expand.custom_legalize(fcvt_from_uint, "expand_fcvt_from_uint");
// Conversions from float to int can trap and modify the control flow graph.
expand.custom_legalize(fcvt_to_sint, "expand_fcvt_to_sint");
expand.custom_legalize(fcvt_to_uint, "expand_fcvt_to_uint");
expand.custom_legalize(fcvt_to_sint_sat, "expand_fcvt_to_sint_sat");
expand.custom_legalize(fcvt_to_uint_sat, "expand_fcvt_to_uint_sat");
// Count leading and trailing zeroes, for baseline x86_64
let c_minus_one = var("c_minus_one");
let c_thirty_one = var("c_thirty_one");
let c_thirty_two = var("c_thirty_two");
let c_sixty_three = var("c_sixty_three");
let c_sixty_four = var("c_sixty_four");
let index1 = var("index1");
let r2flags = var("r2flags");
let index2 = var("index2");
let intcc_eq = Literal::enumerator_for(&imm.intcc, "eq");
let imm64_minus_one = Literal::constant(&imm.imm64, -1);
let imm64_63 = Literal::constant(&imm.imm64, 63);
expand.legalize(
def!(a = clz.I64(x)),
vec![
def!(c_minus_one = iconst(imm64_minus_one)),
def!(c_sixty_three = iconst(imm64_63)),
def!((index1, r2flags) = x86_bsr(x)),
def!(index2 = selectif(intcc_eq, r2flags, c_minus_one, index1)),
def!(a = isub(c_sixty_three, index2)),
],
);
let imm64_31 = Literal::constant(&imm.imm64, 31);
expand.legalize(
def!(a = clz.I32(x)),
vec![
def!(c_minus_one = iconst(imm64_minus_one)),
def!(c_thirty_one = iconst(imm64_31)),
def!((index1, r2flags) = x86_bsr(x)),
def!(index2 = selectif(intcc_eq, r2flags, c_minus_one, index1)),
def!(a = isub(c_thirty_one, index2)),
],
);
let imm64_64 = Literal::constant(&imm.imm64, 64);
expand.legalize(
def!(a = ctz.I64(x)),
vec![
def!(c_sixty_four = iconst(imm64_64)),
def!((index1, r2flags) = x86_bsf(x)),
def!(a = selectif(intcc_eq, r2flags, c_sixty_four, index1)),
],
);
let imm64_32 = Literal::constant(&imm.imm64, 32);
expand.legalize(
def!(a = ctz.I32(x)),
vec![
def!(c_thirty_two = iconst(imm64_32)),
def!((index1, r2flags) = x86_bsf(x)),
def!(a = selectif(intcc_eq, r2flags, c_thirty_two, index1)),
],
);
// Population count for baseline x86_64
let x = var("x");
let r = var("r");
let qv3 = var("qv3");
let qv4 = var("qv4");
let qv5 = var("qv5");
let qv6 = var("qv6");
let qv7 = var("qv7");
let qv8 = var("qv8");
let qv9 = var("qv9");
let qv10 = var("qv10");
let qv11 = var("qv11");
let qv12 = var("qv12");
let qv13 = var("qv13");
let qv14 = var("qv14");
let qv15 = var("qv15");
let qc77 = var("qc77");
#[allow(non_snake_case)]
let qc0F = var("qc0F");
let qc01 = var("qc01");
let imm64_1 = Literal::constant(&imm.imm64, 1);
let imm64_4 = Literal::constant(&imm.imm64, 4);
expand.legalize(
def!(r = popcnt.I64(x)),
vec![
def!(qv3 = ushr_imm(x, imm64_1)),
def!(qc77 = iconst(Literal::constant(&imm.imm64, 0x7777_7777_7777_7777))),
def!(qv4 = band(qv3, qc77)),
def!(qv5 = isub(x, qv4)),
def!(qv6 = ushr_imm(qv4, imm64_1)),
def!(qv7 = band(qv6, qc77)),
def!(qv8 = isub(qv5, qv7)),
def!(qv9 = ushr_imm(qv7, imm64_1)),
def!(qv10 = band(qv9, qc77)),
def!(qv11 = isub(qv8, qv10)),
def!(qv12 = ushr_imm(qv11, imm64_4)),
def!(qv13 = iadd(qv11, qv12)),
def!(qc0F = iconst(Literal::constant(&imm.imm64, 0x0F0F_0F0F_0F0F_0F0F))),
def!(qv14 = band(qv13, qc0F)),
def!(qc01 = iconst(Literal::constant(&imm.imm64, 0x0101_0101_0101_0101))),
def!(qv15 = imul(qv14, qc01)),
def!(r = ushr_imm(qv15, Literal::constant(&imm.imm64, 56))),
],
);
let lv3 = var("lv3");
let lv4 = var("lv4");
let lv5 = var("lv5");
let lv6 = var("lv6");
let lv7 = var("lv7");
let lv8 = var("lv8");
let lv9 = var("lv9");
let lv10 = var("lv10");
let lv11 = var("lv11");
let lv12 = var("lv12");
let lv13 = var("lv13");
let lv14 = var("lv14");
let lv15 = var("lv15");
let lc77 = var("lc77");
#[allow(non_snake_case)]
let lc0F = var("lc0F");
let lc01 = var("lc01");
expand.legalize(
def!(r = popcnt.I32(x)),
vec![
def!(lv3 = ushr_imm(x, imm64_1)),
def!(lc77 = iconst(Literal::constant(&imm.imm64, 0x7777_7777))),
def!(lv4 = band(lv3, lc77)),
def!(lv5 = isub(x, lv4)),
def!(lv6 = ushr_imm(lv4, imm64_1)),
def!(lv7 = band(lv6, lc77)),
def!(lv8 = isub(lv5, lv7)),
def!(lv9 = ushr_imm(lv7, imm64_1)),
def!(lv10 = band(lv9, lc77)),
def!(lv11 = isub(lv8, lv10)),
def!(lv12 = ushr_imm(lv11, imm64_4)),
def!(lv13 = iadd(lv11, lv12)),
def!(lc0F = iconst(Literal::constant(&imm.imm64, 0x0F0F_0F0F))),
def!(lv14 = band(lv13, lc0F)),
def!(lc01 = iconst(Literal::constant(&imm.imm64, 0x0101_0101))),
def!(lv15 = imul(lv14, lc01)),
def!(r = ushr_imm(lv15, Literal::constant(&imm.imm64, 24))),
],
);
expand.custom_legalize(ineg, "convert_ineg");
expand.custom_legalize(tls_value, "expand_tls_value");
widen.custom_legalize(ineg, "convert_ineg");
// To reduce compilation times, separate out large blocks of legalizations by theme.
define_simd(shared, x86_instructions, &mut narrow, &mut narrow_avx);
expand.build_and_add_to(&mut shared.transform_groups);
let narrow_id = narrow.build_and_add_to(&mut shared.transform_groups);
narrow_avx
.chain_with(narrow_id)
.build_and_add_to(&mut shared.transform_groups);
widen.build_and_add_to(&mut shared.transform_groups);
}
fn define_simd(
shared: &mut SharedDefinitions,
x86_instructions: &InstructionGroup,
narrow: &mut TransformGroupBuilder,
narrow_avx: &mut TransformGroupBuilder,
) {
let insts = &shared.instructions;
let band = insts.by_name("band");
let band_not = insts.by_name("band_not");
let bitcast = insts.by_name("bitcast");
let bitselect = insts.by_name("bitselect");
let bor = insts.by_name("bor");
let bnot = insts.by_name("bnot");
let bxor = insts.by_name("bxor");
let extractlane = insts.by_name("extractlane");
let fabs = insts.by_name("fabs");
let fcmp = insts.by_name("fcmp");
let fcvt_from_uint = insts.by_name("fcvt_from_uint");
let fcvt_to_sint_sat = insts.by_name("fcvt_to_sint_sat");
let fcvt_to_uint_sat = insts.by_name("fcvt_to_uint_sat");
let fmax = insts.by_name("fmax");
let fmin = insts.by_name("fmin");
let fneg = insts.by_name("fneg");
let iadd_imm = insts.by_name("iadd_imm");
let icmp = insts.by_name("icmp");
let imax = insts.by_name("imax");
let imin = insts.by_name("imin");
let imul = insts.by_name("imul");
let ineg = insts.by_name("ineg");
let insertlane = insts.by_name("insertlane");
let ishl = insts.by_name("ishl");
let ishl_imm = insts.by_name("ishl_imm");
let raw_bitcast = insts.by_name("raw_bitcast");
let scalar_to_vector = insts.by_name("scalar_to_vector");
let splat = insts.by_name("splat");
let shuffle = insts.by_name("shuffle");
let sshr = insts.by_name("sshr");
let swizzle = insts.by_name("swizzle");
let trueif = insts.by_name("trueif");
let uadd_sat = insts.by_name("uadd_sat");
let umax = insts.by_name("umax");
let umin = insts.by_name("umin");
let snarrow = insts.by_name("snarrow");
let swiden_high = insts.by_name("swiden_high");
let swiden_low = insts.by_name("swiden_low");
let ushr_imm = insts.by_name("ushr_imm");
let ushr = insts.by_name("ushr");
let uwiden_high = insts.by_name("uwiden_high");
let uwiden_low = insts.by_name("uwiden_low");
let vconst = insts.by_name("vconst");
let vall_true = insts.by_name("vall_true");
let vany_true = insts.by_name("vany_true");
let vselect = insts.by_name("vselect");
let x86_palignr = x86_instructions.by_name("x86_palignr");
let x86_pmaxs = x86_instructions.by_name("x86_pmaxs");
let x86_pmaxu = x86_instructions.by_name("x86_pmaxu");
let x86_pmins = x86_instructions.by_name("x86_pmins");
let x86_pminu = x86_instructions.by_name("x86_pminu");
let x86_pshufb = x86_instructions.by_name("x86_pshufb");
let x86_pshufd = x86_instructions.by_name("x86_pshufd");
let x86_psra = x86_instructions.by_name("x86_psra");
let x86_ptest = x86_instructions.by_name("x86_ptest");
let x86_punpckh = x86_instructions.by_name("x86_punpckh");
let x86_punpckl = x86_instructions.by_name("x86_punpckl");
let imm = &shared.imm;
// Set up variables and immediates.
let uimm8_zero = Literal::constant(&imm.uimm8, 0x00);
let uimm8_one = Literal::constant(&imm.uimm8, 0x01);
let uimm8_eight = Literal::constant(&imm.uimm8, 8);
let u128_zeroes = constant(vec![0x00; 16]);
let u128_ones = constant(vec![0xff; 16]);
let u128_seventies = constant(vec![0x70; 16]);
let a = var("a");
let b = var("b");
let c = var("c");
let d = var("d");
let e = var("e");
let f = var("f");
let g = var("g");
let h = var("h");
let x = var("x");
let y = var("y");
let z = var("z");
// Limit the SIMD vector size: eventually multiple vector sizes may be supported
// but for now only SSE-sized vectors are available.
let sse_vector_size: u64 = 128;
let allowed_simd_type = |t: &LaneType| t.lane_bits() >= 8 && t.lane_bits() < 128;
// SIMD splat: 8-bits
for ty in ValueType::all_lane_types().filter(|t| t.lane_bits() == 8) {
let splat_any8x16 = splat.bind(vector(ty, sse_vector_size));
narrow.legalize(
def!(y = splat_any8x16(x)),
vec![
// Move into the lowest 8 bits of an XMM register.
def!(a = scalar_to_vector(x)),
// Zero out a different XMM register; the shuffle mask for moving the lowest byte
// to all other byte lanes is 0x0.
def!(b = vconst(u128_zeroes)),
// PSHUFB takes two XMM operands, one of which is a shuffle mask (i.e. b).
def!(y = x86_pshufb(a, b)),
],
);
}
// SIMD splat: 16-bits
for ty in ValueType::all_lane_types().filter(|t| t.lane_bits() == 16) {
let splat_x16x8 = splat.bind(vector(ty, sse_vector_size));
let raw_bitcast_any16x8_to_i32x4 = raw_bitcast
.bind(vector(I32, sse_vector_size))
.bind(vector(ty, sse_vector_size));
let raw_bitcast_i32x4_to_any16x8 = raw_bitcast
.bind(vector(ty, sse_vector_size))
.bind(vector(I32, sse_vector_size));
narrow.legalize(
def!(y = splat_x16x8(x)),
vec![
// Move into the lowest 16 bits of an XMM register.
def!(a = scalar_to_vector(x)),
// Insert the value again but in the next lowest 16 bits.
def!(b = insertlane(a, x, uimm8_one)),
// No instruction emitted; pretend this is an I32x4 so we can use PSHUFD.
def!(c = raw_bitcast_any16x8_to_i32x4(b)),
// Broadcast the bytes in the XMM register with PSHUFD.
def!(d = x86_pshufd(c, uimm8_zero)),
// No instruction emitted; pretend this is an X16x8 again.
def!(y = raw_bitcast_i32x4_to_any16x8(d)),
],
);
}
// SIMD splat: 32-bits
for ty in ValueType::all_lane_types().filter(|t| t.lane_bits() == 32) {
let splat_any32x4 = splat.bind(vector(ty, sse_vector_size));
narrow.legalize(
def!(y = splat_any32x4(x)),
vec![
// Translate to an x86 MOV to get the value in an XMM register.
def!(a = scalar_to_vector(x)),
// Broadcast the bytes in the XMM register with PSHUFD.
def!(y = x86_pshufd(a, uimm8_zero)),
],
);
}
// SIMD splat: 64-bits
for ty in ValueType::all_lane_types().filter(|t| t.lane_bits() == 64) {
let splat_any64x2 = splat.bind(vector(ty, sse_vector_size));
narrow.legalize(
def!(y = splat_any64x2(x)),
vec![
// Move into the lowest 64 bits of an XMM register.
def!(a = scalar_to_vector(x)),
// Move into the highest 64 bits of the same XMM register.
def!(y = insertlane(a, x, uimm8_one)),
],
);
}
// SIMD swizzle; the following inefficient implementation is due to the Wasm SIMD spec requiring
// mask indexes greater than 15 to have the same semantics as a 0 index. For the spec discussion,
// see https://github.com/WebAssembly/simd/issues/93.
{
let swizzle = swizzle.bind(vector(I8, sse_vector_size));
narrow.legalize(
def!(a = swizzle(x, y)),
vec![
def!(b = vconst(u128_seventies)),
def!(c = uadd_sat(y, b)),
def!(a = x86_pshufb(x, c)),
],
);
}
// SIMD bnot
for ty in ValueType::all_lane_types().filter(allowed_simd_type) {
let bnot = bnot.bind(vector(ty, sse_vector_size));
narrow.legalize(
def!(y = bnot(x)),
vec![def!(a = vconst(u128_ones)), def!(y = bxor(a, x))],
);
}
// SIMD shift right (arithmetic, i16x8 and i32x4)
for ty in &[I16, I32] {
let sshr = sshr.bind(vector(*ty, sse_vector_size));
let bitcast_i64x2 = bitcast.bind(vector(I64, sse_vector_size));
narrow.legalize(
def!(a = sshr(x, y)),
vec![def!(b = bitcast_i64x2(y)), def!(a = x86_psra(x, b))],
);
}
// SIMD shift right (arithmetic, i8x16)
{
let sshr = sshr.bind(vector(I8, sse_vector_size));
let bitcast_i64x2 = bitcast.bind(vector(I64, sse_vector_size));
let raw_bitcast_i16x8 = raw_bitcast.bind(vector(I16, sse_vector_size));
let raw_bitcast_i16x8_again = raw_bitcast.bind(vector(I16, sse_vector_size));
narrow.legalize(
def!(z = sshr(x, y)),
vec![
// Since we will use the high byte of each 16x8 lane, shift an extra 8 bits.
def!(a = iadd_imm(y, uimm8_eight)),
def!(b = bitcast_i64x2(a)),
// Take the low 8 bytes of x, duplicate them in 16x8 lanes, then shift right.
def!(c = x86_punpckl(x, x)),
def!(d = raw_bitcast_i16x8(c)),
def!(e = x86_psra(d, b)),
// Take the high 8 bytes of x, duplicate them in 16x8 lanes, then shift right.
def!(f = x86_punpckh(x, x)),
def!(g = raw_bitcast_i16x8_again(f)),
def!(h = x86_psra(g, b)),
// Re-pack the vector.
def!(z = snarrow(e, h)),
],
);
}
// SIMD shift right (arithmetic, i64x2)
{
let sshr_vector = sshr.bind(vector(I64, sse_vector_size));
let sshr_scalar_lane0 = sshr.bind(I64);
let sshr_scalar_lane1 = sshr.bind(I64);
narrow.legalize(
def!(z = sshr_vector(x, y)),
vec![
// Use scalar operations to shift the first lane.
def!(a = extractlane(x, uimm8_zero)),
def!(b = sshr_scalar_lane0(a, y)),
def!(c = insertlane(x, b, uimm8_zero)),
// Do the same for the second lane.
def!(d = extractlane(x, uimm8_one)),
def!(e = sshr_scalar_lane1(d, y)),
def!(z = insertlane(c, e, uimm8_one)),
],
);
}
// SIMD select
for ty in ValueType::all_lane_types().filter(allowed_simd_type) {
let bitselect = bitselect.bind(vector(ty, sse_vector_size)); // must bind both x/y and c
narrow.legalize(
def!(d = bitselect(c, x, y)),
vec![
def!(a = band(x, c)),
def!(b = band_not(y, c)),
def!(d = bor(a, b)),
],
);
}
// SIMD vselect; replace with bitselect if BLEND* instructions are not available.
// This works, because each lane of boolean vector is filled with zeroes or ones.
for ty in ValueType::all_lane_types().filter(allowed_simd_type) {
let vselect = vselect.bind(vector(ty, sse_vector_size));
let raw_bitcast = raw_bitcast.bind(vector(ty, sse_vector_size));
narrow.legalize(
def!(d = vselect(c, x, y)),
vec![def!(a = raw_bitcast(c)), def!(d = bitselect(a, x, y))],
);
}
// SIMD vany_true
let ne = Literal::enumerator_for(&imm.intcc, "ne");
for ty in ValueType::all_lane_types().filter(allowed_simd_type) {
let vany_true = vany_true.bind(vector(ty, sse_vector_size));
narrow.legalize(
def!(y = vany_true(x)),
vec![def!(a = x86_ptest(x, x)), def!(y = trueif(ne, a))],
);
}
// SIMD vall_true
let eq = Literal::enumerator_for(&imm.intcc, "eq");
for ty in ValueType::all_lane_types().filter(allowed_simd_type) {
let vall_true = vall_true.bind(vector(ty, sse_vector_size));
if ty.is_int() {
// In the common case (Wasm's integer-only all_true), we do not require a
// bitcast.
narrow.legalize(
def!(y = vall_true(x)),
vec![
def!(a = vconst(u128_zeroes)),
def!(c = icmp(eq, x, a)),
def!(d = x86_ptest(c, c)),
def!(y = trueif(eq, d)),
],
);
} else {
// However, to support other types we must bitcast them to an integer vector to
// use icmp.
let lane_type_as_int = LaneType::int_from_bits(ty.lane_bits() as u16);
let raw_bitcast_to_int = raw_bitcast.bind(vector(lane_type_as_int, sse_vector_size));
narrow.legalize(
def!(y = vall_true(x)),
vec![
def!(a = vconst(u128_zeroes)),
def!(b = raw_bitcast_to_int(x)),
def!(c = icmp(eq, b, a)),
def!(d = x86_ptest(c, c)),
def!(y = trueif(eq, d)),
],
);
}
}
// SIMD icmp ne
let ne = Literal::enumerator_for(&imm.intcc, "ne");
for ty in ValueType::all_lane_types().filter(|ty| allowed_simd_type(ty) && ty.is_int()) {
let icmp_ = icmp.bind(vector(ty, sse_vector_size));
narrow.legalize(
def!(c = icmp_(ne, a, b)),
vec![def!(x = icmp(eq, a, b)), def!(c = bnot(x))],
);
}
// SIMD icmp greater-/less-than
let sgt = Literal::enumerator_for(&imm.intcc, "sgt");
let ugt = Literal::enumerator_for(&imm.intcc, "ugt");
let sge = Literal::enumerator_for(&imm.intcc, "sge");
let uge = Literal::enumerator_for(&imm.intcc, "uge");
let slt = Literal::enumerator_for(&imm.intcc, "slt");
let ult = Literal::enumerator_for(&imm.intcc, "ult");
let sle = Literal::enumerator_for(&imm.intcc, "sle");
let ule = Literal::enumerator_for(&imm.intcc, "ule");
for ty in &[I8, I16, I32] {
// greater-than
let icmp_ = icmp.bind(vector(*ty, sse_vector_size));
narrow.legalize(
def!(c = icmp_(ugt, a, b)),
vec![
def!(x = x86_pmaxu(a, b)),
def!(y = icmp(eq, x, b)),
def!(c = bnot(y)),
],
);
let icmp_ = icmp.bind(vector(*ty, sse_vector_size));
narrow.legalize(
def!(c = icmp_(sge, a, b)),
vec![def!(x = x86_pmins(a, b)), def!(c = icmp(eq, x, b))],
);
let icmp_ = icmp.bind(vector(*ty, sse_vector_size));
narrow.legalize(
def!(c = icmp_(uge, a, b)),
vec![def!(x = x86_pminu(a, b)), def!(c = icmp(eq, x, b))],
);
// less-than
let icmp_ = icmp.bind(vector(*ty, sse_vector_size));
narrow.legalize(def!(c = icmp_(slt, a, b)), vec![def!(c = icmp(sgt, b, a))]);
let icmp_ = icmp.bind(vector(*ty, sse_vector_size));
narrow.legalize(def!(c = icmp_(ult, a, b)), vec![def!(c = icmp(ugt, b, a))]);
let icmp_ = icmp.bind(vector(*ty, sse_vector_size));
narrow.legalize(def!(c = icmp_(sle, a, b)), vec![def!(c = icmp(sge, b, a))]);
let icmp_ = icmp.bind(vector(*ty, sse_vector_size));
narrow.legalize(def!(c = icmp_(ule, a, b)), vec![def!(c = icmp(uge, b, a))]);
}
// SIMD integer min/max
for ty in &[I8, I16, I32] {
let imin = imin.bind(vector(*ty, sse_vector_size));
narrow.legalize(def!(c = imin(a, b)), vec![def!(c = x86_pmins(a, b))]);
let umin = umin.bind(vector(*ty, sse_vector_size));
narrow.legalize(def!(c = umin(a, b)), vec![def!(c = x86_pminu(a, b))]);
let imax = imax.bind(vector(*ty, sse_vector_size));
narrow.legalize(def!(c = imax(a, b)), vec![def!(c = x86_pmaxs(a, b))]);
let umax = umax.bind(vector(*ty, sse_vector_size));
narrow.legalize(def!(c = umax(a, b)), vec![def!(c = x86_pmaxu(a, b))]);
}
// SIMD fcmp greater-/less-than
let gt = Literal::enumerator_for(&imm.floatcc, "gt");
let lt = Literal::enumerator_for(&imm.floatcc, "lt");
let ge = Literal::enumerator_for(&imm.floatcc, "ge");
let le = Literal::enumerator_for(&imm.floatcc, "le");
let ugt = Literal::enumerator_for(&imm.floatcc, "ugt");
let ult = Literal::enumerator_for(&imm.floatcc, "ult");
let uge = Literal::enumerator_for(&imm.floatcc, "uge");
let ule = Literal::enumerator_for(&imm.floatcc, "ule");
for ty in &[F32, F64] {
let fcmp_ = fcmp.bind(vector(*ty, sse_vector_size));
narrow.legalize(def!(c = fcmp_(gt, a, b)), vec![def!(c = fcmp(lt, b, a))]);
let fcmp_ = fcmp.bind(vector(*ty, sse_vector_size));
narrow.legalize(def!(c = fcmp_(ge, a, b)), vec![def!(c = fcmp(le, b, a))]);
let fcmp_ = fcmp.bind(vector(*ty, sse_vector_size));
narrow.legalize(def!(c = fcmp_(ult, a, b)), vec![def!(c = fcmp(ugt, b, a))]);
let fcmp_ = fcmp.bind(vector(*ty, sse_vector_size));
narrow.legalize(def!(c = fcmp_(ule, a, b)), vec![def!(c = fcmp(uge, b, a))]);
}
for ty in &[F32, F64] {
let fneg = fneg.bind(vector(*ty, sse_vector_size));
let lane_type_as_int = LaneType::int_from_bits(LaneType::from(*ty).lane_bits() as u16);
let uimm8_shift = Literal::constant(&imm.uimm8, lane_type_as_int.lane_bits() as i64 - 1);
let vconst = vconst.bind(vector(lane_type_as_int, sse_vector_size));
let bitcast_to_float = raw_bitcast.bind(vector(*ty, sse_vector_size));
narrow.legalize(
def!(b = fneg(a)),
vec![
def!(c = vconst(u128_ones)),
def!(d = ishl_imm(c, uimm8_shift)), // Create a mask of all 0s except the MSB.
def!(e = bitcast_to_float(d)), // Cast mask to the floating-point type.
def!(b = bxor(a, e)), // Flip the MSB.
],
);
}
// SIMD fabs
for ty in &[F32, F64] {
let fabs = fabs.bind(vector(*ty, sse_vector_size));
let lane_type_as_int = LaneType::int_from_bits(LaneType::from(*ty).lane_bits() as u16);
let vconst = vconst.bind(vector(lane_type_as_int, sse_vector_size));
let bitcast_to_float = raw_bitcast.bind(vector(*ty, sse_vector_size));
narrow.legalize(
def!(b = fabs(a)),
vec![
def!(c = vconst(u128_ones)),
def!(d = ushr_imm(c, uimm8_one)), // Create a mask of all 1s except the MSB.
def!(e = bitcast_to_float(d)), // Cast mask to the floating-point type.
def!(b = band(a, e)), // Unset the MSB.
],
);
}
// SIMD widen
for ty in &[I8, I16] {
let swiden_high = swiden_high.bind(vector(*ty, sse_vector_size));
narrow.legalize(
def!(b = swiden_high(a)),
vec![
def!(c = x86_palignr(a, a, uimm8_eight)),
def!(b = swiden_low(c)),
],
);
let uwiden_high = uwiden_high.bind(vector(*ty, sse_vector_size));
narrow.legalize(
def!(b = uwiden_high(a)),
vec![
def!(c = x86_palignr(a, a, uimm8_eight)),
def!(b = uwiden_low(c)),
],
);
}
narrow.custom_legalize(shuffle, "convert_shuffle");
narrow.custom_legalize(extractlane, "convert_extractlane");
narrow.custom_legalize(insertlane, "convert_insertlane");
narrow.custom_legalize(ineg, "convert_ineg");
narrow.custom_legalize(ushr, "convert_ushr");
narrow.custom_legalize(ishl, "convert_ishl");
narrow.custom_legalize(fcvt_to_sint_sat, "expand_fcvt_to_sint_sat_vector");
narrow.custom_legalize(fmin, "expand_minmax_vector");
narrow.custom_legalize(fmax, "expand_minmax_vector");
narrow_avx.custom_legalize(imul, "convert_i64x2_imul");
narrow_avx.custom_legalize(fcvt_from_uint, "expand_fcvt_from_uint_vector");
narrow_avx.custom_legalize(fcvt_to_uint_sat, "expand_fcvt_to_uint_sat_vector");
}

View File

@@ -1,87 +0,0 @@
use crate::cdsl::cpu_modes::CpuMode;
use crate::cdsl::isa::TargetIsa;
use crate::cdsl::types::{ReferenceType, VectorType};
use crate::shared::types::Bool::B1;
use crate::shared::types::Float::{F32, F64};
use crate::shared::types::Int::{I16, I32, I64, I8};
use crate::shared::types::Reference::{R32, R64};
use crate::shared::Definitions as SharedDefinitions;
mod encodings;
mod instructions;
mod legalize;
mod opcodes;
mod recipes;
mod registers;
pub(crate) mod settings;
pub(crate) fn define(shared_defs: &mut SharedDefinitions) -> TargetIsa {
let settings = settings::define(&shared_defs.settings);
let regs = registers::define();
let inst_group = instructions::define(
&mut shared_defs.all_instructions,
&shared_defs.formats,
&shared_defs.imm,
&shared_defs.entities,
);
legalize::define(shared_defs, &inst_group);
// CPU modes for 32-bit and 64-bit operations.
let mut x86_64 = CpuMode::new("I64");
let mut x86_32 = CpuMode::new("I32");
let expand_flags = shared_defs.transform_groups.by_name("expand_flags");
let x86_widen = shared_defs.transform_groups.by_name("x86_widen");
let x86_narrow = shared_defs.transform_groups.by_name("x86_narrow");
let x86_narrow_avx = shared_defs.transform_groups.by_name("x86_narrow_avx");
let x86_expand = shared_defs.transform_groups.by_name("x86_expand");
x86_32.legalize_monomorphic(expand_flags);
x86_32.legalize_default(x86_narrow);
x86_32.legalize_type(B1, expand_flags);
x86_32.legalize_type(I8, x86_widen);
x86_32.legalize_type(I16, x86_widen);
x86_32.legalize_type(I32, x86_expand);
x86_32.legalize_value_type(ReferenceType(R32), x86_expand);
x86_32.legalize_type(F32, x86_expand);
x86_32.legalize_type(F64, x86_expand);
x86_32.legalize_value_type(VectorType::new(I32.into(), 4), x86_narrow_avx);
x86_32.legalize_value_type(VectorType::new(I64.into(), 2), x86_narrow_avx);
x86_32.legalize_value_type(VectorType::new(F32.into(), 4), x86_narrow_avx);
x86_64.legalize_monomorphic(expand_flags);
x86_64.legalize_default(x86_narrow);
x86_64.legalize_type(B1, expand_flags);
x86_64.legalize_type(I8, x86_widen);
x86_64.legalize_type(I16, x86_widen);
x86_64.legalize_type(I32, x86_expand);
x86_64.legalize_type(I64, x86_expand);
x86_64.legalize_value_type(ReferenceType(R64), x86_expand);
x86_64.legalize_type(F32, x86_expand);
x86_64.legalize_type(F64, x86_expand);
x86_64.legalize_value_type(VectorType::new(I32.into(), 4), x86_narrow_avx);
x86_64.legalize_value_type(VectorType::new(I64.into(), 2), x86_narrow_avx);
x86_64.legalize_value_type(VectorType::new(F32.into(), 4), x86_narrow_avx);
let recipes = recipes::define(shared_defs, &settings, &regs);
let encodings = encodings::define(shared_defs, &settings, &inst_group, &recipes);
x86_32.set_encodings(encodings.enc32);
x86_64.set_encodings(encodings.enc64);
let encodings_predicates = encodings.inst_pred_reg.extract();
let recipes = encodings.recipes;
let cpu_modes = vec![x86_64, x86_32];
TargetIsa::new(
"x86",
settings,
regs,
recipes,
cpu_modes,
encodings_predicates,
)
}

View File

@@ -1,721 +0,0 @@
//! Static, named definitions of instruction opcodes.
/// Empty opcode for use as a default.
pub static EMPTY: [u8; 0] = [];
/// Add with carry flag r{16,32,64} to r/m of the same size.
pub static ADC: [u8; 1] = [0x11];
/// Add r{16,32,64} to r/m of the same size.
pub static ADD: [u8; 1] = [0x01];
/// Add imm{16,32} to r/m{16,32,64}, possibly sign-extended.
pub static ADD_IMM: [u8; 1] = [0x81];
/// Add sign-extended imm8 to r/m{16,32,64}.
pub static ADD_IMM8_SIGN_EXTEND: [u8; 1] = [0x83];
/// Add packed double-precision floating-point values from xmm2/mem to xmm1 and store result in
/// xmm1 (SSE2).
pub static ADDPD: [u8; 3] = [0x66, 0x0f, 0x58];
/// Add packed single-precision floating-point values from xmm2/mem to xmm1 and store result in
/// xmm1 (SSE).
pub static ADDPS: [u8; 2] = [0x0f, 0x58];
/// Add the low double-precision floating-point value from xmm2/mem to xmm1
/// and store the result in xmm1.
pub static ADDSD: [u8; 3] = [0xf2, 0x0f, 0x58];
/// Add the low single-precision floating-point value from xmm2/mem to xmm1
/// and store the result in xmm1.
pub static ADDSS: [u8; 3] = [0xf3, 0x0f, 0x58];
/// r/m{16,32,64} AND register of the same size (Intel docs have a typo).
pub static AND: [u8; 1] = [0x21];
/// imm{16,32} AND r/m{16,32,64}, possibly sign-extended.
pub static AND_IMM: [u8; 1] = [0x81];
/// r/m{16,32,64} AND sign-extended imm8.
pub static AND_IMM8_SIGN_EXTEND: [u8; 1] = [0x83];
/// Return the bitwise logical AND NOT of packed single-precision floating-point
/// values in xmm1 and xmm2/mem.
pub static ANDNPS: [u8; 2] = [0x0f, 0x55];
/// Return the bitwise logical AND of packed single-precision floating-point values
/// in xmm1 and xmm2/mem.
pub static ANDPS: [u8; 2] = [0x0f, 0x54];
/// Bit scan forward (stores index of first encountered 1 from the front).
pub static BIT_SCAN_FORWARD: [u8; 2] = [0x0f, 0xbc];
/// Bit scan reverse (stores index of first encountered 1 from the back).
pub static BIT_SCAN_REVERSE: [u8; 2] = [0x0f, 0xbd];
/// Select packed single-precision floating-point values from xmm1 and xmm2/m128
/// from mask specified in XMM0 and store the values into xmm1 (SSE4.1).
pub static BLENDVPS: [u8; 4] = [0x66, 0x0f, 0x38, 0x14];
/// Select packed double-precision floating-point values from xmm1 and xmm2/m128
/// from mask specified in XMM0 and store the values into xmm1 (SSE4.1).
pub static BLENDVPD: [u8; 4] = [0x66, 0x0f, 0x38, 0x15];
/// Call near, relative, displacement relative to next instruction (sign-extended).
pub static CALL_RELATIVE: [u8; 1] = [0xe8];
/// Move r/m{16,32,64} if overflow (OF=1).
pub static CMOV_OVERFLOW: [u8; 2] = [0x0f, 0x40];
/// Compare imm{16,32} with r/m{16,32,64} (sign-extended if 64).
pub static CMP_IMM: [u8; 1] = [0x81];
/// Compare imm8 with r/m{16,32,64}.
pub static CMP_IMM8: [u8; 1] = [0x83];
/// Compare r{16,32,64} with r/m of the same size.
pub static CMP_REG: [u8; 1] = [0x39];
/// Compare packed double-precision floating-point value in xmm2/m32 and xmm1 using bits 2:0 of
/// imm8 as comparison predicate (SSE2).
pub static CMPPD: [u8; 3] = [0x66, 0x0f, 0xc2];
/// Compare packed single-precision floating-point value in xmm2/m32 and xmm1 using bits 2:0 of
/// imm8 as comparison predicate (SSE).
pub static CMPPS: [u8; 2] = [0x0f, 0xc2];
/// Convert four packed signed doubleword integers from xmm2/mem to four packed single-precision
/// floating-point values in xmm1 (SSE2).
pub static CVTDQ2PS: [u8; 2] = [0x0f, 0x5b];
/// Convert scalar double-precision floating-point value to scalar single-precision
/// floating-point value.
pub static CVTSD2SS: [u8; 3] = [0xf2, 0x0f, 0x5a];
/// Convert doubleword integer to scalar double-precision floating-point value.
pub static CVTSI2SD: [u8; 3] = [0xf2, 0x0f, 0x2a];
/// Convert doubleword integer to scalar single-precision floating-point value.
pub static CVTSI2SS: [u8; 3] = [0xf3, 0x0f, 0x2a];
/// Convert scalar single-precision floating-point value to scalar double-precision
/// float-point value.
pub static CVTSS2SD: [u8; 3] = [0xf3, 0x0f, 0x5a];
/// Convert four packed single-precision floating-point values from xmm2/mem to four packed signed
/// doubleword values in xmm1 using truncation (SSE2).
pub static CVTTPS2DQ: [u8; 3] = [0xf3, 0x0f, 0x5b];
/// Convert with truncation scalar double-precision floating-point value to signed
/// integer.
pub static CVTTSD2SI: [u8; 3] = [0xf2, 0x0f, 0x2c];
/// Convert with truncation scalar single-precision floating-point value to integer.
pub static CVTTSS2SI: [u8; 3] = [0xf3, 0x0f, 0x2c];
/// Unsigned divide for {16,32,64}-bit.
pub static DIV: [u8; 1] = [0xf7];
/// Divide packed double-precision floating-point values in xmm1 by packed double-precision
/// floating-point values in xmm2/mem (SSE2).
pub static DIVPD: [u8; 3] = [0x66, 0x0f, 0x5e];
/// Divide packed single-precision floating-point values in xmm1 by packed single-precision
/// floating-point values in xmm2/mem (SSE).
pub static DIVPS: [u8; 2] = [0x0f, 0x5e];
/// Divide low double-precision floating-point value in xmm1 by low double-precision
/// floating-point value in xmm2/m64.
pub static DIVSD: [u8; 3] = [0xf2, 0x0f, 0x5e];
/// Divide low single-precision floating-point value in xmm1 by low single-precision
/// floating-point value in xmm2/m32.
pub static DIVSS: [u8; 3] = [0xf3, 0x0f, 0x5e];
/// Signed divide for {16,32,64}-bit.
pub static IDIV: [u8; 1] = [0xf7];
/// Signed multiply for {16,32,64}-bit, generic registers.
pub static IMUL: [u8; 2] = [0x0f, 0xaf];
/// Signed multiply for {16,32,64}-bit, storing into RDX:RAX.
pub static IMUL_RDX_RAX: [u8; 1] = [0xf7];
/// Insert scalar single-precision floating-point value.
pub static INSERTPS: [u8; 4] = [0x66, 0x0f, 0x3a, 0x21];
/// Either:
/// 1. Jump near, absolute indirect, RIP = 64-bit offset from register or memory.
/// 2. Jump far, absolute indirect, address given in m16:64.
pub static JUMP_ABSOLUTE: [u8; 1] = [0xff];
/// Jump near, relative, RIP = RIP + 32-bit displacement sign extended to 64 bits.
pub static JUMP_NEAR_RELATIVE: [u8; 1] = [0xe9];
/// Jump near (rel32) if overflow (OF=1).
pub static JUMP_NEAR_IF_OVERFLOW: [u8; 2] = [0x0f, 0x80];
/// Jump short, relative, RIP = RIP + 8-bit displacement sign extended to 64 bits.
pub static JUMP_SHORT: [u8; 1] = [0xeb];
/// Jump short (rel8) if equal (ZF=1).
pub static JUMP_SHORT_IF_EQUAL: [u8; 1] = [0x74];
/// Jump short (rel8) if not equal (ZF=0).
pub static JUMP_SHORT_IF_NOT_EQUAL: [u8; 1] = [0x75];
/// Jump short (rel8) if overflow (OF=1).
pub static JUMP_SHORT_IF_OVERFLOW: [u8; 1] = [0x70];
/// Store effective address for m in register r{16,32,64}.
pub static LEA: [u8; 1] = [0x8d];
/// Count the number of leading zero bits.
pub static LZCNT: [u8; 3] = [0xf3, 0x0f, 0xbd];
/// Return the maximum packed double-precision floating-point values between xmm1 and xmm2/m128
/// (SSE2).
pub static MAXPD: [u8; 3] = [0x66, 0x0f, 0x5f];
/// Return the maximum packed single-precision floating-point values between xmm1 and xmm2/m128
/// (SSE).
pub static MAXPS: [u8; 2] = [0x0f, 0x5f];
/// Return the maximum scalar double-precision floating-point value between
/// xmm2/m64 and xmm1.
pub static MAXSD: [u8; 3] = [0xf2, 0x0f, 0x5f];
/// Return the maximum scalar single-precision floating-point value between
/// xmm2/m32 and xmm1.
pub static MAXSS: [u8; 3] = [0xf3, 0x0f, 0x5f];
/// Return the minimum packed double-precision floating-point values between xmm1 and xmm2/m128
/// (SSE2).
pub static MINPD: [u8; 3] = [0x66, 0x0f, 0x5d];
/// Return the minimum packed single-precision floating-point values between xmm1 and xmm2/m128
/// (SSE).
pub static MINPS: [u8; 2] = [0x0f, 0x5d];
/// Return the minimum scalar double-precision floating-point value between
/// xmm2/m64 and xmm1.
pub static MINSD: [u8; 3] = [0xf2, 0x0f, 0x5d];
/// Return the minimum scalar single-precision floating-point value between
/// xmm2/m32 and xmm1.
pub static MINSS: [u8; 3] = [0xf3, 0x0f, 0x5d];
/// Move r8 to r/m8.
pub static MOV_BYTE_STORE: [u8; 1] = [0x88];
/// Move imm{16,32,64} to same-sized register.
pub static MOV_IMM: [u8; 1] = [0xb8];
/// Move imm{16,32} to r{16,32,64}, sign-extended if 64-bit target.
pub static MOV_IMM_SIGNEXTEND: [u8; 1] = [0xc7];
/// Move {r/m16, r/m32, r/m64} to same-sized register.
pub static MOV_LOAD: [u8; 1] = [0x8b];
/// Move r16 to r/m16.
pub static MOV_STORE_16: [u8; 2] = [0x66, 0x89];
/// Move {r16, r32, r64} to same-sized register or memory.
pub static MOV_STORE: [u8; 1] = [0x89];
/// Move aligned packed single-precision floating-point values from x/m to xmm (SSE).
pub static MOVAPS_LOAD: [u8; 2] = [0x0f, 0x28];
/// Move doubleword from r/m32 to xmm (SSE2). Quadword with REX prefix.
pub static MOVD_LOAD_XMM: [u8; 3] = [0x66, 0x0f, 0x6e];
/// Move doubleword from xmm to r/m32 (SSE2). Quadword with REX prefix.
pub static MOVD_STORE_XMM: [u8; 3] = [0x66, 0x0f, 0x7e];
/// Move packed single-precision floating-point values low to high (SSE).
pub static MOVLHPS: [u8; 2] = [0x0f, 0x16];
/// Move scalar double-precision floating-point value (from reg/mem to reg).
pub static MOVSD_LOAD: [u8; 3] = [0xf2, 0x0f, 0x10];
/// Move scalar double-precision floating-point value (from reg to reg/mem).
pub static MOVSD_STORE: [u8; 3] = [0xf2, 0x0f, 0x11];
/// Move scalar single-precision floating-point value (from reg to reg/mem).
pub static MOVSS_STORE: [u8; 3] = [0xf3, 0x0f, 0x11];
/// Move scalar single-precision floating-point-value (from reg/mem to reg).
pub static MOVSS_LOAD: [u8; 3] = [0xf3, 0x0f, 0x10];
/// Move byte to register with sign-extension.
pub static MOVSX_BYTE: [u8; 2] = [0x0f, 0xbe];
/// Move word to register with sign-extension.
pub static MOVSX_WORD: [u8; 2] = [0x0f, 0xbf];
/// Move doubleword to register with sign-extension.
pub static MOVSXD: [u8; 1] = [0x63];
/// Move unaligned packed single-precision floating-point from x/m to xmm (SSE).
pub static MOVUPS_LOAD: [u8; 2] = [0x0f, 0x10];
/// Move unaligned packed single-precision floating-point value from xmm to x/m (SSE).
pub static MOVUPS_STORE: [u8; 2] = [0x0f, 0x11];
/// Move byte to register with zero-extension.
pub static MOVZX_BYTE: [u8; 2] = [0x0f, 0xb6];
/// Move word to register with zero-extension.
pub static MOVZX_WORD: [u8; 2] = [0x0f, 0xb7];
/// Unsigned multiply for {16,32,64}-bit.
pub static MUL: [u8; 1] = [0xf7];
/// Multiply packed double-precision floating-point values from xmm2/mem to xmm1 and store result
/// in xmm1 (SSE2).
pub static MULPD: [u8; 3] = [0x66, 0x0f, 0x59];
/// Multiply packed single-precision floating-point values from xmm2/mem to xmm1 and store result
/// in xmm1 (SSE).
pub static MULPS: [u8; 2] = [0x0f, 0x59];
/// Multiply the low double-precision floating-point value in xmm2/m64 by the
/// low double-precision floating-point value in xmm1.
pub static MULSD: [u8; 3] = [0xf2, 0x0f, 0x59];
/// Multiply the low single-precision floating-point value in xmm2/m32 by the
/// low single-precision floating-point value in xmm1.
pub static MULSS: [u8; 3] = [0xf3, 0x0f, 0x59];
/// Reverse each bit of r/m{16,32,64}.
pub static NOT: [u8; 1] = [0xf7];
/// r{16,32,64} OR register of same size.
pub static OR: [u8; 1] = [0x09];
/// imm{16,32} OR r/m{16,32,64}, possibly sign-extended.
pub static OR_IMM: [u8; 1] = [0x81];
/// r/m{16,32,64} OR sign-extended imm8.
pub static OR_IMM8_SIGN_EXTEND: [u8; 1] = [0x83];
/// Return the bitwise logical OR of packed single-precision values in xmm and x/m (SSE).
pub static ORPS: [u8; 2] = [0x0f, 0x56];
/// Compute the absolute value of bytes in xmm2/m128 and store the unsigned result in xmm1 (SSSE3).
pub static PABSB: [u8; 4] = [0x66, 0x0f, 0x38, 0x1c];
/// Compute the absolute value of 32-bit integers in xmm2/m128 and store the unsigned result in
/// xmm1 (SSSE3).
pub static PABSD: [u8; 4] = [0x66, 0x0f, 0x38, 0x1e];
/// Compute the absolute value of 16-bit integers in xmm2/m128 and store the unsigned result in
/// xmm1 (SSSE3).
pub static PABSW: [u8; 4] = [0x66, 0x0f, 0x38, 0x1d];
/// Converts 8 packed signed word integers from xmm1 and from xmm2/m128 into 16 packed signed byte
/// integers in xmm1 using signed saturation (SSE2).
pub static PACKSSWB: [u8; 3] = [0x66, 0x0f, 0x63];
/// Converts 4 packed signed doubleword integers from xmm1 and from xmm2/m128 into 8 packed signed
/// word integers in xmm1 using signed saturation (SSE2).
pub static PACKSSDW: [u8; 3] = [0x66, 0x0f, 0x6b];
/// Converts 8 packed signed word integers from xmm1 and from xmm2/m128 into 16 packed unsigned byte
/// integers in xmm1 using unsigned saturation (SSE2).
pub static PACKUSWB: [u8; 3] = [0x66, 0x0f, 0x67];
/// Converts 4 packed signed doubleword integers from xmm1 and from xmm2/m128 into 8 unpacked signed
/// word integers in xmm1 using unsigned saturation (SSE4.1).
pub static PACKUSDW: [u8; 4] = [0x66, 0x0f, 0x38, 0x2b];
/// Add packed byte integers from xmm2/m128 and xmm1 (SSE2).
pub static PADDB: [u8; 3] = [0x66, 0x0f, 0xfc];
/// Add packed doubleword integers from xmm2/m128 and xmm1 (SSE2).
pub static PADDD: [u8; 3] = [0x66, 0x0f, 0xfe];
/// Add packed quadword integers from xmm2/m128 and xmm1 (SSE2).
pub static PADDQ: [u8; 3] = [0x66, 0x0f, 0xd4];
/// Add packed word integers from xmm2/m128 and xmm1 (SSE2).
pub static PADDW: [u8; 3] = [0x66, 0x0f, 0xfd];
/// Add packed signed byte integers from xmm2/m128 and xmm1 saturate the results (SSE).
pub static PADDSB: [u8; 3] = [0x66, 0x0f, 0xec];
/// Add packed signed word integers from xmm2/m128 and xmm1 saturate the results (SSE).
pub static PADDSW: [u8; 3] = [0x66, 0x0f, 0xed];
/// Add packed unsigned byte integers from xmm2/m128 and xmm1 saturate the results (SSE).
pub static PADDUSB: [u8; 3] = [0x66, 0x0f, 0xdc];
/// Add packed unsigned word integers from xmm2/m128 and xmm1 saturate the results (SSE).
pub static PADDUSW: [u8; 3] = [0x66, 0x0f, 0xdd];
/// Concatenate destination and source operands, extract a byte-aligned result into xmm1 that is
/// shifted to the right by the constant number of bytes in imm8 (SSSE3).
pub static PALIGNR: [u8; 4] = [0x66, 0x0f, 0x3a, 0x0f];
/// Bitwise AND of xmm2/m128 and xmm1 (SSE2).
pub static PAND: [u8; 3] = [0x66, 0x0f, 0xdb];
/// Bitwise AND NOT of xmm2/m128 and xmm1 (SSE2).
pub static PANDN: [u8; 3] = [0x66, 0x0f, 0xdf];
/// Average packed unsigned byte integers from xmm2/m128 and xmm1 with rounding (SSE2).
pub static PAVGB: [u8; 3] = [0x66, 0x0f, 0xE0];
/// Average packed unsigned word integers from xmm2/m128 and xmm1 with rounding (SSE2).
pub static PAVGW: [u8; 3] = [0x66, 0x0f, 0xE3];
/// Select byte values from xmm1 and xmm2/m128 from mask specified in the high bit of each byte
/// in XMM0 and store the values into xmm1 (SSE4.1).
pub static PBLENDVB: [u8; 4] = [0x66, 0x0f, 0x38, 0x10];
/// Select words from xmm1 and xmm2/m128 from mask specified in imm8 and store the values into xmm1
/// (SSE4.1).
pub static PBLENDW: [u8; 4] = [0x66, 0x0f, 0x3a, 0x0e];
/// Compare packed data for equal (SSE2).
pub static PCMPEQB: [u8; 3] = [0x66, 0x0f, 0x74];
/// Compare packed data for equal (SSE2).
pub static PCMPEQD: [u8; 3] = [0x66, 0x0f, 0x76];
/// Compare packed data for equal (SSE4.1).
pub static PCMPEQQ: [u8; 4] = [0x66, 0x0f, 0x38, 0x29];
/// Compare packed data for equal (SSE2).
pub static PCMPEQW: [u8; 3] = [0x66, 0x0f, 0x75];
/// Compare packed signed byte integers for greater than (SSE2).
pub static PCMPGTB: [u8; 3] = [0x66, 0x0f, 0x64];
/// Compare packed signed doubleword integers for greater than (SSE2).
pub static PCMPGTD: [u8; 3] = [0x66, 0x0f, 0x66];
/// Compare packed signed quadword integers for greater than (SSE4.2).
pub static PCMPGTQ: [u8; 4] = [0x66, 0x0f, 0x38, 0x37];
/// Compare packed signed word integers for greater than (SSE2).
pub static PCMPGTW: [u8; 3] = [0x66, 0x0f, 0x65];
/// Extract doubleword or quadword, depending on REX.W (SSE4.1).
pub static PEXTR: [u8; 4] = [0x66, 0x0f, 0x3a, 0x16];
/// Extract byte (SSE4.1).
pub static PEXTRB: [u8; 4] = [0x66, 0x0f, 0x3a, 0x14];
/// Extract word (SSE4.1). There is a 3-byte SSE2 variant that can also move to m/16.
pub static PEXTRW: [u8; 4] = [0x66, 0x0f, 0x3a, 0x15];
/// Insert doubleword or quadword, depending on REX.W (SSE4.1).
pub static PINSR: [u8; 4] = [0x66, 0x0f, 0x3a, 0x22];
/// Insert byte (SSE4.1).
pub static PINSRB: [u8; 4] = [0x66, 0x0f, 0x3a, 0x20];
/// Insert word (SSE2).
pub static PINSRW: [u8; 3] = [0x66, 0x0f, 0xc4];
/// Compare packed signed byte integers in xmm1 and xmm2/m128 and store packed maximum values in
/// xmm1 (SSE4.1).
pub static PMAXSB: [u8; 4] = [0x66, 0x0f, 0x38, 0x3c];
/// Compare packed signed doubleword integers in xmm1 and xmm2/m128 and store packed maximum
/// values in xmm1 (SSE4.1).
pub static PMAXSD: [u8; 4] = [0x66, 0x0f, 0x38, 0x3d];
/// Compare packed signed word integers in xmm1 and xmm2/m128 and store packed maximum values in
/// xmm1 (SSE2).
pub static PMAXSW: [u8; 3] = [0x66, 0x0f, 0xee];
/// Compare packed unsigned byte integers in xmm1 and xmm2/m128 and store packed maximum values in
/// xmm1 (SSE2).
pub static PMAXUB: [u8; 3] = [0x66, 0x0f, 0xde];
/// Compare packed unsigned doubleword integers in xmm1 and xmm2/m128 and store packed maximum
/// values in xmm1 (SSE4.1).
pub static PMAXUD: [u8; 4] = [0x66, 0x0f, 0x38, 0x3f];
/// Compare packed unsigned word integers in xmm1 and xmm2/m128 and store packed maximum values in
/// xmm1 (SSE4.1).
pub static PMAXUW: [u8; 4] = [0x66, 0x0f, 0x38, 0x3e];
/// Compare packed signed byte integers in xmm1 and xmm2/m128 and store packed minimum values in
/// xmm1 (SSE4.1).
pub static PMINSB: [u8; 4] = [0x66, 0x0f, 0x38, 0x38];
/// Compare packed signed doubleword integers in xmm1 and xmm2/m128 and store packed minimum
/// values in xmm1 (SSE4.1).
pub static PMINSD: [u8; 4] = [0x66, 0x0f, 0x38, 0x39];
/// Compare packed signed word integers in xmm1 and xmm2/m128 and store packed minimum values in
/// xmm1 (SSE2).
pub static PMINSW: [u8; 3] = [0x66, 0x0f, 0xea];
/// Compare packed unsigned byte integers in xmm1 and xmm2/m128 and store packed minimum values in
/// xmm1 (SSE2).
pub static PMINUB: [u8; 3] = [0x66, 0x0f, 0xda];
/// Compare packed unsigned doubleword integers in xmm1 and xmm2/m128 and store packed minimum
/// values in xmm1 (SSE4.1).
pub static PMINUD: [u8; 4] = [0x66, 0x0f, 0x38, 0x3b];
/// Compare packed unsigned word integers in xmm1 and xmm2/m128 and store packed minimum values in
/// xmm1 (SSE4.1).
pub static PMINUW: [u8; 4] = [0x66, 0x0f, 0x38, 0x3a];
/// Sign extend 8 packed 8-bit integers in the low 8 bytes of xmm2/m64 to 8 packed 16-bit
/// integers in xmm1 (SSE4.1).
pub static PMOVSXBW: [u8; 4] = [0x66, 0x0f, 0x38, 0x20];
/// Sign extend 4 packed 16-bit integers in the low 8 bytes of xmm2/m64 to 4 packed 32-bit
/// integers in xmm1 (SSE4.1).
pub static PMOVSXWD: [u8; 4] = [0x66, 0x0f, 0x38, 0x23];
/// Sign extend 2 packed 32-bit integers in the low 8 bytes of xmm2/m64 to 2 packed 64-bit
/// integers in xmm1 (SSE4.1).
pub static PMOVSXDQ: [u8; 4] = [0x66, 0x0f, 0x38, 0x25];
/// Zero extend 8 packed 8-bit integers in the low 8 bytes of xmm2/m64 to 8 packed 16-bit
/// integers in xmm1 (SSE4.1).
pub static PMOVZXBW: [u8; 4] = [0x66, 0x0f, 0x38, 0x30];
/// Zero extend 4 packed 16-bit integers in the low 8 bytes of xmm2/m64 to 4 packed 32-bit
/// integers in xmm1 (SSE4.1).
pub static PMOVZXWD: [u8; 4] = [0x66, 0x0f, 0x38, 0x33];
/// Zero extend 2 packed 32-bit integers in the low 8 bytes of xmm2/m64 to 2 packed 64-bit
/// integers in xmm1 (SSE4.1).
pub static PMOVZXDQ: [u8; 4] = [0x66, 0x0f, 0x38, 0x35];
/// Multiply the packed signed word integers in xmm1 and xmm2/m128, and store the low 16 bits of
/// the results in xmm1 (SSE2).
pub static PMULLW: [u8; 3] = [0x66, 0x0f, 0xd5];
/// Multiply the packed doubleword signed integers in xmm1 and xmm2/m128 and store the low 32
/// bits of each product in xmm1 (SSE4.1).
pub static PMULLD: [u8; 4] = [0x66, 0x0f, 0x38, 0x40];
/// Multiply the packed quadword signed integers in xmm2 and xmm3/m128 and store the low 64
/// bits of each product in xmm1 (AVX512VL/DQ). Requires an EVEX encoding.
pub static VPMULLQ: [u8; 4] = [0x66, 0x0f, 0x38, 0x40];
/// Multiply packed unsigned doubleword integers in xmm1 by packed unsigned doubleword integers
/// in xmm2/m128, and store the quadword results in xmm1 (SSE2).
pub static PMULUDQ: [u8; 3] = [0x66, 0x0f, 0xf4];
/// Multiply the packed word integers, add adjacent doubleword results.
pub static PMADDWD: [u8; 3] = [0x66, 0x0f, 0xf5];
/// Pop top of stack into r{16,32,64}; increment stack pointer.
pub static POP_REG: [u8; 1] = [0x58];
/// Returns the count of number of bits set to 1.
pub static POPCNT: [u8; 3] = [0xf3, 0x0f, 0xb8];
/// Bitwise OR of xmm2/m128 and xmm1 (SSE2).
pub static POR: [u8; 3] = [0x66, 0x0f, 0xeb];
/// Shuffle bytes in xmm1 according to contents of xmm2/m128 (SSE3).
pub static PSHUFB: [u8; 4] = [0x66, 0x0f, 0x38, 0x00];
/// Shuffle the doublewords in xmm2/m128 based on the encoding in imm8 and
/// store the result in xmm1 (SSE2).
pub static PSHUFD: [u8; 3] = [0x66, 0x0f, 0x70];
/// Shift words in xmm1 by imm8; the direction and sign-bit behavior is controlled by the RRR
/// digit used in the ModR/M byte (SSE2).
pub static PS_W_IMM: [u8; 3] = [0x66, 0x0f, 0x71];
/// Shift doublewords in xmm1 by imm8; the direction and sign-bit behavior is controlled by the RRR
/// digit used in the ModR/M byte (SSE2).
pub static PS_D_IMM: [u8; 3] = [0x66, 0x0f, 0x72];
/// Shift quadwords in xmm1 by imm8; the direction and sign-bit behavior is controlled by the RRR
/// digit used in the ModR/M byte (SSE2).
pub static PS_Q_IMM: [u8; 3] = [0x66, 0x0f, 0x73];
/// Shift words in xmm1 left by xmm2/m128 while shifting in 0s (SSE2).
pub static PSLLW: [u8; 3] = [0x66, 0x0f, 0xf1];
/// Shift doublewords in xmm1 left by xmm2/m128 while shifting in 0s (SSE2).
pub static PSLLD: [u8; 3] = [0x66, 0x0f, 0xf2];
/// Shift quadwords in xmm1 left by xmm2/m128 while shifting in 0s (SSE2).
pub static PSLLQ: [u8; 3] = [0x66, 0x0f, 0xf3];
/// Shift words in xmm1 right by xmm2/m128 while shifting in 0s (SSE2).
pub static PSRLW: [u8; 3] = [0x66, 0x0f, 0xd1];
/// Shift doublewords in xmm1 right by xmm2/m128 while shifting in 0s (SSE2).
pub static PSRLD: [u8; 3] = [0x66, 0x0f, 0xd2];
/// Shift quadwords in xmm1 right by xmm2/m128 while shifting in 0s (SSE2).
pub static PSRLQ: [u8; 3] = [0x66, 0x0f, 0xd3];
/// Shift words in xmm1 right by xmm2/m128 while shifting in sign bits (SSE2).
pub static PSRAW: [u8; 3] = [0x66, 0x0f, 0xe1];
/// Shift doublewords in xmm1 right by xmm2/m128 while shifting in sign bits (SSE2).
pub static PSRAD: [u8; 3] = [0x66, 0x0f, 0xe2];
/// Subtract packed byte integers in xmm2/m128 from packed byte integers in xmm1 (SSE2).
pub static PSUBB: [u8; 3] = [0x66, 0x0f, 0xf8];
/// Subtract packed word integers in xmm2/m128 from packed word integers in xmm1 (SSE2).
pub static PSUBW: [u8; 3] = [0x66, 0x0f, 0xf9];
/// Subtract packed doubleword integers in xmm2/m128 from doubleword byte integers in xmm1 (SSE2).
pub static PSUBD: [u8; 3] = [0x66, 0x0f, 0xfa];
/// Subtract packed quadword integers in xmm2/m128 from xmm1 (SSE2).
pub static PSUBQ: [u8; 3] = [0x66, 0x0f, 0xfb];
/// Subtract packed signed byte integers in xmm2/m128 from packed signed byte integers in xmm1
/// and saturate results (SSE2).
pub static PSUBSB: [u8; 3] = [0x66, 0x0f, 0xe8];
/// Subtract packed signed word integers in xmm2/m128 from packed signed word integers in xmm1
/// and saturate results (SSE2).
pub static PSUBSW: [u8; 3] = [0x66, 0x0f, 0xe9];
/// Subtract packed unsigned byte integers in xmm2/m128 from packed unsigned byte integers in xmm1
/// and saturate results (SSE2).
pub static PSUBUSB: [u8; 3] = [0x66, 0x0f, 0xd8];
/// Subtract packed unsigned word integers in xmm2/m128 from packed unsigned word integers in xmm1
/// and saturate results (SSE2).
pub static PSUBUSW: [u8; 3] = [0x66, 0x0f, 0xd9];
/// Set ZF if xmm2/m128 AND xmm1 result is all 0s; set CF if xmm2/m128 AND NOT xmm1 result is all
/// 0s (SSE4.1).
pub static PTEST: [u8; 4] = [0x66, 0x0f, 0x38, 0x17];
/// Unpack and interleave high-order bytes from xmm1 and xmm2/m128 into xmm1 (SSE2).
pub static PUNPCKHBW: [u8; 3] = [0x66, 0x0f, 0x68];
/// Unpack and interleave high-order words from xmm1 and xmm2/m128 into xmm1 (SSE2).
pub static PUNPCKHWD: [u8; 3] = [0x66, 0x0f, 0x69];
/// Unpack and interleave high-order doublewords from xmm1 and xmm2/m128 into xmm1 (SSE2).
pub static PUNPCKHDQ: [u8; 3] = [0x66, 0x0f, 0x6A];
/// Unpack and interleave high-order quadwords from xmm1 and xmm2/m128 into xmm1 (SSE2).
pub static PUNPCKHQDQ: [u8; 3] = [0x66, 0x0f, 0x6D];
/// Unpack and interleave low-order bytes from xmm1 and xmm2/m128 into xmm1 (SSE2).
pub static PUNPCKLBW: [u8; 3] = [0x66, 0x0f, 0x60];
/// Unpack and interleave low-order words from xmm1 and xmm2/m128 into xmm1 (SSE2).
pub static PUNPCKLWD: [u8; 3] = [0x66, 0x0f, 0x61];
/// Unpack and interleave low-order doublewords from xmm1 and xmm2/m128 into xmm1 (SSE2).
pub static PUNPCKLDQ: [u8; 3] = [0x66, 0x0f, 0x62];
/// Unpack and interleave low-order quadwords from xmm1 and xmm2/m128 into xmm1 (SSE2).
pub static PUNPCKLQDQ: [u8; 3] = [0x66, 0x0f, 0x6C];
/// Push r{16,32,64}.
pub static PUSH_REG: [u8; 1] = [0x50];
/// Logical exclusive OR (SSE2).
pub static PXOR: [u8; 3] = [0x66, 0x0f, 0xef];
/// Near return to calling procedure.
pub static RET_NEAR: [u8; 1] = [0xc3];
/// General rotation opcode. Kind of rotation depends on encoding.
pub static ROTATE_CL: [u8; 1] = [0xd3];
/// General rotation opcode. Kind of rotation depends on encoding.
pub static ROTATE_IMM8: [u8; 1] = [0xc1];
/// Round scalar doubl-precision floating-point values.
pub static ROUNDSD: [u8; 4] = [0x66, 0x0f, 0x3a, 0x0b];
/// Round scalar single-precision floating-point values.
pub static ROUNDSS: [u8; 4] = [0x66, 0x0f, 0x3a, 0x0a];
/// Subtract with borrow r{16,32,64} from r/m of the same size.
pub static SBB: [u8; 1] = [0x19];
/// Set byte if overflow (OF=1).
pub static SET_BYTE_IF_OVERFLOW: [u8; 2] = [0x0f, 0x90];
/// Compute the square root of the packed double-precision floating-point values and store the
/// result in xmm1 (SSE2).
pub static SQRTPD: [u8; 3] = [0x66, 0x0f, 0x51];
/// Compute the square root of the packed double-precision floating-point values and store the
/// result in xmm1 (SSE).
pub static SQRTPS: [u8; 2] = [0x0f, 0x51];
/// Compute square root of scalar double-precision floating-point value.
pub static SQRTSD: [u8; 3] = [0xf2, 0x0f, 0x51];
/// Compute square root of scalar single-precision value.
pub static SQRTSS: [u8; 3] = [0xf3, 0x0f, 0x51];
/// Subtract r{16,32,64} from r/m of same size.
pub static SUB: [u8; 1] = [0x29];
/// Subtract packed double-precision floating-point values in xmm2/mem from xmm1 and store result
/// in xmm1 (SSE2).
pub static SUBPD: [u8; 3] = [0x66, 0x0f, 0x5c];
/// Subtract packed single-precision floating-point values in xmm2/mem from xmm1 and store result
/// in xmm1 (SSE).
pub static SUBPS: [u8; 2] = [0x0f, 0x5c];
/// Subtract the low double-precision floating-point value in xmm2/m64 from xmm1
/// and store the result in xmm1.
pub static SUBSD: [u8; 3] = [0xf2, 0x0f, 0x5c];
/// Subtract the low single-precision floating-point value in xmm2/m32 from xmm1
/// and store the result in xmm1.
pub static SUBSS: [u8; 3] = [0xf3, 0x0f, 0x5c];
/// AND r8 with r/m8; set SF, ZF, PF according to result.
pub static TEST_BYTE_REG: [u8; 1] = [0x84];
/// AND {r16, r32, r64} with r/m of the same size; set SF, ZF, PF according to result.
pub static TEST_REG: [u8; 1] = [0x85];
/// Count the number of trailing zero bits.
pub static TZCNT: [u8; 3] = [0xf3, 0x0f, 0xbc];
/// Compare low double-precision floating-point values in xmm1 and xmm2/mem64
/// and set the EFLAGS flags accordingly.
pub static UCOMISD: [u8; 3] = [0x66, 0x0f, 0x2e];
/// Compare low single-precision floating-point values in xmm1 and xmm2/mem32
/// and set the EFLAGS flags accordingly.
pub static UCOMISS: [u8; 2] = [0x0f, 0x2e];
/// Raise invalid opcode instruction.
pub static UNDEFINED2: [u8; 2] = [0x0f, 0x0b];
/// Convert four packed unsigned doubleword integers from xmm2/m128/m32bcst to packed
/// single-precision floating-point values in xmm1 with writemask k1. Rounding behavior
/// is controlled by MXCSR but can be overriden by EVEX.L'L in static rounding mode
/// (AVX512VL, AVX512F).
pub static VCVTUDQ2PS: [u8; 3] = [0xf2, 0x0f, 0x7a];
/// imm{16,32} XOR r/m{16,32,64}, possibly sign-extended.
pub static XOR_IMM: [u8; 1] = [0x81];
/// r/m{16,32,64} XOR sign-extended imm8.
pub static XOR_IMM8_SIGN_EXTEND: [u8; 1] = [0x83];
/// r/m{16,32,64} XOR register of the same size.
pub static XOR: [u8; 1] = [0x31];
/// Bitwise logical XOR of packed double-precision floating-point values.
pub static XORPD: [u8; 3] = [0x66, 0x0f, 0x57];
/// Bitwise logical XOR of packed single-precision floating-point values.
pub static XORPS: [u8; 2] = [0x0f, 0x57];

File diff suppressed because it is too large Load Diff

View File

@@ -1,43 +0,0 @@
use crate::cdsl::regs::{IsaRegs, IsaRegsBuilder, RegBankBuilder, RegClassBuilder};
pub(crate) fn define() -> IsaRegs {
let mut regs = IsaRegsBuilder::new();
let builder = RegBankBuilder::new("FloatRegs", "xmm")
.units(16)
.track_pressure(true);
let float_regs = regs.add_bank(builder);
let builder = RegBankBuilder::new("IntRegs", "r")
.units(16)
.names(vec!["rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi"])
.track_pressure(true)
.pinned_reg(15);
let int_regs = regs.add_bank(builder);
let builder = RegBankBuilder::new("FlagRegs", "")
.units(1)
.names(vec!["rflags"])
.track_pressure(false);
let flag_reg = regs.add_bank(builder);
let builder = RegClassBuilder::new_toplevel("GPR", int_regs);
let gpr = regs.add_class(builder);
let builder = RegClassBuilder::new_toplevel("FPR", float_regs);
let fpr = regs.add_class(builder);
let builder = RegClassBuilder::new_toplevel("FLAG", flag_reg);
regs.add_class(builder);
let builder = RegClassBuilder::subclass_of("GPR8", gpr, 0, 8);
let gpr8 = regs.add_class(builder);
let builder = RegClassBuilder::subclass_of("ABCD", gpr8, 0, 4);
regs.add_class(builder);
let builder = RegClassBuilder::subclass_of("FPR8", fpr, 0, 8);
regs.add_class(builder);
regs.build()
}

View File

@@ -7,11 +7,7 @@ mod srcgen;
pub mod error;
pub mod isa;
mod gen_binemit;
mod gen_encodings;
mod gen_inst;
mod gen_legalizer;
mod gen_registers;
mod gen_settings;
mod gen_types;
@@ -25,11 +21,7 @@ pub fn isa_from_arch(arch: &str) -> Result<isa::Isa, String> {
}
/// Generates all the Rust source files used in Cranelift from the meta-language.
pub fn generate(
old_backend_isas: &[isa::Isa],
new_backend_isas: &[isa::Isa],
out_dir: &str,
) -> Result<(), error::Error> {
pub fn generate(isas: &[isa::Isa], out_dir: &str) -> Result<(), error::Error> {
// Create all the definitions:
// - common definitions.
let mut shared_defs = shared::define();
@@ -43,7 +35,7 @@ pub fn generate(
gen_types::generate("types.rs", &out_dir)?;
// - per ISA definitions.
let target_isas = isa::define(old_backend_isas, &mut shared_defs);
let target_isas = isa::define(isas, &mut shared_defs);
// At this point, all definitions are done.
let all_formats = shared_defs.verify_instruction_formats();
@@ -57,70 +49,13 @@ pub fn generate(
&out_dir,
)?;
let extra_legalization_groups: &[&'static str] = if !new_backend_isas.is_empty() {
// The new backend only requires the "expand" legalization group.
&["expand"]
} else {
&[]
};
gen_legalizer::generate(
&target_isas,
&shared_defs.transform_groups,
extra_legalization_groups,
"legalize",
&out_dir,
)?;
for isa in target_isas {
gen_registers::generate(&isa, &format!("registers-{}.rs", isa.name), &out_dir)?;
gen_settings::generate(
&isa.settings,
gen_settings::ParentGroup::Shared,
&format!("settings-{}.rs", isa.name),
&out_dir,
)?;
gen_encodings::generate(
&shared_defs,
&isa,
&format!("encoding-{}.rs", isa.name),
&out_dir,
)?;
gen_binemit::generate(
&isa.name,
&isa.recipes,
&format!("binemit-{}.rs", isa.name),
&out_dir,
)?;
}
for isa in new_backend_isas {
match isa {
isa::Isa::X86 => {
// If the old backend ISAs contained x86, this file has already been generated.
if old_backend_isas.iter().any(|isa| *isa == isa::Isa::X86) {
continue;
}
let settings = crate::isa::x86::settings::define(&shared_defs.settings);
gen_settings::generate(
&settings,
gen_settings::ParentGroup::Shared,
"settings-x86.rs",
&out_dir,
)?;
}
isa::Isa::Arm64 => {
// aarch64 doesn't have platform-specific settings.
}
isa::Isa::S390x => {
// s390x doesn't have platform-specific settings.
}
isa::Isa::Arm32 | isa::Isa::Riscv => todo!(),
}
}
Ok(())

View File

@@ -18,8 +18,6 @@ pub(crate) struct Formats {
pub(crate) call: Rc<InstructionFormat>,
pub(crate) call_indirect: Rc<InstructionFormat>,
pub(crate) cond_trap: Rc<InstructionFormat>,
pub(crate) copy_special: Rc<InstructionFormat>,
pub(crate) copy_to_ssa: Rc<InstructionFormat>,
pub(crate) float_compare: Rc<InstructionFormat>,
pub(crate) float_cond: Rc<InstructionFormat>,
pub(crate) float_cond_trap: Rc<InstructionFormat>,
@@ -37,9 +35,6 @@ pub(crate) struct Formats {
pub(crate) load_no_offset: Rc<InstructionFormat>,
pub(crate) multiary: Rc<InstructionFormat>,
pub(crate) nullary: Rc<InstructionFormat>,
pub(crate) reg_fill: Rc<InstructionFormat>,
pub(crate) reg_move: Rc<InstructionFormat>,
pub(crate) reg_spill: Rc<InstructionFormat>,
pub(crate) shuffle: Rc<InstructionFormat>,
pub(crate) stack_load: Rc<InstructionFormat>,
pub(crate) stack_store: Rc<InstructionFormat>,
@@ -283,33 +278,6 @@ impl Formats {
.imm(&imm.offset32)
.build(),
reg_move: Builder::new("RegMove")
.value()
.imm_with_name("src", &imm.regunit)
.imm_with_name("dst", &imm.regunit)
.build(),
copy_special: Builder::new("CopySpecial")
.imm_with_name("src", &imm.regunit)
.imm_with_name("dst", &imm.regunit)
.build(),
copy_to_ssa: Builder::new("CopyToSsa")
.imm_with_name("src", &imm.regunit)
.build(),
reg_spill: Builder::new("RegSpill")
.value()
.imm_with_name("src", &imm.regunit)
.imm_with_name("dst", &entities.stack_slot)
.build(),
reg_fill: Builder::new("RegFill")
.value()
.imm_with_name("src", &entities.stack_slot)
.imm_with_name("dst", &imm.regunit)
.build(),
trap: Builder::new("Trap").imm(&imm.trapcode).build(),
cond_trap: Builder::new("CondTrap").value().imm(&imm.trapcode).build(),

View File

@@ -64,9 +64,6 @@ pub(crate) struct Immediates {
/// Flags for memory operations like `load` and `store`.
pub memflags: OperandKind,
/// A register unit in the current target ISA.
pub regunit: OperandKind,
/// A trap code indicating the reason for trapping.
///
/// The Rust enum type also has a `User(u16)` variant for user-provided trap codes.
@@ -149,8 +146,6 @@ impl Immediates {
},
memflags: new_imm("flags", "ir::MemFlags").with_doc("Memory operation flags"),
regunit: new_imm("regunit", "isa::RegUnit")
.with_doc("A register unit in the target ISA"),
trapcode: {
let mut trapcode_values = HashMap::new();
trapcode_values.insert("stk_ovf", "StackOverflow");

View File

@@ -1,7 +1,7 @@
#![allow(non_snake_case)]
use crate::cdsl::instructions::{
AllInstructions, InstructionBuilder as Inst, InstructionGroup, InstructionGroupBuilder,
AllInstructions, InstructionBuilder as Inst, InstructionGroupBuilder,
};
use crate::cdsl::operands::Operand;
use crate::cdsl::type_inference::Constraint::WiderOrEq;
@@ -767,7 +767,7 @@ pub(crate) fn define(
formats: &Formats,
imm: &Immediates,
entities: &EntityRefs,
) -> InstructionGroup {
) {
let mut ig = InstructionGroupBuilder::new(all_instructions);
define_control_flow(&mut ig, formats, imm, entities);
@@ -1929,90 +1929,6 @@ pub(crate) fn define(
.can_load(true),
);
let Sarg = &TypeVar::new(
"Sarg",
"Any scalar or vector type with at most 128 lanes",
TypeSetBuilder::new()
.specials(vec![crate::cdsl::types::SpecialType::StructArgument])
.build(),
);
let sarg_t = &Operand::new("sarg_t", Sarg);
// FIXME remove once the old style codegen backends are removed.
ig.push(
Inst::new(
"dummy_sarg_t",
r#"
This creates a sarg_t
This instruction is internal and should not be created by
Cranelift users.
"#,
&formats.nullary,
)
.operands_in(vec![])
.operands_out(vec![sarg_t]),
);
let src = &Operand::new("src", &imm.regunit);
let dst = &Operand::new("dst", &imm.regunit);
ig.push(
Inst::new(
"regmove",
r#"
Temporarily divert ``x`` from ``src`` to ``dst``.
This instruction moves the location of a value from one register to
another without creating a new SSA value. It is used by the register
allocator to temporarily rearrange register assignments in order to
satisfy instruction constraints.
The register diversions created by this instruction must be undone
before the value leaves the block. At the entry to a new block, all live
values must be in their originally assigned registers.
"#,
&formats.reg_move,
)
.operands_in(vec![x, src, dst])
.other_side_effects(true),
);
ig.push(
Inst::new(
"copy_special",
r#"
Copies the contents of ''src'' register to ''dst'' register.
This instructions copies the contents of one register to another
register without involving any SSA values. This is used for copying
special registers, e.g. copying the stack register to the frame
register in a function prologue.
"#,
&formats.copy_special,
)
.operands_in(vec![src, dst])
.other_side_effects(true),
);
ig.push(
Inst::new(
"copy_to_ssa",
r#"
Copies the contents of ''src'' register to ''a'' SSA name.
This instruction copies the contents of one register, regardless of its SSA name, to
another register, creating a new SSA name. In that sense it is a one-sided version
of ''copy_special''. This instruction is internal and should not be created by
Cranelift users.
"#,
&formats.copy_to_ssa,
)
.operands_in(vec![src])
.operands_out(vec![a])
.other_side_effects(true),
);
ig.push(
Inst::new(
"copy_nop",
@@ -2098,44 +2014,6 @@ pub(crate) fn define(
.operands_out(vec![f]),
);
ig.push(
Inst::new(
"regspill",
r#"
Temporarily divert ``x`` from ``src`` to ``SS``.
This instruction moves the location of a value from a register to a
stack slot without creating a new SSA value. It is used by the register
allocator to temporarily rearrange register assignments in order to
satisfy instruction constraints.
See also `regmove`.
"#,
&formats.reg_spill,
)
.operands_in(vec![x, src, SS])
.other_side_effects(true),
);
ig.push(
Inst::new(
"regfill",
r#"
Temporarily divert ``x`` from ``SS`` to ``dst``.
This instruction moves the location of a value from a stack slot to a
register without creating a new SSA value. It is used by the register
allocator to temporarily rearrange register assignments in order to
satisfy instruction constraints.
See also `regmove`.
"#,
&formats.reg_fill,
)
.operands_in(vec![x, SS, dst])
.other_side_effects(true),
);
let N =
&Operand::new("args", &entities.varargs).with_doc("Variable number of args for StackMap");
@@ -2302,10 +2180,9 @@ pub(crate) fn define(
| of | * | Overflow |
| nof | * | No Overflow |
\* The unsigned version of overflow conditions have ISA-specific
semantics and thus have been kept as methods on the TargetIsa trait as
[unsigned_add_overflow_condition][isa::TargetIsa::unsigned_add_overflow_condition] and
[unsigned_sub_overflow_condition][isa::TargetIsa::unsigned_sub_overflow_condition].
\* The unsigned version of overflow condition for add has ISA-specific semantics and thus
has been kept as a method on the TargetIsa trait as
[unsigned_add_overflow_condition][crate::isa::TargetIsa::unsigned_add_overflow_condition].
When this instruction compares integer vectors, it returns a boolean
vector of lane-wise comparisons.
@@ -4047,7 +3924,7 @@ pub(crate) fn define(
Combine `x` and `y` into a vector with twice the lanes but half the integer width while
saturating overflowing values to the unsigned maximum and minimum.
Note that all input lanes are considered unsigned.
Note that all input lanes are considered unsigned: any negative values will be interpreted as unsigned, overflowing and being replaced with the unsigned maximum.
The lanes will be concatenated after narrowing. For example, when `x` and `y` are `i32x4`
and `x = [x3, x2, x1, x0]` and `y = [y3, y2, y1, y0]`, then after narrowing the value
@@ -4647,6 +4524,4 @@ pub(crate) fn define(
)
.other_side_effects(true),
);
ig.build()
}

File diff suppressed because it is too large Load Diff

View File

@@ -4,14 +4,12 @@ pub mod entities;
pub mod formats;
pub mod immediates;
pub mod instructions;
pub mod legalize;
pub mod settings;
pub mod types;
use crate::cdsl::formats::{FormatStructure, InstructionFormat};
use crate::cdsl::instructions::{AllInstructions, InstructionGroup};
use crate::cdsl::instructions::AllInstructions;
use crate::cdsl::settings::SettingGroup;
use crate::cdsl::xform::TransformGroups;
use crate::shared::entities::EntityRefs;
use crate::shared::formats::Formats;
@@ -24,11 +22,6 @@ use std::rc::Rc;
pub(crate) struct Definitions {
pub settings: SettingGroup,
pub all_instructions: AllInstructions,
pub instructions: InstructionGroup,
pub imm: Immediates,
pub formats: Formats,
pub transform_groups: TransformGroups,
pub entities: EntityRefs,
}
pub(crate) fn define() -> Definitions {
@@ -37,18 +30,11 @@ pub(crate) fn define() -> Definitions {
let immediates = Immediates::new();
let entities = EntityRefs::new();
let formats = Formats::new(&immediates, &entities);
let instructions =
instructions::define(&mut all_instructions, &formats, &immediates, &entities);
let transform_groups = legalize::define(&instructions, &immediates);
instructions::define(&mut all_instructions, &formats, &immediates, &entities);
Definitions {
settings: settings::define(),
all_instructions,
instructions,
imm: immediates,
formats,
transform_groups,
entities,
}
}

View File

@@ -77,15 +77,6 @@ impl Formatter {
}
}
/// Get a string containing whitespace outdented one level. Used for
/// lines of code that are inside a single indented block.
fn get_outdent(&mut self) -> String {
self.indent_pop();
let s = self.get_indent();
self.indent_push();
s
}
/// Add an indented line.
pub fn line(&mut self, contents: impl AsRef<str>) {
let indented_line = format!("{}{}\n", self.get_indent(), contents.as_ref());
@@ -97,12 +88,6 @@ impl Formatter {
self.lines.push("\n".to_string());
}
/// Emit a line outdented one level.
pub fn outdented_line(&mut self, s: &str) {
let new_line = format!("{}{}\n", self.get_outdent(), s);
self.lines.push(new_line);
}
/// Write `self.lines` to a file.
pub fn update_file(
&self,

View File

@@ -32,9 +32,6 @@ impl<'entries, T: Eq + Hash> UniqueTable<'entries, T> {
pub fn len(&self) -> usize {
self.table.len()
}
pub fn get(&self, index: usize) -> &T {
self.table[index]
}
pub fn iter(&self) -> slice::Iter<&'entries T> {
self.table.iter()
}

View File

@@ -1,3 +0,0 @@
//! Shared ISA-specific definitions.
pub mod x86;

View File

@@ -1,419 +0,0 @@
//! Provides a named interface to the `u16` Encoding bits.
use std::ops::RangeInclusive;
/// Named interface to the `u16` Encoding bits, representing an opcode.
///
/// Cranelift requires each recipe to have a single encoding size in bytes.
/// X86 opcodes are variable length, so we use separate recipes for different
/// styles of opcodes and prefixes. The opcode format is indicated by the
/// recipe name prefix.
///
/// 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)
#[derive(Copy, Clone, PartialEq)]
pub struct EncodingBits(u16);
const OPCODE: RangeInclusive<u16> = 0..=7;
const OPCODE_PREFIX: RangeInclusive<u16> = 8..=11; // Includes pp and mm.
const RRR: RangeInclusive<u16> = 12..=14;
const REX_W: RangeInclusive<u16> = 15..=15;
impl From<u16> for EncodingBits {
fn from(bits: u16) -> Self {
Self(bits)
}
}
impl EncodingBits {
/// Constructs a new EncodingBits from parts.
pub fn new(op_bytes: &[u8], rrr: u16, rex_w: u16) -> Self {
assert!(
!op_bytes.is_empty(),
"op_bytes must include at least one opcode byte"
);
let mut new = Self::from(0);
let last_byte = op_bytes[op_bytes.len() - 1];
new.write(OPCODE, last_byte as u16);
let prefix: u8 = OpcodePrefix::from_opcode(op_bytes).into();
new.write(OPCODE_PREFIX, prefix as u16);
new.write(RRR, rrr);
new.write(REX_W, rex_w);
new
}
/// Returns a copy of the EncodingBits with the RRR bits set.
#[inline]
pub fn with_rrr(mut self, rrr: u8) -> Self {
debug_assert_eq!(self.rrr(), 0);
self.write(RRR, rrr.into());
self
}
/// Returns a copy of the EncodingBits with the REX.W bit set.
#[inline]
pub fn with_rex_w(mut self) -> Self {
debug_assert_eq!(self.rex_w(), 0);
self.write(REX_W, 1);
self
}
/// Returns the raw bits.
#[inline]
pub fn bits(self) -> u16 {
self.0
}
/// Convenience method for writing bits to specific range.
#[inline]
fn write(&mut self, range: RangeInclusive<u16>, value: u16) {
assert!(ExactSizeIterator::len(&range) > 0);
let size = range.end() - range.start() + 1; // Calculate the number of bits in the range.
let mask = (1 << size) - 1; // Generate a bit mask.
debug_assert!(
value <= mask,
"The written value should have fewer than {} bits.",
size
);
let mask_complement = !(mask << *range.start()); // Create the bitwise complement for the clear mask.
self.0 &= mask_complement; // Clear the bits in `range`.
let value = (value & mask) << *range.start(); // Place the value in the correct location.
self.0 |= value; // Modify the bits in `range`.
}
/// Convenience method for reading bits from a specific range.
#[inline]
fn read(self, range: RangeInclusive<u16>) -> u8 {
assert!(ExactSizeIterator::len(&range) > 0);
let size = range.end() - range.start() + 1; // Calculate the number of bits in the range.
debug_assert!(size <= 8, "This structure expects ranges of at most 8 bits");
let mask = (1 << size) - 1; // Generate a bit mask.
((self.0 >> *range.start()) & mask) as u8
}
/// Instruction opcode byte, without the prefix.
#[inline]
pub fn opcode_byte(self) -> u8 {
self.read(OPCODE)
}
/// Prefix kind for the instruction, as an enum.
#[inline]
pub fn prefix(self) -> OpcodePrefix {
OpcodePrefix::from(self.read(OPCODE_PREFIX))
}
/// Extracts the PP bits of the OpcodePrefix.
#[inline]
pub fn pp(self) -> u8 {
self.prefix().to_primitive() & 0x3
}
/// Extracts the MM bits of the OpcodePrefix.
#[inline]
pub fn mm(self) -> u8 {
(self.prefix().to_primitive() >> 2) & 0x3
}
/// Bits for the ModR/M byte for certain opcodes.
#[inline]
pub fn rrr(self) -> u8 {
self.read(RRR)
}
/// REX.W bit (or VEX.W/E).
#[inline]
pub fn rex_w(self) -> u8 {
self.read(REX_W)
}
}
/// Opcode prefix representation.
///
/// The prefix type occupies four of the EncodingBits.
#[allow(non_camel_case_types)]
#[allow(missing_docs)]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum OpcodePrefix {
Op1,
Mp1_66,
Mp1_f3,
Mp1_f2,
Op2_0f,
Mp2_66_0f,
Mp2_f3_0f,
Mp2_f2_0f,
Op3_0f_38,
Mp3_66_0f_38,
Mp3_f3_0f_38,
Mp3_f2_0f_38,
Op3_0f_3a,
Mp3_66_0f_3a,
Mp3_f3_0f_3a,
Mp3_f2_0f_3a,
}
impl From<u8> for OpcodePrefix {
fn from(n: u8) -> Self {
use OpcodePrefix::*;
match n {
0b0000 => Op1,
0b0001 => Mp1_66,
0b0010 => Mp1_f3,
0b0011 => Mp1_f2,
0b0100 => Op2_0f,
0b0101 => Mp2_66_0f,
0b0110 => Mp2_f3_0f,
0b0111 => Mp2_f2_0f,
0b1000 => Op3_0f_38,
0b1001 => Mp3_66_0f_38,
0b1010 => Mp3_f3_0f_38,
0b1011 => Mp3_f2_0f_38,
0b1100 => Op3_0f_3a,
0b1101 => Mp3_66_0f_3a,
0b1110 => Mp3_f3_0f_3a,
0b1111 => Mp3_f2_0f_3a,
_ => panic!("invalid opcode prefix"),
}
}
}
impl Into<u8> for OpcodePrefix {
fn into(self) -> u8 {
use OpcodePrefix::*;
match self {
Op1 => 0b0000,
Mp1_66 => 0b0001,
Mp1_f3 => 0b0010,
Mp1_f2 => 0b0011,
Op2_0f => 0b0100,
Mp2_66_0f => 0b0101,
Mp2_f3_0f => 0b0110,
Mp2_f2_0f => 0b0111,
Op3_0f_38 => 0b1000,
Mp3_66_0f_38 => 0b1001,
Mp3_f3_0f_38 => 0b1010,
Mp3_f2_0f_38 => 0b1011,
Op3_0f_3a => 0b1100,
Mp3_66_0f_3a => 0b1101,
Mp3_f3_0f_3a => 0b1110,
Mp3_f2_0f_3a => 0b1111,
}
}
}
impl OpcodePrefix {
/// Convert an opcode prefix to a `u8`; this is a convenience proxy for `Into<u8>`.
fn to_primitive(self) -> u8 {
self.into()
}
/// Extracts the OpcodePrefix from the opcode.
pub fn from_opcode(op_bytes: &[u8]) -> Self {
assert!(!op_bytes.is_empty(), "at least one opcode byte");
let prefix_bytes = &op_bytes[..op_bytes.len() - 1];
match prefix_bytes {
[] => Self::Op1,
[0x66] => Self::Mp1_66,
[0xf3] => Self::Mp1_f3,
[0xf2] => Self::Mp1_f2,
[0x0f] => Self::Op2_0f,
[0x66, 0x0f] => Self::Mp2_66_0f,
[0xf3, 0x0f] => Self::Mp2_f3_0f,
[0xf2, 0x0f] => Self::Mp2_f2_0f,
[0x0f, 0x38] => Self::Op3_0f_38,
[0x66, 0x0f, 0x38] => Self::Mp3_66_0f_38,
[0xf3, 0x0f, 0x38] => Self::Mp3_f3_0f_38,
[0xf2, 0x0f, 0x38] => Self::Mp3_f2_0f_38,
[0x0f, 0x3a] => Self::Op3_0f_3a,
[0x66, 0x0f, 0x3a] => Self::Mp3_66_0f_3a,
[0xf3, 0x0f, 0x3a] => Self::Mp3_f3_0f_3a,
[0xf2, 0x0f, 0x3a] => Self::Mp3_f2_0f_3a,
_ => {
panic!("unexpected opcode sequence: {:?}", op_bytes);
}
}
}
/// Returns the recipe name prefix.
///
/// At the moment, each similar OpcodePrefix group is given its own Recipe.
/// In order to distinguish them, this string is prefixed.
pub fn recipe_name_prefix(self) -> &'static str {
use OpcodePrefix::*;
match self {
Op1 => "Op1",
Op2_0f => "Op2",
Op3_0f_38 | Op3_0f_3a => "Op3",
Mp1_66 | Mp1_f3 | Mp1_f2 => "Mp1",
Mp2_66_0f | Mp2_f3_0f | Mp2_f2_0f => "Mp2",
Mp3_66_0f_38 | Mp3_f3_0f_38 | Mp3_f2_0f_38 => "Mp3",
Mp3_66_0f_3a | Mp3_f3_0f_3a | Mp3_f2_0f_3a => "Mp3",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
/// Helper function for prefix_roundtrip() to avoid long lines.
fn test_roundtrip(p: OpcodePrefix) {
assert_eq!(p, OpcodePrefix::from(p.to_primitive()));
}
/// Tests that to/from each opcode matches.
#[test]
fn prefix_roundtrip() {
test_roundtrip(OpcodePrefix::Op1);
test_roundtrip(OpcodePrefix::Mp1_66);
test_roundtrip(OpcodePrefix::Mp1_f3);
test_roundtrip(OpcodePrefix::Mp1_f2);
test_roundtrip(OpcodePrefix::Op2_0f);
test_roundtrip(OpcodePrefix::Mp2_66_0f);
test_roundtrip(OpcodePrefix::Mp2_f3_0f);
test_roundtrip(OpcodePrefix::Mp2_f2_0f);
test_roundtrip(OpcodePrefix::Op3_0f_38);
test_roundtrip(OpcodePrefix::Mp3_66_0f_38);
test_roundtrip(OpcodePrefix::Mp3_f3_0f_38);
test_roundtrip(OpcodePrefix::Mp3_f2_0f_38);
test_roundtrip(OpcodePrefix::Op3_0f_3a);
test_roundtrip(OpcodePrefix::Mp3_66_0f_3a);
test_roundtrip(OpcodePrefix::Mp3_f3_0f_3a);
test_roundtrip(OpcodePrefix::Mp3_f2_0f_3a);
}
#[test]
fn prefix_to_name() {
assert_eq!(OpcodePrefix::Op1.recipe_name_prefix(), "Op1");
assert_eq!(OpcodePrefix::Op2_0f.recipe_name_prefix(), "Op2");
assert_eq!(OpcodePrefix::Op3_0f_38.recipe_name_prefix(), "Op3");
assert_eq!(OpcodePrefix::Mp1_66.recipe_name_prefix(), "Mp1");
assert_eq!(OpcodePrefix::Mp2_66_0f.recipe_name_prefix(), "Mp2");
assert_eq!(OpcodePrefix::Mp3_66_0f_3a.recipe_name_prefix(), "Mp3");
}
/// Tests that the opcode_byte is the lower of the EncodingBits.
#[test]
fn encodingbits_opcode_byte() {
let enc = EncodingBits::from(0x00ff);
assert_eq!(enc.opcode_byte(), 0xff);
assert_eq!(enc.prefix().to_primitive(), 0x0);
assert_eq!(enc.rrr(), 0x0);
assert_eq!(enc.rex_w(), 0x0);
let enc = EncodingBits::from(0x00cd);
assert_eq!(enc.opcode_byte(), 0xcd);
}
/// Tests that the OpcodePrefix is encoded correctly.
#[test]
fn encodingbits_prefix() {
let enc = EncodingBits::from(0x0c00);
assert_eq!(enc.opcode_byte(), 0x00);
assert_eq!(enc.prefix().to_primitive(), 0xc);
assert_eq!(enc.prefix(), OpcodePrefix::Op3_0f_3a);
assert_eq!(enc.rrr(), 0x0);
assert_eq!(enc.rex_w(), 0x0);
}
/// Tests that the PP bits are encoded correctly.
#[test]
fn encodingbits_pp() {
let enc = EncodingBits::from(0x0300);
assert_eq!(enc.opcode_byte(), 0x0);
assert_eq!(enc.pp(), 0x3);
assert_eq!(enc.mm(), 0x0);
assert_eq!(enc.rrr(), 0x0);
assert_eq!(enc.rex_w(), 0x0);
}
/// Tests that the MM bits are encoded correctly.
#[test]
fn encodingbits_mm() {
let enc = EncodingBits::from(0x0c00);
assert_eq!(enc.opcode_byte(), 0x0);
assert_eq!(enc.pp(), 0x00);
assert_eq!(enc.mm(), 0x3);
assert_eq!(enc.rrr(), 0x0);
assert_eq!(enc.rex_w(), 0x0);
}
/// Tests that the ModR/M bits are encoded correctly.
#[test]
fn encodingbits_rrr() {
let enc = EncodingBits::from(0x5000);
assert_eq!(enc.opcode_byte(), 0x0);
assert_eq!(enc.prefix().to_primitive(), 0x0);
assert_eq!(enc.rrr(), 0x5);
assert_eq!(enc.rex_w(), 0x0);
}
/// Tests that the REX.W bit is encoded correctly.
#[test]
fn encodingbits_rex_w() {
let enc = EncodingBits::from(0x8000);
assert_eq!(enc.opcode_byte(), 0x00);
assert_eq!(enc.prefix().to_primitive(), 0x0);
assert_eq!(enc.rrr(), 0x0);
assert_eq!(enc.rex_w(), 0x1);
}
/// Tests setting and unsetting a bit using EncodingBits::write.
#[test]
fn encodingbits_flip() {
let mut bits = EncodingBits::from(0);
let range = 2..=2;
bits.write(range.clone(), 1);
assert_eq!(bits.bits(), 0b100);
bits.write(range, 0);
assert_eq!(bits.bits(), 0b000);
}
/// Tests a round-trip of EncodingBits from/to a u16 (hardcoded endianness).
#[test]
fn encodingbits_roundtrip() {
let bits: u16 = 0x1234;
assert_eq!(EncodingBits::from(bits).bits(), bits);
}
#[test]
// I purposely want to divide the bits using the ranges defined above.
#[allow(clippy::inconsistent_digit_grouping)]
fn encodingbits_construction() {
assert_eq!(
EncodingBits::new(&[0x66, 0x40], 5, 1).bits(),
0b1_101_0001_01000000 // 1 = rex_w, 101 = rrr, 0001 = prefix, 01000000 = opcode
);
}
#[test]
#[should_panic]
fn encodingbits_panics_at_write_to_invalid_range() {
EncodingBits::from(0).write(1..=0, 42);
}
#[test]
#[should_panic]
fn encodingbits_panics_at_read_to_invalid_range() {
EncodingBits::from(0).read(1..=0);
}
}

View File

@@ -1,4 +0,0 @@
//! Shared x86-specific definitions.
mod encoding_bits;
pub use encoding_bits::*;

View File

@@ -19,10 +19,8 @@
)
)]
pub mod condcodes;
pub mod constant_hash;
pub mod constants;
pub mod isa;
/// Version number of this crate.
pub const VERSION: &str = env!("CARGO_PKG_VERSION");

View File

@@ -1,270 +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 crate::ir::{AbiParam, ArgumentExtension, ArgumentLoc, Type};
use alloc::borrow::Cow;
use alloc::vec::Vec;
use core::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, Debug)]
pub enum ArgAction {
/// Assign the argument to the given location.
Assign(ArgumentLoc),
/// Assign the argument to the given location and change the type to the specified type.
/// This is used by [`ArgumentPurpose::StructArgument`].
AssignAndChangeType(ArgumentLoc, Type),
/// 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) -> Self {
Self::Assign(x)
}
}
impl From<ValueConversion> for ArgAction {
fn from(x: ValueConversion) -> Self {
Self::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),
/// Pass value by pointer of given integer type.
Pointer(Type),
}
impl ValueConversion {
/// Apply this conversion to a type, return the converted type.
pub fn apply(self, ty: Type) -> Type {
match self {
Self::IntSplit => ty.half_width().expect("Integer type too small to split"),
Self::VectorSplit => ty.half_vector().expect("Not a vector"),
Self::IntBits => Type::int(ty.bits()).expect("Bad integer size"),
Self::Sext(nty) | Self::Uext(nty) | Self::Pointer(nty) => nty,
}
}
/// Is this a split conversion that results in two arguments?
pub fn is_split(self) -> bool {
match self {
Self::IntSplit | Self::VectorSplit => true,
_ => false,
}
}
/// Is this a conversion to pointer?
pub fn is_pointer(self) -> bool {
match self {
Self::Pointer(_) => 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: &AbiParam) -> 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: &[AbiParam], aa: &mut AA) -> Option<Vec<AbiParam>> {
let mut args = Cow::Borrowed(args);
// 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.to_mut()[argno].location = loc;
argno += 1;
}
// Assign argument to a location, change type to the requested one and move on to the
// next one.
ArgAction::AssignAndChangeType(loc, ty) => {
let arg = &mut args.to_mut()[argno];
arg.location = loc;
arg.value_type = ty;
argno += 1;
}
// Split this argument into two smaller ones. Then revisit both.
ArgAction::Convert(conv) => {
debug_assert!(
!arg.legalized_to_pointer,
"No more conversions allowed after conversion to pointer"
);
let value_type = conv.apply(arg.value_type);
args.to_mut()[argno].value_type = value_type;
if conv.is_pointer() {
args.to_mut()[argno].legalized_to_pointer = true;
} else if conv.is_split() {
let new_arg = AbiParam { value_type, ..arg };
args.to_mut().insert(argno + 1, new_arg);
}
}
}
}
match args {
Cow::Borrowed(_) => None,
Cow::Owned(a) => Some(a),
}
}
/// 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 block.
/// - 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: &AbiParam) -> ValueConversion {
let have_bits = have.bits();
let arg_bits = arg.value_type.bits();
if arg.legalized_to_pointer {
return ValueConversion::Pointer(arg.value_type);
}
match have_bits.cmp(&arg_bits) {
// We have fewer bits than the ABI argument.
Ordering::Less => {
debug_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.
debug_assert!(arg.value_type.is_int());
debug_assert!(have.is_vector(), "expected vector type, got {}", have);
ValueConversion::VectorSplit
}
// We have more bits than the argument.
Ordering::Greater => {
if have.is_vector() {
ValueConversion::VectorSplit
} else 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
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::types;
use crate::ir::AbiParam;
#[test]
fn legalize() {
let mut arg = AbiParam::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
);
// Value is passed by reference
arg.legalized_to_pointer = true;
assert_eq!(
legalize_abi_value(types::F64, &arg),
ValueConversion::Pointer(types::I32)
);
}
}

View File

@@ -15,9 +15,7 @@
//! `CodeSink::put*` methods, so the performance impact of the virtual callbacks is less severe.
use super::{Addend, CodeInfo, CodeOffset, CodeSink, Reloc};
use crate::binemit::stack_map::StackMap;
use crate::ir::entities::Value;
use crate::ir::{ConstantOffset, ExternalName, Function, JumpTable, Opcode, SourceLoc, TrapCode};
use crate::isa::TargetIsa;
use crate::ir::{ExternalName, Opcode, SourceLoc, TrapCode};
use core::ptr::write_unaligned;
/// A `CodeSink` that writes binary machine code directly into memory.
@@ -38,7 +36,6 @@ pub struct MemoryCodeSink<'a> {
offset: isize,
relocs: &'a mut dyn RelocSink,
traps: &'a mut dyn TrapSink,
stack_maps: &'a mut dyn StackMapSink,
/// Information about the generated code and read-only data.
pub info: CodeInfo,
}
@@ -54,7 +51,6 @@ impl<'a> MemoryCodeSink<'a> {
data: *mut u8,
relocs: &'a mut dyn RelocSink,
traps: &'a mut dyn TrapSink,
stack_maps: &'a mut dyn StackMapSink,
) -> Self {
Self {
data,
@@ -67,7 +63,6 @@ impl<'a> MemoryCodeSink<'a> {
},
relocs,
traps,
stack_maps,
}
}
}
@@ -84,12 +79,6 @@ pub trait RelocSink {
_: Addend,
);
/// Add a relocation referencing a constant.
fn reloc_constant(&mut self, _: CodeOffset, _: Reloc, _: ConstantOffset);
/// Add a relocation referencing a jump table.
fn reloc_jt(&mut self, _: CodeOffset, _: Reloc, _: JumpTable);
/// Track a call site whose return address is the given CodeOffset, for the given opcode. Does
/// nothing in general, only useful for certain embedders (SpiderMonkey).
fn add_call_site(&mut self, _: Opcode, _: CodeOffset, _: SourceLoc) {}
@@ -146,16 +135,6 @@ impl<'a> CodeSink for MemoryCodeSink<'a> {
self.relocs.reloc_external(ofs, srcloc, rel, name, addend);
}
fn reloc_constant(&mut self, rel: Reloc, constant_offset: ConstantOffset) {
let ofs = self.offset();
self.relocs.reloc_constant(ofs, rel, constant_offset);
}
fn reloc_jt(&mut self, rel: Reloc, jt: JumpTable) {
let ofs = self.offset();
self.relocs.reloc_jt(ofs, rel, jt);
}
fn trap(&mut self, code: TrapCode, srcloc: SourceLoc) {
let ofs = self.offset();
self.traps.trap(ofs, srcloc, code);
@@ -174,12 +153,6 @@ impl<'a> CodeSink for MemoryCodeSink<'a> {
self.info.total_size = self.offset();
}
fn add_stack_map(&mut self, val_list: &[Value], func: &Function, isa: &dyn TargetIsa) {
let ofs = self.offset();
let stack_map = StackMap::from_values(&val_list, func, isa);
self.stack_maps.add_stack_map(ofs, stack_map);
}
fn add_call_site(&mut self, opcode: Opcode, loc: SourceLoc) {
debug_assert!(
opcode.is_call(),
@@ -205,8 +178,6 @@ impl RelocSink for NullRelocSink {
_: Addend,
) {
}
fn reloc_constant(&mut self, _: CodeOffset, _: Reloc, _: ConstantOffset) {}
fn reloc_jt(&mut self, _: CodeOffset, _: Reloc, _: JumpTable) {}
}
/// A `TrapSink` implementation that does nothing, which is convenient when

View File

@@ -4,23 +4,14 @@
//! binary machine code.
mod memorysink;
mod relaxation;
mod shrink;
mod stack_map;
pub use self::memorysink::{
MemoryCodeSink, NullRelocSink, NullStackMapSink, NullTrapSink, RelocSink, StackMapSink,
TrapSink,
};
pub use self::relaxation::relax_branches;
pub use self::shrink::shrink_instructions;
pub use self::stack_map::StackMap;
use crate::ir::entities::Value;
use crate::ir::{
ConstantOffset, ExternalName, Function, Inst, JumpTable, Opcode, SourceLoc, TrapCode,
};
use crate::isa::TargetIsa;
pub use crate::regalloc::RegDiversions;
use crate::ir::{ExternalName, Opcode, SourceLoc, TrapCode};
use core::fmt;
#[cfg(feature = "enable-serde")]
use serde::{Deserialize, Serialize};
@@ -44,8 +35,6 @@ pub enum Reloc {
Abs8,
/// x86 PC-relative 4-byte
X86PCRel4,
/// x86 PC-relative 4-byte offset to trailing rodata
X86PCRelRodata4,
/// x86 call to PC-relative 4-byte
X86CallPCRel4,
/// x86 call to PLT-relative 4-byte
@@ -58,8 +47,6 @@ pub enum Reloc {
/// value is sign-extended, multiplied by 4, and added to the PC of
/// the call instruction to form the destination address.
Arm64Call,
/// RISC-V call target
RiscvCall,
/// s390x PC-relative 4-byte offset
S390xPCRel32Dbl,
@@ -89,11 +76,10 @@ impl fmt::Display for Reloc {
Self::Abs8 => write!(f, "Abs8"),
Self::S390xPCRel32Dbl => write!(f, "PCRel32Dbl"),
Self::X86PCRel4 => write!(f, "PCRel4"),
Self::X86PCRelRodata4 => write!(f, "PCRelRodata4"),
Self::X86CallPCRel4 => write!(f, "CallPCRel4"),
Self::X86CallPLTRel4 => write!(f, "CallPLTRel4"),
Self::X86GOTPCRel4 => write!(f, "GOTPCRel4"),
Self::Arm32Call | Self::Arm64Call | Self::RiscvCall => write!(f, "Call"),
Self::Arm32Call | Self::Arm64Call => write!(f, "Call"),
Self::ElfX86_64TlsGd => write!(f, "ElfX86_64TlsGd"),
Self::MachOX86_64Tlv => write!(f, "MachOX86_64Tlv"),
@@ -158,12 +144,6 @@ pub trait CodeSink {
/// Add a relocation referencing an external symbol plus the addend at the current offset.
fn reloc_external(&mut self, _: SourceLoc, _: Reloc, _: &ExternalName, _: Addend);
/// Add a relocation referencing a constant.
fn reloc_constant(&mut self, _: Reloc, _: ConstantOffset);
/// Add a relocation referencing a jump table.
fn reloc_jt(&mut self, _: Reloc, _: JumpTable);
/// Add trap information for the current offset.
fn trap(&mut self, _: TrapCode, _: SourceLoc);
@@ -176,62 +156,8 @@ pub trait CodeSink {
/// Read-only data output is complete, we're done.
fn end_codegen(&mut self);
/// Add a stack map at the current code offset.
fn add_stack_map(&mut self, _: &[Value], _: &Function, _: &dyn TargetIsa);
/// Add a call site for a call with the given opcode, returning at the current offset.
fn add_call_site(&mut self, _: Opcode, _: SourceLoc) {
// Default implementation doesn't need to do anything.
}
}
/// Report a bad encoding error.
#[cold]
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, isa: &dyn TargetIsa)
where
CS: CodeSink,
EI: Fn(&Function, Inst, &mut RegDiversions, &mut CS, &dyn TargetIsa),
{
let mut divert = RegDiversions::new();
for block in func.layout.blocks() {
divert.at_block(&func.entry_diversions, block);
debug_assert_eq!(func.offsets[block], sink.offset());
for inst in func.layout.block_insts(block) {
emit_inst(func, inst, &mut divert, sink, isa);
}
}
sink.begin_jumptables();
// Output jump tables.
for (jt, jt_data) in func.jump_tables.iter() {
let jt_offset = func.jt_offsets[jt];
for block in jt_data.iter() {
let rel_offset: i32 = func.offsets[*block] as i32 - jt_offset as i32;
sink.put4(rel_offset as u32)
}
}
sink.begin_rodata();
// Output constants.
for (_, constant_data) in func.dfg.constants.iter() {
for byte in constant_data.iter() {
sink.put1(*byte)
}
}
sink.end_codegen();
}

View File

@@ -1,396 +0,0 @@
//! Branch relaxation and offset computation.
//!
//! # block header offsets
//!
//! Before we can generate binary machine code for branch instructions, we need to know the final
//! offsets of all the block 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, x86 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:
//!
//! ```clif
//! brz v1, block17
//! ```
//!
//! can be transformed into:
//!
//! ```clif
//! brnz v1, block23
//! jump block17
//! block23:
//! ```
use crate::binemit::{CodeInfo, CodeOffset};
use crate::cursor::{Cursor, FuncCursor};
use crate::dominator_tree::DominatorTree;
use crate::flowgraph::ControlFlowGraph;
use crate::ir::{Block, Function, Inst, InstructionData, Opcode, Value, ValueList};
use crate::isa::{EncInfo, TargetIsa};
use crate::iterators::IteratorExtras;
use crate::regalloc::RegDiversions;
use crate::timing;
use crate::CodegenResult;
use core::convert::TryFrom;
/// Relax branches and compute the final layout of block headers in `func`.
///
/// Fill in the `func.offsets` table so the function is ready for binary emission.
pub fn relax_branches(
func: &mut Function,
_cfg: &mut ControlFlowGraph,
_domtree: &mut DominatorTree,
isa: &dyn TargetIsa,
) -> CodegenResult<CodeInfo> {
let _tt = timing::relax_branches();
let encinfo = isa.encoding_info();
// Clear all offsets so we can recognize blocks that haven't been visited yet.
func.offsets.clear();
func.offsets.resize(func.dfg.num_blocks());
// Start by removing redundant jumps.
fold_redundant_jumps(func, _cfg, _domtree);
// Convert jumps to fallthrough instructions where possible.
fallthroughs(func);
let mut offset = 0;
let mut divert = RegDiversions::new();
// First, compute initial offsets for every block.
{
let mut cur = FuncCursor::new(func);
while let Some(block) = cur.next_block() {
divert.at_block(&cur.func.entry_diversions, block);
cur.func.offsets[block] = offset;
while let Some(inst) = cur.next_inst() {
divert.apply(&cur.func.dfg[inst]);
let enc = cur.func.encodings[inst];
offset += encinfo.byte_size(enc, inst, &divert, &cur.func);
}
}
}
// Then, run the relaxation algorithm until it converges.
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(block) = cur.next_block() {
divert.at_block(&cur.func.entry_diversions, block);
// Record the offset for `block` and make sure we iterate until offsets are stable.
if cur.func.offsets[block] != offset {
cur.func.offsets[block] = offset;
go_again = true;
}
while let Some(inst) = cur.next_inst() {
divert.apply(&cur.func.dfg[inst]);
let enc = cur.func.encodings[inst];
// See if this is a branch has a range and a destination, and if the target is in
// 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) {
offset +=
relax_branch(&mut cur, &divert, offset, dest_offset, &encinfo, isa);
continue;
}
}
}
offset += encinfo.byte_size(enc, inst, &divert, &cur.func);
}
}
}
let code_size = offset;
let jumptables = offset;
for (jt, jt_data) in func.jump_tables.iter() {
func.jt_offsets[jt] = offset;
// TODO: this should be computed based on the min size needed to hold the furthest branch.
offset += jt_data.len() as u32 * 4;
}
let jumptables_size = offset - jumptables;
let rodata = offset;
for constant in func.dfg.constants.entries_mut() {
constant.set_offset(offset);
offset +=
u32::try_from(constant.len()).expect("Constants must have a length that fits in a u32")
}
let rodata_size = offset - rodata;
Ok(CodeInfo {
code_size,
jumptables_size,
rodata_size,
total_size: offset,
})
}
/// Folds an instruction if it is a redundant jump.
/// Returns whether folding was performed (which invalidates the CFG).
fn try_fold_redundant_jump(
func: &mut Function,
cfg: &mut ControlFlowGraph,
block: Block,
first_inst: Inst,
) -> bool {
let first_dest = match func.dfg[first_inst].branch_destination() {
Some(block) => block, // The instruction was a single-target branch.
None => {
return false; // The instruction was either multi-target or not a branch.
}
};
// For the moment, only attempt to fold a branch to a block that is parameterless.
// These blocks are mainly produced by critical edge splitting.
//
// TODO: Allow folding blocks that define SSA values and function as phi nodes.
if func.dfg.num_block_params(first_dest) != 0 {
return false;
}
// Look at the first instruction of the first branch's destination.
// If it is an unconditional branch, maybe the second jump can be bypassed.
let second_inst = func.layout.first_inst(first_dest).expect("Instructions");
if func.dfg[second_inst].opcode() != Opcode::Jump {
return false;
}
// Now we need to fix up first_inst's block parameters to match second_inst's,
// without changing the branch-specific arguments.
//
// The intermediary block is allowed to reference any SSA value that dominates it,
// but that SSA value may not necessarily also dominate the instruction that's
// being patched.
// Get the arguments and parameters passed by the first branch.
let num_fixed = func.dfg[first_inst]
.opcode()
.constraints()
.num_fixed_value_arguments();
let (first_args, first_params) = func.dfg[first_inst]
.arguments(&func.dfg.value_lists)
.split_at(num_fixed);
// Get the parameters passed by the second jump.
let num_fixed = func.dfg[second_inst]
.opcode()
.constraints()
.num_fixed_value_arguments();
let (_, second_params) = func.dfg[second_inst]
.arguments(&func.dfg.value_lists)
.split_at(num_fixed);
let mut second_params = second_params.to_vec(); // Clone for rewriting below.
// For each parameter passed by the second jump, if any of those parameters
// was a block parameter, rewrite it to refer to the value that the first jump
// passed in its parameters. Otherwise, make sure it dominates first_inst.
//
// For example: if we `block0: jump block1(v1)` to `block1(v2): jump block2(v2)`,
// we want to rewrite the original jump to `jump block2(v1)`.
let block_params: &[Value] = func.dfg.block_params(first_dest);
debug_assert!(block_params.len() == first_params.len());
for value in second_params.iter_mut() {
if let Some((n, _)) = block_params.iter().enumerate().find(|(_, &p)| p == *value) {
// This value was the Nth parameter passed to the second_inst's block.
// Rewrite it as the Nth parameter passed by first_inst.
*value = first_params[n];
}
}
// Build a value list of first_args (unchanged) followed by second_params (rewritten).
let arguments_vec: alloc::vec::Vec<_> = first_args
.iter()
.chain(second_params.iter())
.copied()
.collect();
let value_list = ValueList::from_slice(&arguments_vec, &mut func.dfg.value_lists);
func.dfg[first_inst].take_value_list(); // Drop the current list.
func.dfg[first_inst].put_value_list(value_list); // Put the new list.
// Bypass the second jump.
// This can disconnect the Block containing `second_inst`, to be cleaned up later.
let second_dest = func.dfg[second_inst].branch_destination().expect("Dest");
func.change_branch_destination(first_inst, second_dest);
cfg.recompute_block(func, block);
// The previously-intermediary Block may now be unreachable. Update CFG.
if cfg.pred_iter(first_dest).count() == 0 {
// Remove all instructions from that block.
while let Some(inst) = func.layout.first_inst(first_dest) {
func.layout.remove_inst(inst);
}
// Remove the block...
cfg.recompute_block(func, first_dest); // ...from predecessor lists.
func.layout.remove_block(first_dest); // ...from the layout.
}
true
}
/// Redirects `jump` instructions that point to other `jump` instructions to the final destination.
/// This transformation may orphan some blocks.
fn fold_redundant_jumps(
func: &mut Function,
cfg: &mut ControlFlowGraph,
domtree: &mut DominatorTree,
) {
let mut folded = false;
// Postorder iteration guarantees that a chain of jumps is visited from
// the end of the chain to the start of the chain.
for &block in domtree.cfg_postorder() {
// Only proceed if the first terminator instruction is a single-target branch.
let first_inst = func
.layout
.last_inst(block)
.expect("Block has no terminator");
folded |= try_fold_redundant_jump(func, cfg, block, first_inst);
// Also try the previous instruction.
if let Some(prev_inst) = func.layout.prev_inst(first_inst) {
folded |= try_fold_redundant_jump(func, cfg, block, prev_inst);
}
}
// Folding jumps invalidates the dominator tree.
if folded {
domtree.compute(func, cfg);
}
}
/// Convert `jump` instructions to `fallthrough` instructions where possible and verify that any
/// existing `fallthrough` instructions are correct.
fn fallthroughs(func: &mut Function) {
for (block, succ) in func.layout.blocks().adjacent_pairs() {
let term = func
.layout
.last_inst(block)
.expect("block 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.
debug_assert_eq!(
destination, succ,
"Illegal fallthrough from {} to {}, but {}'s successor is {}",
block, destination, block, succ
)
}
Opcode::Jump => {
// If this is a jump to the successor block, change it to a fall-through.
if destination == succ {
*opcode = Opcode::Fallthrough;
func.encodings[term] = Default::default();
}
}
_ => {}
}
}
}
}
/// Relax the branch instruction at `cur` so it can cover the range `offset - dest_offset`.
///
/// Return the size of the replacement instructions up to and including the location where `cur` is
/// left.
fn relax_branch(
cur: &mut FuncCursor,
divert: &RegDiversions,
offset: CodeOffset,
dest_offset: CodeOffset,
encinfo: &EncInfo,
isa: &dyn TargetIsa,
) -> CodeOffset {
let inst = cur.current_inst().unwrap();
log::trace!(
"Relaxing [{}] {} for {:#x}-{:#x} range",
encinfo.display(cur.func.encodings[inst]),
cur.func.dfg.display_inst(inst, isa),
offset,
dest_offset
);
// Pick the smallest encoding that can handle the branch range.
let dfg = &cur.func.dfg;
let ctrl_type = dfg.ctrl_typevar(inst);
if let Some(enc) = isa
.legal_encodings(cur.func, &dfg[inst], ctrl_type)
.filter(|&enc| {
let range = encinfo.branch_range(enc).expect("Branch with no range");
if !range.contains(offset, dest_offset) {
log::trace!(" trying [{}]: out of range", encinfo.display(enc));
false
} else if encinfo.operand_constraints(enc)
!= encinfo.operand_constraints(cur.func.encodings[inst])
{
// Conservatively give up if the encoding has different constraints
// than the original, so that we don't risk picking a new encoding
// which the existing operands don't satisfy. We can't check for
// validity directly because we don't have a RegDiversions active so
// we don't know which registers are actually in use.
log::trace!(" trying [{}]: constraints differ", encinfo.display(enc));
false
} else {
log::trace!(" trying [{}]: OK", encinfo.display(enc));
true
}
})
.min_by_key(|&enc| encinfo.byte_size(enc, inst, &divert, &cur.func))
{
debug_assert!(enc != cur.func.encodings[inst]);
cur.func.encodings[inst] = enc;
return encinfo.byte_size(enc, inst, &divert, &cur.func);
}
// Note: On some RISC ISAs, conditional branches have shorter range than unconditional
// branches, so one way of extending the range of a conditional branch is to invert its
// condition and make it branch over an unconditional jump which has the larger range.
//
// Splitting the block is problematic this late because there may be register diversions in
// effect across the conditional branch, and they can't survive the control flow edge to a new
// block. We have two options for handling that:
//
// 1. Set a flag on the new block that indicates it wants the preserve the register diversions of
// its layout predecessor, or
// 2. Use an encoding macro for the branch-over-jump pattern so we don't need to split the block.
//
// It seems that 1. would allow us to share code among RISC ISAs that need this.
//
// We can't allow register diversions to survive from the layout predecessor because the layout
// predecessor could contain kill points for some values that are live in this block, and
// diversions are not automatically cancelled when the live range of a value ends.
// This assumes solution 2. above:
panic!("No branch in range for {:#x}-{:#x}", offset, dest_offset);
}

View File

@@ -1,72 +0,0 @@
//! Instruction shrinking.
//!
//! Sometimes there are multiple valid encodings for a given instruction. Cranelift often initially
//! chooses the largest one, because this typically provides the register allocator the most
//! flexibility. However, once register allocation is done, this is no longer important, and we
//! can switch to smaller encodings when possible.
use crate::ir::instructions::InstructionData;
use crate::ir::Function;
use crate::isa::TargetIsa;
use crate::regalloc::RegDiversions;
use crate::timing;
/// Pick the smallest valid encodings for instructions.
pub fn shrink_instructions(func: &mut Function, isa: &dyn TargetIsa) {
let _tt = timing::shrink_instructions();
let encinfo = isa.encoding_info();
let mut divert = RegDiversions::new();
for block in func.layout.blocks() {
// Load diversions from predecessors.
divert.at_block(&func.entry_diversions, block);
for inst in func.layout.block_insts(block) {
let enc = func.encodings[inst];
if enc.is_legal() {
// regmove/regfill/regspill are special instructions with register immediates
// that represented as normal operands, so the normal predicates below don't
// handle them correctly.
//
// Also, they need to be presented to the `RegDiversions` to update the
// location tracking.
//
// TODO: Eventually, we want the register allocator to avoid leaving these special
// instructions behind, but for now, just temporarily avoid trying to shrink them.
let inst_data = &func.dfg[inst];
match inst_data {
InstructionData::RegMove { .. }
| InstructionData::RegFill { .. }
| InstructionData::RegSpill { .. } => {
divert.apply(inst_data);
continue;
}
_ => (),
}
let ctrl_type = func.dfg.ctrl_typevar(inst);
// Pick the last encoding with constraints that are satisfied.
let best_enc = isa
.legal_encodings(func, &func.dfg[inst], ctrl_type)
.filter(|e| encinfo.constraints[e.recipe()].satisfied(inst, &divert, &func))
.min_by_key(|e| encinfo.byte_size(*e, inst, &divert, &func))
.unwrap();
if best_enc != enc {
func.encodings[inst] = best_enc;
log::trace!(
"Shrunk [{}] to [{}] in {}, reducing the size from {} to {}",
encinfo.display(enc),
encinfo.display(best_enc),
func.dfg.display_inst(inst, isa),
encinfo.byte_size(enc, inst, &divert, &func),
encinfo.byte_size(best_enc, inst, &divert, &func)
);
}
}
}
}
}

View File

@@ -1,6 +1,4 @@
use crate::bitset::BitSet;
use crate::ir;
use crate::isa::TargetIsa;
use alloc::vec::Vec;
type Num = u32;
@@ -76,57 +74,6 @@ pub struct StackMap {
}
impl StackMap {
/// Create a `StackMap` based on where references are located on a
/// function's stack.
pub fn from_values(
args: &[ir::entities::Value],
func: &ir::Function,
isa: &dyn TargetIsa,
) -> Self {
let loc = &func.locations;
let mut live_ref_in_stack_slot = crate::HashSet::new();
// References can be in registers, and live registers values are pushed onto the stack before calls and traps.
// TODO: Implement register maps. If a register containing a reference is spilled and reused after a safepoint,
// it could contain a stale reference value if the garbage collector relocated the value.
for val in args {
if let Some(value_loc) = loc.get(*val) {
match *value_loc {
ir::ValueLoc::Stack(stack_slot) => {
live_ref_in_stack_slot.insert(stack_slot);
}
_ => {}
}
}
}
let stack = &func.stack_slots;
let info = func.stack_slots.layout_info.unwrap();
// Refer to the doc comment for `StackMap` above to understand the
// bitmap representation used here.
let map_size = (info.frame_size + info.inbound_args_size) as usize;
let word_size = isa.pointer_bytes() as usize;
let num_words = map_size / word_size;
let mut vec = alloc::vec::Vec::with_capacity(num_words);
vec.resize(num_words, false);
for (ss, ssd) in stack.iter() {
if !live_ref_in_stack_slot.contains(&ss)
|| ssd.kind == ir::stackslot::StackSlotKind::OutgoingArg
{
continue;
}
debug_assert!(ssd.size as usize == word_size);
let bytes_from_bottom = info.frame_size as i32 + ssd.offset.unwrap();
let words_from_bottom = (bytes_from_bottom as usize) / word_size;
vec[words_from_bottom] = true;
}
Self::from_slice(&vec)
}
/// Create a vec of Bitsets from a slice of bools.
pub fn from_slice(vec: &[bool]) -> Self {
let len = vec.len();

View File

@@ -51,11 +51,11 @@ impl<'a> CFGPrinter<'a> {
for block in &self.func.layout {
write!(w, " {} [shape=record, label=\"{{", block)?;
crate::write::write_block_header(w, self.func, None, block, 4)?;
crate::write::write_block_header(w, self.func, block, 4)?;
// Add all outgoing branch instructions to the label.
for inst in self.func.layout.block_likely_branches(block) {
write!(w, " | <{}>", inst)?;
PlainWriter.write_instruction(w, self.func, &aliases, None, inst, 0)?;
PlainWriter.write_instruction(w, self.func, &aliases, inst, 0)?;
}
writeln!(w, "}}\"]")?
}

View File

@@ -9,24 +9,17 @@
//! contexts concurrently. Typically, you would have one context per compilation thread and only a
//! single ISA instance.
use crate::binemit::{
relax_branches, shrink_instructions, CodeInfo, MemoryCodeSink, RelocSink, StackMapSink,
TrapSink,
};
use crate::binemit::{CodeInfo, MemoryCodeSink, RelocSink, StackMapSink, TrapSink};
use crate::dce::do_dce;
use crate::dominator_tree::DominatorTree;
use crate::flowgraph::ControlFlowGraph;
use crate::ir::Function;
use crate::isa::TargetIsa;
use crate::legalize_function;
use crate::legalizer::simple_legalize;
use crate::licm::do_licm;
use crate::loop_analysis::LoopAnalysis;
use crate::machinst::{MachCompileResult, MachStackMap};
use crate::nan_canonicalization::do_nan_canonicalization;
use crate::postopt::do_postopt;
use crate::redundant_reload_remover::RedundantReloadRemover;
use crate::regalloc;
use crate::remove_constant_phis::do_remove_constant_phis;
use crate::result::CodegenResult;
use crate::settings::{FlagsOrIsa, OptLevel};
@@ -34,8 +27,7 @@ use crate::simple_gvn::do_simple_gvn;
use crate::simple_preopt::do_preopt;
use crate::timing;
use crate::unreachable_code::eliminate_unreachable_code;
use crate::value_label::{build_value_labels_ranges, ComparableSourceLoc, ValueLabelsRanges};
use crate::verifier::{verify_context, verify_locations, VerifierErrors, VerifierResult};
use crate::verifier::{verify_context, VerifierErrors, VerifierResult};
#[cfg(feature = "souper-harvest")]
use alloc::string::String;
use alloc::vec::Vec;
@@ -54,15 +46,9 @@ pub struct Context {
/// Dominator tree for `func`.
pub domtree: DominatorTree,
/// Register allocation context.
pub regalloc: regalloc::Context,
/// Loop analysis of `func`.
pub loop_analysis: LoopAnalysis,
/// Redundant-reload remover context.
pub redundant_reload_remover: RedundantReloadRemover,
/// Result of MachBackend compilation, if computed.
pub mach_compile_result: Option<MachCompileResult>,
@@ -88,9 +74,7 @@ impl Context {
func,
cfg: ControlFlowGraph::new(),
domtree: DominatorTree::new(),
regalloc: regalloc::Context::new(),
loop_analysis: LoopAnalysis::new(),
redundant_reload_remover: RedundantReloadRemover::new(),
mach_compile_result: None,
want_disasm: false,
}
@@ -101,9 +85,7 @@ impl Context {
self.func.clear();
self.cfg.clear();
self.domtree.clear();
self.regalloc.clear();
self.loop_analysis.clear();
self.redundant_reload_remover.clear();
self.mach_compile_result = None;
self.want_disasm = false;
}
@@ -137,13 +119,7 @@ impl Context {
let old_len = mem.len();
mem.resize(old_len + info.total_size as usize, 0);
let new_info = unsafe {
self.emit_to_memory(
isa,
mem.as_mut_ptr().add(old_len),
relocs,
traps,
stack_maps,
)
self.emit_to_memory(mem.as_mut_ptr().add(old_len), relocs, traps, stack_maps)
};
debug_assert!(new_info == info);
Ok(info)
@@ -164,7 +140,7 @@ impl Context {
log::debug!(
"Compiling (opt level {:?}):\n{}",
opt_level,
self.func.display(isa)
self.func.display()
);
self.compute_cfg();
@@ -177,7 +153,6 @@ impl Context {
self.legalize(isa)?;
if opt_level != OptLevel::None {
self.postopt(isa)?;
self.compute_domtree();
self.compute_loop_analysis();
self.licm(isa)?;
@@ -192,25 +167,12 @@ impl Context {
self.remove_constant_phis(isa)?;
if let Some(backend) = isa.get_mach_backend() {
let result = backend.compile_function(&self.func, self.want_disasm)?;
let info = result.code_info();
self.mach_compile_result = Some(result);
Ok(info)
} else {
self.regalloc(isa)?;
self.prologue_epilogue(isa)?;
if opt_level == OptLevel::Speed || opt_level == OptLevel::SpeedAndSize {
self.redundant_reload_remover(isa)?;
}
if opt_level == OptLevel::SpeedAndSize {
self.shrink_instructions(isa)?;
}
let result = self.relax_branches(isa);
log::trace!("Compiled:\n{}", self.func.display(isa));
result
}
// FIXME: make this non optional
let backend = isa.get_mach_backend().expect("only mach backends nowadays");
let result = backend.compile_function(&self.func, self.want_disasm)?;
let info = result.code_info();
self.mach_compile_result = Some(result);
Ok(info)
}
/// Emit machine code directly into raw memory.
@@ -228,33 +190,31 @@ impl Context {
/// Returns information about the emitted code and data.
pub unsafe fn emit_to_memory(
&self,
isa: &dyn TargetIsa,
mem: *mut u8,
relocs: &mut dyn RelocSink,
traps: &mut dyn TrapSink,
stack_maps: &mut dyn StackMapSink,
) -> CodeInfo {
let _tt = timing::binemit();
let mut sink = MemoryCodeSink::new(mem, relocs, traps, stack_maps);
if let Some(ref result) = &self.mach_compile_result {
result.buffer.emit(&mut sink);
let info = sink.info;
// New backends do not emit StackMaps through the `CodeSink` because its interface
// requires `Value`s; instead, the `StackMap` objects are directly accessible via
// `result.buffer.stack_maps()`.
for &MachStackMap {
offset_end,
ref stack_map,
..
} in result.buffer.stack_maps()
{
stack_maps.add_stack_map(offset_end, stack_map.clone());
}
info
} else {
isa.emit_function_to_memory(&self.func, &mut sink);
sink.info
let mut sink = MemoryCodeSink::new(mem, relocs, traps);
let result = self
.mach_compile_result
.as_ref()
.expect("only using mach backend now");
result.buffer.emit(&mut sink);
let info = sink.info;
// New backends do not emit StackMaps through the `CodeSink` because its interface
// requires `Value`s; instead, the `StackMap` objects are directly accessible via
// `result.buffer.stack_maps()`.
for &MachStackMap {
offset_end,
ref stack_map,
..
} in result.buffer.stack_maps()
{
stack_maps.add_stack_map(offset_end, stack_map.clone());
}
info
}
/// If available, return information about the code layout in the
@@ -314,26 +274,6 @@ impl Context {
Ok(())
}
/// Run the locations verifier on the function.
pub fn verify_locations(&self, isa: &dyn TargetIsa) -> VerifierResult<()> {
let mut errors = VerifierErrors::default();
let _ = verify_locations(isa, &self.func, &self.cfg, None, &mut errors);
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
/// Run the locations verifier only if the `enable_verifier` setting is true.
pub fn verify_locations_if(&self, isa: &dyn TargetIsa) -> CodegenResult<()> {
if isa.flags().enable_verifier() {
self.verify_locations(isa)?;
}
Ok(())
}
/// Perform dead-code elimination on the function.
pub fn dce<'a, FOI: Into<FlagsOrIsa<'a>>>(&mut self, fisa: FOI) -> CodegenResult<()> {
do_dce(&mut self.func, &mut self.domtree);
@@ -370,22 +310,10 @@ impl Context {
// TODO: Avoid doing this when legalization doesn't actually mutate the CFG.
self.domtree.clear();
self.loop_analysis.clear();
if isa.get_mach_backend().is_some() {
// Run some specific legalizations only.
simple_legalize(&mut self.func, &mut self.cfg, isa);
self.verify_if(isa)
} else {
legalize_function(&mut self.func, &mut self.cfg, isa);
log::trace!("Legalized:\n{}", self.func.display(isa));
self.verify_if(isa)
}
}
/// Perform post-legalization rewrites on the function.
pub fn postopt(&mut self, isa: &dyn TargetIsa) -> CodegenResult<()> {
do_postopt(&mut self.func, isa);
self.verify_if(isa)?;
Ok(())
// Run some specific legalizations only.
simple_legalize(&mut self.func, &mut self.cfg, isa);
self.verify_if(isa)
}
/// Compute the control flow graph.
@@ -419,7 +347,6 @@ impl Context {
/// Perform LICM on the function.
pub fn licm(&mut self, isa: &dyn TargetIsa) -> CodegenResult<()> {
do_licm(
isa,
&mut self.func,
&mut self.cfg,
&mut self.domtree,
@@ -437,58 +364,6 @@ impl Context {
self.verify_if(fisa)
}
/// Run the register allocator.
pub fn regalloc(&mut self, isa: &dyn TargetIsa) -> CodegenResult<()> {
self.regalloc
.run(isa, &mut self.func, &mut self.cfg, &mut self.domtree)
}
/// Insert prologue and epilogues after computing the stack frame layout.
pub fn prologue_epilogue(&mut self, isa: &dyn TargetIsa) -> CodegenResult<()> {
isa.prologue_epilogue(&mut self.func)?;
self.verify_if(isa)?;
self.verify_locations_if(isa)?;
Ok(())
}
/// Do redundant-reload removal after allocation of both registers and stack slots.
pub fn redundant_reload_remover(&mut self, isa: &dyn TargetIsa) -> CodegenResult<()> {
self.redundant_reload_remover
.run(isa, &mut self.func, &self.cfg);
self.verify_if(isa)?;
Ok(())
}
/// Run the instruction shrinking pass.
pub fn shrink_instructions(&mut self, isa: &dyn TargetIsa) -> CodegenResult<()> {
shrink_instructions(&mut self.func, isa);
self.verify_if(isa)?;
self.verify_locations_if(isa)?;
Ok(())
}
/// Run the branch relaxation pass and return information about the function's code and
/// read-only data.
pub fn relax_branches(&mut self, isa: &dyn TargetIsa) -> CodegenResult<CodeInfo> {
let info = relax_branches(&mut self.func, &mut self.cfg, &mut self.domtree, isa)?;
self.verify_if(isa)?;
self.verify_locations_if(isa)?;
Ok(info)
}
/// Builds ranges and location for specified value labels.
pub fn build_value_labels_ranges(
&self,
isa: &dyn TargetIsa,
) -> CodegenResult<ValueLabelsRanges> {
Ok(build_value_labels_ranges::<ComparableSourceLoc>(
&self.func,
&self.regalloc,
self.mach_compile_result.as_ref(),
isa,
))
}
/// Harvest candidate left-hand sides for superoptimization with Souper.
#[cfg(feature = "souper-harvest")]
pub fn souper_harvest(

View File

@@ -3,7 +3,6 @@
//! This module defines cursor data types that can be used for inserting instructions.
use crate::ir;
use crate::isa::TargetIsa;
/// The possible positions of a cursor.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
@@ -634,7 +633,7 @@ impl<'c, 'f> ir::InstInserterBase<'c> for &'c mut FuncCursor<'f> {
&mut self.func.dfg
}
fn insert_built_inst(self, inst: ir::Inst, _: ir::Type) -> &'c mut ir::DataFlowGraph {
fn insert_built_inst(self, inst: ir::Inst) -> &'c mut ir::DataFlowGraph {
// TODO: Remove this assertion once #796 is fixed.
#[cfg(debug_assertions)]
{
@@ -664,152 +663,3 @@ impl<'c, 'f> ir::InstInserterBase<'c> for &'c mut FuncCursor<'f> {
&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,
srcloc: ir::SourceLoc,
built_inst: Option<ir::Inst>,
/// The referenced function.
pub func: &'f mut ir::Function,
/// The target ISA that will be used to encode instructions.
pub isa: &'f dyn TargetIsa,
}
impl<'f> EncCursor<'f> {
/// Create a new `EncCursor` pointing nowhere.
pub fn new(func: &'f mut ir::Function, isa: &'f dyn TargetIsa) -> Self {
Self {
pos: CursorPosition::Nowhere,
srcloc: Default::default(),
built_inst: None,
func,
isa,
}
}
/// Use the source location of `inst` for future instructions.
pub fn use_srcloc(&mut self, inst: ir::Inst) {
self.srcloc = self.func.srclocs[inst];
}
/// 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 srcloc(&self) -> ir::SourceLoc {
self.srcloc
}
fn set_srcloc(&mut self, srcloc: ir::SourceLoc) {
self.srcloc = srcloc;
}
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 {
// TODO: Remove this assertion once #796 is fixed.
#[cfg(debug_assertions)]
{
if let CursorPosition::At(_) = self.position() {
if let Some(curr) = self.current_inst() {
if let Some(prev) = self.layout().prev_inst(curr) {
let prev_op = self.data_flow_graph()[prev].opcode();
let inst_op = self.data_flow_graph()[inst].opcode();
if prev_op.is_branch()
&& !prev_op.is_terminator()
&& !inst_op.is_terminator()
{
panic!(
"Inserting instruction {} after {} and before {}",
self.display_inst(inst),
self.display_inst(prev),
self.display_inst(curr)
)
}
};
};
};
}
// Insert the instruction and remember the reference.
self.insert_inst(inst);
self.built_inst = Some(inst);
if !self.srcloc.is_default() {
self.func.srclocs[inst] = self.srcloc;
}
// Skip the encoding update if we're using a new (MachInst) backend; encodings come later,
// during lowering.
if self.isa.get_mach_backend().is_none() {
// Assign an encoding.
// XXX Is there a way to describe this error to the user?
#[cfg_attr(feature = "cargo-clippy", allow(clippy::match_wild_err_arm))]
match self
.isa
.encode(&self.func, &self.func.dfg[inst], ctrl_typevar)
{
Ok(e) => self.func.encodings[inst] = e,
Err(_) => panic!("can't encode {}", self.display_inst(inst)),
}
}
&mut self.func.dfg
}
}

View File

@@ -86,6 +86,7 @@ impl DataValue {
DataValue::I16(i) => dst[..2].copy_from_slice(&i.to_ne_bytes()[..]),
DataValue::I32(i) => dst[..4].copy_from_slice(&i.to_ne_bytes()[..]),
DataValue::I64(i) => dst[..8].copy_from_slice(&i.to_ne_bytes()[..]),
DataValue::I128(i) => dst[..16].copy_from_slice(&i.to_ne_bytes()[..]),
DataValue::F32(f) => dst[..4].copy_from_slice(&f.bits().to_ne_bytes()[..]),
DataValue::F64(f) => dst[..8].copy_from_slice(&f.bits().to_ne_bytes()[..]),
DataValue::V128(v) => dst[..16].copy_from_slice(&v[..]),
@@ -104,6 +105,7 @@ impl DataValue {
types::I16 => DataValue::I16(i16::from_ne_bytes(src[..2].try_into().unwrap())),
types::I32 => DataValue::I32(i32::from_ne_bytes(src[..4].try_into().unwrap())),
types::I64 => DataValue::I64(i64::from_ne_bytes(src[..8].try_into().unwrap())),
types::I128 => DataValue::I128(i128::from_ne_bytes(src[..16].try_into().unwrap())),
types::F32 => DataValue::F32(Ieee32::with_bits(u32::from_ne_bytes(
src[..4].try_into().unwrap(),
))),

View File

@@ -78,10 +78,3 @@ pub fn is_constant_64bit(func: &Function, inst: Inst) -> Option<u64> {
_ => None,
}
}
/// Is the given instruction a safepoint (i.e., potentially causes a GC, depending on the
/// embedding, and so requires reftyped values to be enumerated with a stack map)?
pub fn is_safepoint(func: &Function, inst: Inst) -> bool {
let op = func.dfg[inst].opcode();
op.is_resumable_trap() || op.is_call()
}

View File

@@ -7,7 +7,6 @@ use crate::ir;
use crate::ir::types;
use crate::ir::{DataFlowGraph, InstructionData};
use crate::ir::{Inst, Opcode, Type, Value};
use crate::isa;
/// Base trait for instruction builders.
///
@@ -56,7 +55,7 @@ pub trait InstInserterBase<'f>: Sized {
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;
fn insert_built_inst(self, inst: Inst) -> &'f mut DataFlowGraph;
}
use core::marker::PhantomData;
@@ -129,7 +128,7 @@ impl<'f, IIB: InstInserterBase<'f>> InstBuilderBase<'f> for InsertBuilder<'f, II
inst = dfg.make_inst(data);
dfg.make_inst_results(inst, ctrl_typevar);
}
(inst, self.inserter.insert_built_inst(inst, ctrl_typevar))
(inst, self.inserter.insert_built_inst(inst))
}
}
@@ -166,7 +165,7 @@ where
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))
(inst, self.inserter.insert_built_inst(inst))
}
}

View File

@@ -167,38 +167,6 @@ impl FromStr for ConstantData {
}
}
/// This type describes an offset in bytes within a constant pool.
pub type ConstantOffset = u32;
/// Inner type for storing data and offset together in the constant pool. The offset is optional
/// because it must be set relative to the function code size (i.e. constants are emitted after the
/// function body); because the function is not yet compiled when constants are inserted,
/// [`set_offset`](crate::ir::ConstantPool::set_offset) must be called once a constant's offset
/// from the beginning of the function is known (see
/// `relaxation` in `relaxation.rs`).
#[derive(Clone)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct ConstantPoolEntry {
data: ConstantData,
offset: Option<ConstantOffset>,
}
impl ConstantPoolEntry {
fn new(data: ConstantData) -> Self {
Self { data, offset: None }
}
/// Return the size of the constant at this entry.
pub fn len(&self) -> usize {
self.data.len()
}
/// Assign a new offset to the constant at this entry.
pub fn set_offset(&mut self, offset: ConstantOffset) {
self.offset = Some(offset)
}
}
/// Maintains the mapping between a constant handle (i.e. [`Constant`](crate::ir::Constant)) and
/// its constant data (i.e. [`ConstantData`](crate::ir::ConstantData)).
#[derive(Clone)]
@@ -206,7 +174,7 @@ impl ConstantPoolEntry {
pub struct ConstantPool {
/// This mapping maintains the insertion order as long as Constants are created with
/// sequentially increasing integers.
handles_to_values: BTreeMap<Constant, ConstantPoolEntry>,
handles_to_values: BTreeMap<Constant, ConstantData>,
/// This mapping is unordered (no need for lexicographic ordering) but allows us to map
/// constant data back to handles.
@@ -244,64 +212,34 @@ impl ConstantPool {
/// Retrieve the constant data given a handle.
pub fn get(&self, constant_handle: Constant) -> &ConstantData {
assert!(self.handles_to_values.contains_key(&constant_handle));
&self.handles_to_values.get(&constant_handle).unwrap().data
self.handles_to_values.get(&constant_handle).unwrap()
}
/// Link a constant handle to its value. This does not de-duplicate data but does avoid
/// replacing any existing constant values. use `set` to tie a specific `const42` to its value;
/// use `insert` to add a value and return the next available `const` entity.
pub fn set(&mut self, constant_handle: Constant, constant_value: ConstantData) {
let replaced = self.handles_to_values.insert(
constant_handle,
ConstantPoolEntry::new(constant_value.clone()),
);
let replaced = self
.handles_to_values
.insert(constant_handle, constant_value.clone());
assert!(
replaced.is_none(),
"attempted to overwrite an existing constant {:?}: {:?} => {:?}",
constant_handle,
&constant_value,
replaced.unwrap().data
replaced.unwrap()
);
self.values_to_handles
.insert(constant_value, constant_handle);
}
/// Assign an offset to a given constant, where the offset is the number of bytes from the
/// beginning of the function to the beginning of the constant data inside the pool.
pub fn set_offset(&mut self, constant_handle: Constant, constant_offset: ConstantOffset) {
assert!(
self.handles_to_values.contains_key(&constant_handle),
"A constant handle must have already been inserted into the pool; perhaps a \
constant pool was created outside of the pool?"
);
self.handles_to_values
.entry(constant_handle)
.and_modify(|e| e.offset = Some(constant_offset));
}
/// Retrieve the offset of a given constant, where the offset is the number of bytes from the
/// beginning of the function to the beginning of the constant data inside the pool.
pub fn get_offset(&self, constant_handle: Constant) -> ConstantOffset {
self.handles_to_values
.get(&constant_handle)
.expect(
"A constant handle must have a corresponding constant value; was a constant \
handle created outside of the pool?",
)
.offset
.expect(
"A constant offset has not yet been set; verify that `set_offset` has been \
called before this point",
)
}
/// Iterate over the constants in insertion order.
pub fn iter(&self) -> impl Iterator<Item = (&Constant, &ConstantData)> {
self.handles_to_values.iter().map(|(h, e)| (h, &e.data))
self.handles_to_values.iter()
}
/// Iterate over mutable entries in the constant pool in insertion order.
pub fn entries_mut(&mut self) -> impl Iterator<Item = &mut ConstantPoolEntry> {
pub fn entries_mut(&mut self) -> impl Iterator<Item = &mut ConstantData> {
self.handles_to_values.values_mut()
}
@@ -398,22 +336,6 @@ mod tests {
sut.get(a); // panics, only use constants returned by ConstantPool
}
#[test]
fn get_offset() {
let mut sut = ConstantPool::new();
let a = sut.insert(vec![1].into());
sut.set_offset(a, 42);
assert_eq!(sut.get_offset(a), 42)
}
#[test]
#[should_panic]
fn get_nonexistent_offset() {
let mut sut = ConstantPool::new();
let a = sut.insert(vec![1].into());
sut.get_offset(a); // panics, set_offset should have been called
}
#[test]
fn display_constant_data() {
assert_eq!(ConstantData::from([0].as_ref()).to_string(), "0x00");

View File

@@ -10,7 +10,6 @@ use crate::ir::{
Block, FuncRef, Inst, SigRef, Signature, SourceLoc, Type, Value, ValueLabelAssignments,
ValueList, ValueListPool,
};
use crate::isa::TargetIsa;
use crate::packed_option::ReservedValue;
use crate::write::write_operands;
use crate::HashMap;
@@ -466,12 +465,8 @@ impl DataFlowGraph {
}
/// Returns an object that displays `inst`.
pub fn display_inst<'a, I: Into<Option<&'a dyn TargetIsa>>>(
&'a self,
inst: Inst,
isa: I,
) -> DisplayInst<'a> {
DisplayInst(self, isa.into(), inst)
pub fn display_inst<'a>(&'a self, inst: Inst) -> DisplayInst<'a> {
DisplayInst(self, inst)
}
/// Get all value arguments on `inst` as a slice.
@@ -657,7 +652,7 @@ impl DataFlowGraph {
old_value,
"{} wasn't detached from {}",
old_value,
self.display_inst(inst, None)
self.display_inst(inst)
);
new_value
}
@@ -963,13 +958,12 @@ impl BlockData {
}
/// Object that can display an instruction.
pub struct DisplayInst<'a>(&'a DataFlowGraph, Option<&'a dyn TargetIsa>, Inst);
pub struct DisplayInst<'a>(&'a DataFlowGraph, Inst);
impl<'a> fmt::Display for DisplayInst<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let dfg = self.0;
let isa = self.1;
let inst = self.2;
let inst = self.1;
if let Some((first, rest)) = dfg.inst_results(inst).split_first() {
write!(f, "{}", first)?;
@@ -985,7 +979,7 @@ impl<'a> fmt::Display for DisplayInst<'a> {
} else {
write!(f, "{}.{}", dfg[inst].opcode(), typevar)?;
}
write_operands(f, dfg, isa, inst)
write_operands(f, dfg, inst)
}
}
@@ -1150,10 +1144,7 @@ mod tests {
dfg.make_inst_results(inst, types::I32);
assert_eq!(inst.to_string(), "inst0");
assert_eq!(
dfg.display_inst(inst, None).to_string(),
"v0 = iconst.i32 0"
);
assert_eq!(dfg.display_inst(inst).to_string(), "v0 = iconst.i32 0");
// Immutable reference resolution.
{
@@ -1188,7 +1179,7 @@ mod tests {
code: TrapCode::User(0),
};
let inst = dfg.make_inst(idata);
assert_eq!(dfg.display_inst(inst, None).to_string(), "trap user0");
assert_eq!(dfg.display_inst(inst).to_string(), "trap user0");
// Result slice should be empty.
assert_eq!(dfg.inst_results(inst), &[]);

View File

@@ -5,8 +5,8 @@
//!
//! This module declares the data types used to represent external functions and call signatures.
use crate::ir::{ArgumentLoc, ExternalName, SigRef, Type};
use crate::isa::{CallConv, RegInfo, RegUnit};
use crate::ir::{ExternalName, SigRef, Type};
use crate::isa::CallConv;
use crate::machinst::RelocDistance;
use alloc::vec::Vec;
use core::fmt;
@@ -50,11 +50,6 @@ impl Signature {
self.call_conv = call_conv;
}
/// 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())
}
/// Find the index of a presumed unique special-purpose parameter.
pub fn special_param_index(&self, purpose: ArgumentPurpose) -> Option<usize> {
self.params.iter().rposition(|arg| arg.purpose == purpose)
@@ -108,38 +103,29 @@ impl Signature {
}
}
/// 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: &[AbiParam], regs: Option<&RegInfo>) -> fmt::Result {
fn write_list(f: &mut fmt::Formatter, args: &[AbiParam]) -> fmt::Result {
match args.split_first() {
None => {}
Some((first, rest)) => {
write!(f, "{}", first.display(regs))?;
write!(f, "{}", first)?;
for arg in rest {
write!(f, ", {}", arg.display(regs))?;
write!(f, ", {}", arg)?;
}
}
}
Ok(())
}
impl<'a> fmt::Display for DisplaySignature<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "(")?;
write_list(f, &self.0.params, self.1)?;
write!(f, ")")?;
if !self.0.returns.is_empty() {
write!(f, " -> ")?;
write_list(f, &self.0.returns, 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)
write!(f, "(")?;
write_list(f, &self.params)?;
write!(f, ")")?;
if !self.returns.is_empty() {
write!(f, " -> ")?;
write_list(f, &self.returns)?;
}
write!(f, " {}", self.call_conv)
}
}
@@ -157,9 +143,6 @@ pub struct AbiParam {
/// 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,
/// Was the argument converted to pointer during legalization?
pub legalized_to_pointer: bool,
}
@@ -171,7 +154,6 @@ impl AbiParam {
value_type: vt,
extension: ArgumentExtension::None,
purpose: ArgumentPurpose::Normal,
location: Default::default(),
legalized_to_pointer: false,
}
}
@@ -182,18 +164,6 @@ impl AbiParam {
value_type: vt,
extension: ArgumentExtension::None,
purpose,
location: Default::default(),
legalized_to_pointer: false,
}
}
/// Create a parameter for a special-purpose register.
pub fn special_reg(vt: Type, purpose: ArgumentPurpose, regunit: RegUnit) -> Self {
Self {
value_type: vt,
extension: ArgumentExtension::None,
purpose,
location: ArgumentLoc::Reg(regunit),
legalized_to_pointer: false,
}
}
@@ -215,42 +185,23 @@ impl AbiParam {
..self
}
}
/// Return an object that can display `self` with correct register names.
pub fn display<'a, R: Into<Option<&'a RegInfo>>>(&'a self, regs: R) -> DisplayAbiParam<'a> {
DisplayAbiParam(self, regs.into())
}
}
/// Wrapper type capable of displaying a `AbiParam` with correct register names.
pub struct DisplayAbiParam<'a>(&'a AbiParam, Option<&'a RegInfo>);
impl<'a> fmt::Display for DisplayAbiParam<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0.value_type)?;
if self.0.legalized_to_pointer {
write!(f, " ptr")?;
}
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 AbiParam {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.display(None).fmt(f)
write!(f, "{}", self.value_type)?;
if self.legalized_to_pointer {
write!(f, " ptr")?;
}
match self.extension {
ArgumentExtension::None => {}
ArgumentExtension::Uext => write!(f, " uext")?,
ArgumentExtension::Sext => write!(f, " sext")?,
}
if self.purpose != ArgumentPurpose::Normal {
write!(f, " {}", self.purpose)?;
}
Ok(())
}
}
@@ -519,15 +470,5 @@ mod tests {
sig.to_string(),
"(i32, i32x4) -> f32, b8 baldrdash_system_v"
);
// Order does not matter.
sig.params[0].location = ArgumentLoc::Stack(24);
sig.params[1].location = ArgumentLoc::Stack(8);
// Writing ABI-annotated signatures.
assert_eq!(
sig.to_string(),
"(i32 [24], i32x4 [8]) -> f32, b8 baldrdash_system_v"
);
}
}

View File

@@ -3,24 +3,21 @@
//! The `Function` struct defined in this module owns all of its basic blocks and
//! instructions.
use crate::binemit::CodeOffset;
use crate::entity::{PrimaryMap, SecondaryMap};
use crate::ir;
use crate::ir::JumpTables;
use crate::ir::{
instructions::BranchInfo, Block, ExtFuncData, FuncRef, GlobalValue, GlobalValueData, Heap,
HeapData, Inst, InstructionData, JumpTable, JumpTableData, Opcode, SigRef, StackSlot,
StackSlotData, Table, TableData,
};
use crate::ir::{BlockOffsets, InstEncodings, SourceLocs, StackSlots, ValueLocations};
use crate::ir::{DataFlowGraph, ExternalName, Layout, Signature};
use crate::ir::{JumpTableOffsets, JumpTables};
use crate::isa::{CallConv, EncInfo, Encoding, Legalize, TargetIsa};
use crate::regalloc::{EntryRegDiversions, RegDiversions};
use crate::ir::{SourceLocs, StackSlots};
use crate::isa::CallConv;
use crate::value_label::ValueLabelsRanges;
use crate::write::write_function;
#[cfg(feature = "enable-serde")]
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
#[cfg(feature = "enable-serde")]
@@ -81,10 +78,6 @@ pub struct Function {
/// Signature of this function.
pub signature: Signature,
/// The old signature of this function, before the most recent legalization,
/// if any.
pub old_signature: Option<Signature>,
/// Stack slots allocated in this function.
pub stack_slots: StackSlots,
@@ -106,45 +99,12 @@ pub struct Function {
/// Layout of blocks 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,
/// Non-default locations assigned to value at the entry of basic blocks.
///
/// At the entry of each basic block, we might have values which are not in their default
/// ValueLocation. This field records these register-to-register moves as Diversions.
pub entry_diversions: EntryRegDiversions,
/// Code offsets of the block 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 IR format.
pub offsets: BlockOffsets,
/// Code offsets of Jump Table headers.
pub jt_offsets: JumpTableOffsets,
/// Source locations.
///
/// Track the original source location for each instruction. The source locations are not
/// interpreted by Cranelift, only preserved.
pub srclocs: SourceLocs,
/// Instruction that marks the end (inclusive) of the function's prologue.
///
/// This is used for some ABIs to generate unwind information.
pub prologue_end: Option<Inst>,
/// The instructions that mark the start (inclusive) of an epilogue in the function.
///
/// This is used for some ABIs to generate unwind information.
pub epilogues_start: Vec<(Inst, Block)>,
/// An optional global value which represents an expression evaluating to
/// the stack limit for this function. This `GlobalValue` will be
/// interpreted in the prologue, if necessary, to insert a stack check to
@@ -160,7 +120,6 @@ impl Function {
version_marker: VersionMarker,
name,
signature: sig,
old_signature: None,
stack_slots: StackSlots::new(),
global_values: PrimaryMap::new(),
heaps: PrimaryMap::new(),
@@ -168,14 +127,7 @@ impl Function {
jump_tables: PrimaryMap::new(),
dfg: DataFlowGraph::new(),
layout: Layout::new(),
encodings: SecondaryMap::new(),
locations: SecondaryMap::new(),
entry_diversions: EntryRegDiversions::new(),
offsets: SecondaryMap::new(),
jt_offsets: SecondaryMap::new(),
srclocs: SecondaryMap::new(),
prologue_end: None,
epilogues_start: Vec::new(),
stack_limit: None,
}
}
@@ -190,14 +142,7 @@ impl Function {
self.jump_tables.clear();
self.dfg.clear();
self.layout.clear();
self.encodings.clear();
self.locations.clear();
self.entry_diversions.clear();
self.offsets.clear();
self.jt_offsets.clear();
self.srclocs.clear();
self.prologue_end = None;
self.epilogues_start.clear();
self.stack_limit = None;
}
@@ -243,11 +188,8 @@ impl Function {
}
/// Return an object that can display this function with correct ISA-specific annotations.
pub fn display<'a, I: Into<Option<&'a dyn TargetIsa>>>(
&'a self,
isa: I,
) -> DisplayFunction<'a> {
DisplayFunction(self, isa.into().into())
pub fn display(&self) -> DisplayFunction<'_> {
DisplayFunction(self, Default::default())
}
/// Return an object that can display this function with correct ISA-specific annotations.
@@ -268,51 +210,6 @@ impl Function {
.map(|i| self.dfg.block_params(entry)[i])
}
/// Get an iterator over the instructions in `block`, including offsets and encoded instruction
/// sizes.
///
/// The iterator returns `(offset, inst, size)` tuples, where `offset` if the offset in bytes
/// from the beginning of the function to the instruction, and `size` is the size of the
/// instruction in bytes, or 0 for unencoded instructions.
///
/// This function can only be used after the code layout has been computed by the
/// `binemit::relax_branches()` function.
pub fn inst_offsets<'a>(&'a self, block: Block, encinfo: &EncInfo) -> InstOffsetIter<'a> {
assert!(
!self.offsets.is_empty(),
"Code layout must be computed first"
);
let mut divert = RegDiversions::new();
divert.at_block(&self.entry_diversions, block);
InstOffsetIter {
encinfo: encinfo.clone(),
func: self,
divert,
encodings: &self.encodings,
offset: self.offsets[block],
iter: self.layout.block_insts(block),
}
}
/// Wrapper around `encode` which assigns `inst` the resulting encoding.
pub fn update_encoding(&mut self, inst: ir::Inst, isa: &dyn TargetIsa) -> Result<(), Legalize> {
if isa.get_mach_backend().is_some() {
Ok(())
} else {
self.encode(inst, isa).map(|e| self.encodings[inst] = e)
}
}
/// Wrapper around `TargetIsa::encode` for encoding an existing instruction
/// in the `Function`.
pub fn encode(&self, inst: ir::Inst, isa: &dyn TargetIsa) -> Result<Encoding, Legalize> {
if isa.get_mach_backend().is_some() {
Ok(Encoding::new(0, 0))
} else {
isa.encode(&self, &self.dfg[inst], self.dfg.ctrl_typevar(inst))
}
}
/// Starts collection of debug information.
pub fn collect_debug_info(&mut self) {
self.dfg.collect_debug_info();
@@ -356,7 +253,7 @@ impl Function {
}
_ => panic!(
"Unexpected instruction {} having default destination",
self.dfg.display_inst(inst, None)
self.dfg.display_inst(inst)
),
}
}
@@ -433,65 +330,27 @@ impl Function {
/// Additional annotations for function display.
#[derive(Default)]
pub struct DisplayFunctionAnnotations<'a> {
/// Enable ISA annotations.
pub isa: Option<&'a dyn TargetIsa>,
/// Enable value labels annotations.
pub value_ranges: Option<&'a ValueLabelsRanges>,
}
impl<'a> From<Option<&'a dyn TargetIsa>> for DisplayFunctionAnnotations<'a> {
fn from(isa: Option<&'a dyn TargetIsa>) -> DisplayFunctionAnnotations {
DisplayFunctionAnnotations {
isa,
value_ranges: None,
}
}
}
/// Wrapper type capable of displaying a `Function` with correct ISA annotations.
pub struct DisplayFunction<'a>(&'a Function, DisplayFunctionAnnotations<'a>);
impl<'a> fmt::Display for DisplayFunction<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write_function(fmt, self.0, &self.1)
write_function(fmt, self.0)
}
}
impl fmt::Display for Function {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write_function(fmt, self, &DisplayFunctionAnnotations::default())
write_function(fmt, self)
}
}
impl fmt::Debug for Function {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write_function(fmt, self, &DisplayFunctionAnnotations::default())
}
}
/// Iterator returning instruction offsets and sizes: `(offset, inst, size)`.
pub struct InstOffsetIter<'a> {
encinfo: EncInfo,
divert: RegDiversions,
func: &'a Function,
encodings: &'a InstEncodings,
offset: CodeOffset,
iter: ir::layout::Insts<'a>,
}
impl<'a> Iterator for InstOffsetIter<'a> {
type Item = (CodeOffset, ir::Inst, CodeOffset);
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|inst| {
self.divert.apply(&self.func.dfg[inst]);
let byte_size =
self.encinfo
.byte_size(self.encodings[inst], inst, &self.divert, self.func);
let offset = self.offset;
self.offset += byte_size;
(offset, inst, byte_size)
})
write_function(fmt, self)
}
}

View File

@@ -25,7 +25,6 @@ use crate::ir::{
trapcode::TrapCode,
types, Block, FuncRef, JumpTable, MemFlags, SigRef, StackSlot, Type, Value,
};
use crate::isa;
/// Some instructions use an external list of argument values because there is not enough space in
/// the 16-byte `InstructionData` struct. These value lists are stored in a memory pool in

View File

@@ -1,10 +1,6 @@
//! Naming well-known routines in the runtime library.
use crate::ir::{
types, AbiParam, ArgumentPurpose, ExtFuncData, ExternalName, FuncRef, Function, Inst, Opcode,
Signature, Type,
};
use crate::isa::{CallConv, RegUnit, TargetIsa};
use crate::ir::{types, ExternalName, FuncRef, Function, Opcode, Type};
use core::fmt;
use core::str::FromStr;
#[cfg(feature = "enable-serde")]
@@ -166,32 +162,11 @@ impl LibCall {
}
}
/// Get a function reference for `libcall` in `func`, following the signature
/// for `inst`.
///
/// If there is an existing reference, use it, otherwise make a new one.
pub(crate) fn get_libcall_funcref(
libcall: LibCall,
call_conv: CallConv,
func: &mut Function,
inst: Inst,
isa: &dyn TargetIsa,
) -> FuncRef {
find_funcref(libcall, func)
.unwrap_or_else(|| make_funcref_for_inst(libcall, call_conv, func, inst, isa))
}
/// Get a function reference for the probestack function in `func`.
///
/// If there is an existing reference, use it, otherwise make a new one.
pub fn get_probestack_funcref(
func: &mut Function,
reg_type: Type,
arg_reg: RegUnit,
isa: &dyn TargetIsa,
) -> FuncRef {
pub fn get_probestack_funcref(func: &mut Function) -> Option<FuncRef> {
find_funcref(LibCall::Probestack, func)
.unwrap_or_else(|| make_funcref_for_probestack(func, reg_type, arg_reg, isa))
}
/// Get the existing function reference for `libcall` in `func` if it exists.
@@ -211,65 +186,6 @@ fn find_funcref(libcall: LibCall, func: &Function) -> Option<FuncRef> {
None
}
/// Create a funcref for `LibCall::Probestack`.
fn make_funcref_for_probestack(
func: &mut Function,
reg_type: Type,
arg_reg: RegUnit,
isa: &dyn TargetIsa,
) -> FuncRef {
let mut sig = Signature::new(CallConv::Probestack);
let rax = AbiParam::special_reg(reg_type, ArgumentPurpose::Normal, arg_reg);
sig.params.push(rax);
if !isa.flags().probestack_func_adjusts_sp() {
sig.returns.push(rax);
}
make_funcref(LibCall::Probestack, func, sig, isa)
}
/// Create a funcref for `libcall` with a signature matching `inst`.
fn make_funcref_for_inst(
libcall: LibCall,
call_conv: CallConv,
func: &mut Function,
inst: Inst,
isa: &dyn TargetIsa,
) -> FuncRef {
let mut sig = Signature::new(call_conv);
for &v in func.dfg.inst_args(inst) {
sig.params.push(AbiParam::new(func.dfg.value_type(v)));
}
for &v in func.dfg.inst_results(inst) {
sig.returns.push(AbiParam::new(func.dfg.value_type(v)));
}
if call_conv.extends_baldrdash() {
// Adds the special VMContext parameter to the signature.
sig.params.push(AbiParam::special(
isa.pointer_type(),
ArgumentPurpose::VMContext,
));
}
make_funcref(libcall, func, sig, isa)
}
/// Create a funcref for `libcall`.
fn make_funcref(
libcall: LibCall,
func: &mut Function,
sig: Signature,
isa: &dyn TargetIsa,
) -> FuncRef {
let sigref = func.import_signature(sig);
func.import_function(ExtFuncData {
name: ExternalName::LibCall(libcall),
signature: sigref,
colocated: isa.flags().use_colocated_libcalls(),
})
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -2,6 +2,7 @@
mod atomic_rmw_op;
mod builder;
pub mod condcodes;
pub mod constant;
pub mod dfg;
pub mod entities;
@@ -22,7 +23,6 @@ pub mod stackslot;
mod table;
mod trapcode;
pub mod types;
mod valueloc;
#[cfg(feature = "enable-serde")]
use serde::{Deserialize, Serialize};
@@ -31,7 +31,7 @@ pub use crate::ir::atomic_rmw_op::AtomicRmwOp;
pub use crate::ir::builder::{
InsertBuilder, InstBuilder, InstBuilderBase, InstInserterBase, ReplaceBuilder,
};
pub use crate::ir::constant::{ConstantData, ConstantOffset, ConstantPool};
pub use crate::ir::constant::{ConstantData, ConstantPool};
pub use crate::ir::dfg::{DataFlowGraph, ValueDef};
pub use crate::ir::entities::{
Block, Constant, FuncRef, GlobalValue, Heap, Immediate, Inst, JumpTable, SigRef, StackSlot,
@@ -53,33 +53,17 @@ pub use crate::ir::libcall::{get_probestack_funcref, LibCall};
pub use crate::ir::memflags::{Endianness, MemFlags};
pub use crate::ir::progpoint::{ExpandedProgramPoint, ProgramOrder, ProgramPoint};
pub use crate::ir::sourceloc::SourceLoc;
pub use crate::ir::stackslot::{StackLayoutInfo, StackSlotData, StackSlotKind, StackSlots};
pub use crate::ir::stackslot::{StackSlotData, StackSlotKind, StackSlots};
pub use crate::ir::table::TableData;
pub use crate::ir::trapcode::TrapCode;
pub use crate::ir::types::Type;
pub use crate::ir::valueloc::{ArgumentLoc, ValueLoc};
pub use crate::value_label::LabelValueLoc;
pub use cranelift_codegen_shared::condcodes;
use crate::binemit;
use crate::entity::{entity_impl, PrimaryMap, SecondaryMap};
use crate::isa;
/// Map of value locations.
pub type ValueLocations = SecondaryMap<Value, ValueLoc>;
/// Map of jump tables.
pub type JumpTables = PrimaryMap<JumpTable, JumpTableData>;
/// Map of instruction encodings.
pub type InstEncodings = SecondaryMap<Inst, isa::Encoding>;
/// Code offsets for blocks.
pub type BlockOffsets = SecondaryMap<Block, binemit::CodeOffset>;
/// Code offsets for Jump Tables.
pub type JumpTableOffsets = SecondaryMap<JumpTable, binemit::CodeOffset>;
/// Source locations for instructions.
pub type SourceLocs = SecondaryMap<Inst, SourceLoc>;

View File

@@ -162,23 +162,6 @@ impl fmt::Display for StackSlotData {
}
}
/// Stack frame layout information.
///
/// This is computed by the `layout_stack()` method.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct StackLayoutInfo {
/// The total size of the stack frame.
///
/// This is the distance from the stack pointer in the current function to the stack pointer in
/// the calling function, so it includes a pushed return address as well as space for outgoing
/// call arguments.
pub frame_size: StackSize,
/// The total size of the stack frame for inbound arguments pushed by the caller.
pub inbound_args_size: StackSize,
}
/// Stack frame manager.
///
/// Keep track of all the stack slots used by a function.
@@ -193,9 +176,6 @@ pub struct StackSlots {
/// All the emergency slots.
emergency: Vec<StackSlot>,
/// Layout information computed from `layout_stack`.
pub layout_info: Option<StackLayoutInfo>,
}
/// Stack slot manager functions that behave mostly like an entity map.
@@ -210,7 +190,6 @@ impl StackSlots {
self.slots.clear();
self.outgoing.clear();
self.emergency.clear();
self.layout_info = None;
}
/// Allocate a new stack slot.

View File

@@ -79,6 +79,30 @@ impl Type {
}
}
/// Get the (minimum, maximum) values represented by each lane in the type.
/// Note that these are returned as unsigned 'bit patterns'.
pub fn bounds(self, signed: bool) -> (u128, u128) {
if signed {
match self.lane_type() {
I8 => (i8::MIN as u128, i8::MAX as u128),
I16 => (i16::MIN as u128, i16::MAX as u128),
I32 => (i32::MIN as u128, i32::MAX as u128),
I64 => (i64::MIN as u128, i64::MAX as u128),
I128 => (i128::MIN as u128, i128::MAX as u128),
_ => unimplemented!(),
}
} else {
match self.lane_type() {
I8 => (u8::MIN as u128, u8::MAX as u128),
I16 => (u16::MIN as u128, u16::MAX as u128),
I32 => (u32::MIN as u128, u32::MAX as u128),
I64 => (u64::MIN as u128, u64::MAX as u128),
I128 => (u128::MIN, u128::MAX),
_ => unimplemented!(),
}
}
}
/// Get an integer type with the requested number of bits.
pub fn int(bits: u16) -> Option<Self> {
match bits {
@@ -376,7 +400,6 @@ impl Display for Type {
f.write_str(match *self {
IFLAGS => "iflags",
FFLAGS => "fflags",
SARG_T => "sarg_t",
INVALID => panic!("INVALID encountered"),
_ => panic!("Unknown Type(0x{:x})", self.0),
})

View File

@@ -1,166 +0,0 @@
//! Value locations.
//!
//! The register allocator assigns every SSA value to either a register or a stack slot. This
//! assignment is represented by a `ValueLoc` object.
use crate::ir::StackSlot;
use crate::isa::{RegInfo, RegUnit};
use core::fmt;
#[cfg(feature = "enable-serde")]
use serde::{Deserialize, Serialize};
/// Value location.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub enum ValueLoc {
/// This value has not been assigned to a location yet.
Unassigned,
/// Value is assigned to a register.
Reg(RegUnit),
/// Value is assigned to a stack slot.
Stack(StackSlot),
}
impl Default for ValueLoc {
fn default() -> Self {
Self::Unassigned
}
}
impl ValueLoc {
/// Is this an assigned location? (That is, not `Unassigned`).
pub fn is_assigned(self) -> bool {
match self {
Self::Unassigned => false,
_ => true,
}
}
/// Get the register unit of this location, or panic.
pub fn unwrap_reg(self) -> RegUnit {
match self {
Self::Reg(ru) => ru,
_ => panic!("unwrap_reg expected register, found {:?}", self),
}
}
/// Get the stack slot of this location, or panic.
pub fn unwrap_stack(self) -> StackSlot {
match self {
Self::Stack(ss) => ss,
_ => panic!("unwrap_stack expected stack slot, found {:?}", self),
}
}
/// Return an object that can display this value location, using the register info from the
/// target ISA.
pub fn display<'a, R: Into<Option<&'a RegInfo>>>(self, regs: R) -> DisplayValueLoc<'a> {
DisplayValueLoc(self, regs.into())
}
}
/// Displaying a `ValueLoc` correctly requires the associated `RegInfo` from the target ISA.
/// Without the register info, register units are simply show as numbers.
///
/// The `DisplayValueLoc` type can display the contained `ValueLoc`.
pub struct DisplayValueLoc<'a>(ValueLoc, Option<&'a RegInfo>);
impl<'a> fmt::Display for DisplayValueLoc<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.0 {
ValueLoc::Unassigned => write!(f, "-"),
ValueLoc::Reg(ru) => match self.1 {
Some(regs) => write!(f, "{}", regs.display_regunit(ru)),
None => write!(f, "%{}", ru),
},
ValueLoc::Stack(ss) => write!(f, "{}", ss),
}
}
}
/// Function argument location.
///
/// The ABI specifies how arguments are passed to a function, and where return values appear after
/// the call. Just like a `ValueLoc`, function arguments can be passed in registers or on the
/// stack.
///
/// Function arguments on the stack are accessed differently for the incoming arguments to the
/// current function and the outgoing arguments to a called external function. For this reason,
/// the location of stack arguments is described as an offset into the array of function arguments
/// on the stack.
///
/// An `ArgumentLoc` can be translated to a `ValueLoc` only when we know if we're talking about an
/// incoming argument or an outgoing argument.
///
/// - For stack arguments, different `StackSlot` entities are used to represent incoming and
/// outgoing arguments.
/// - For register arguments, there is usually no difference, but if we ever add support for a
/// register-window ISA like SPARC, register arguments would also need to be translated.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub enum ArgumentLoc {
/// This argument has not been assigned to a location yet.
Unassigned,
/// Argument is passed in a register.
Reg(RegUnit),
/// Argument is passed on the stack, at the given byte offset into the argument array.
Stack(i32),
}
impl Default for ArgumentLoc {
fn default() -> Self {
Self::Unassigned
}
}
impl ArgumentLoc {
/// Is this an assigned location? (That is, not `Unassigned`).
pub fn is_assigned(self) -> bool {
match self {
Self::Unassigned => false,
_ => true,
}
}
/// Is this a register location?
pub fn is_reg(self) -> bool {
match self {
Self::Reg(_) => true,
_ => false,
}
}
/// Is this a stack location?
pub fn is_stack(self) -> bool {
match self {
Self::Stack(_) => true,
_ => false,
}
}
/// Return an object that can display this argument location, using the register info from the
/// target ISA.
pub fn display<'a, R: Into<Option<&'a RegInfo>>>(self, regs: R) -> DisplayArgumentLoc<'a> {
DisplayArgumentLoc(self, regs.into())
}
}
/// Displaying a `ArgumentLoc` correctly requires the associated `RegInfo` from the target ISA.
/// Without the register info, register units are simply show as numbers.
///
/// The `DisplayArgumentLoc` type can display the contained `ArgumentLoc`.
pub struct DisplayArgumentLoc<'a>(ArgumentLoc, Option<&'a RegInfo>);
impl<'a> fmt::Display for DisplayArgumentLoc<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.0 {
ArgumentLoc::Unassigned => write!(f, "-"),
ArgumentLoc::Reg(ru) => match self.1 {
Some(regs) => write!(f, "{}", regs.display_regunit(ru)),
None => write!(f, "%{}", ru),
},
ArgumentLoc::Stack(offset) => write!(f, "{}", offset),
}
}
}

View File

@@ -504,6 +504,33 @@ fn enc_dmb_ish() -> u32 {
0xD5033BBF
}
fn enc_ldal(ty: Type, op: AtomicRMWOp, rs: Reg, rt: Writable<Reg>, rn: Reg) -> u32 {
assert!(machreg_to_gpr(rt.to_reg()) != 31);
let sz = match ty {
I64 => 0b11,
I32 => 0b10,
I16 => 0b01,
I8 => 0b00,
_ => unreachable!(),
};
let op = match op {
AtomicRMWOp::Add => 0b000,
AtomicRMWOp::Clr => 0b001,
AtomicRMWOp::Eor => 0b010,
AtomicRMWOp::Set => 0b011,
AtomicRMWOp::Smax => 0b100,
AtomicRMWOp::Smin => 0b101,
AtomicRMWOp::Umax => 0b110,
AtomicRMWOp::Umin => 0b111,
};
0b00_111_000_111_00000_0_000_00_00000_00000
| (sz << 30)
| (machreg_to_gpr(rs) << 16)
| (op << 12)
| (machreg_to_gpr(rn) << 5)
| machreg_to_gpr(rt.to_reg())
}
fn enc_ldar(ty: Type, rt: Writable<Reg>, rn: Reg) -> u32 {
let sz = match ty {
I64 => 0b11,
@@ -1318,7 +1345,10 @@ impl MachInstEmit for Inst {
} => {
sink.put4(enc_ccmp_imm(size, rn, imm, nzcv, cond));
}
&Inst::AtomicRMW { ty, op } => {
&Inst::AtomicRMW { ty, op, rs, rt, rn } => {
sink.put4(enc_ldal(ty, op, rs, rt, rn));
}
&Inst::AtomicRMWLoop { ty, op } => {
/* Emit this:
again:
ldaxr{,b,h} x/w27, [x25]
@@ -1340,7 +1370,7 @@ impl MachInstEmit for Inst {
so that we simply write in the destination, the "2nd arg for op".
*/
// TODO: We should not hardcode registers here, a better idea would be to
// pass some scratch registers in the AtomicRMW pseudo-instruction, and use those
// pass some scratch registers in the AtomicRMWLoop pseudo-instruction, and use those
let xzr = zero_reg();
let x24 = xreg(24);
let x25 = xreg(25);
@@ -2308,7 +2338,11 @@ impl MachInstEmit for Inst {
VecALUOp::Orr => (0b000_01110_10_1, 0b000111),
VecALUOp::Eor => (0b001_01110_00_1, 0b000111),
VecALUOp::Bsl => (0b001_01110_01_1, 0b000111),
VecALUOp::Umaxp => (0b001_01110_00_1 | enc_size << 1, 0b101001),
VecALUOp::Umaxp => {
debug_assert_ne!(size, VectorSize::Size64x2);
(0b001_01110_00_1 | enc_size << 1, 0b101001)
}
VecALUOp::Add => (0b000_01110_00_1 | enc_size << 1, 0b100001),
VecALUOp::Sub => (0b001_01110_00_1 | enc_size << 1, 0b100001),
VecALUOp::Mul => {

View File

@@ -5986,7 +5986,7 @@ fn test_aarch64_binemit() {
));
insns.push((
Inst::AtomicRMW {
Inst::AtomicRMWLoop {
ty: I16,
op: inst_common::AtomicRmwOp::Xor,
},
@@ -5996,6 +5996,359 @@ fn test_aarch64_binemit() {
insns.push((
Inst::AtomicRMW {
ty: I8,
op: AtomicRMWOp::Add,
rs: xreg(1),
rt: writable_xreg(2),
rn: xreg(3),
},
"6200E138",
"ldaddalb w1, w2, [x3]",
));
insns.push((
Inst::AtomicRMW {
ty: I16,
op: AtomicRMWOp::Add,
rs: xreg(4),
rt: writable_xreg(5),
rn: xreg(6),
},
"C500E478",
"ldaddalh w4, w5, [x6]",
));
insns.push((
Inst::AtomicRMW {
ty: I32,
op: AtomicRMWOp::Add,
rs: xreg(7),
rt: writable_xreg(8),
rn: xreg(9),
},
"2801E7B8",
"ldaddal w7, w8, [x9]",
));
insns.push((
Inst::AtomicRMW {
ty: I64,
op: AtomicRMWOp::Add,
rs: xreg(10),
rt: writable_xreg(11),
rn: xreg(12),
},
"8B01EAF8",
"ldaddal x10, x11, [x12]",
));
insns.push((
Inst::AtomicRMW {
ty: I8,
op: AtomicRMWOp::Clr,
rs: xreg(13),
rt: writable_xreg(14),
rn: xreg(15),
},
"EE11ED38",
"ldclralb w13, w14, [x15]",
));
insns.push((
Inst::AtomicRMW {
ty: I16,
op: AtomicRMWOp::Clr,
rs: xreg(16),
rt: writable_xreg(17),
rn: xreg(18),
},
"5112F078",
"ldclralh w16, w17, [x18]",
));
insns.push((
Inst::AtomicRMW {
ty: I32,
op: AtomicRMWOp::Clr,
rs: xreg(19),
rt: writable_xreg(20),
rn: xreg(21),
},
"B412F3B8",
"ldclral w19, w20, [x21]",
));
insns.push((
Inst::AtomicRMW {
ty: I64,
op: AtomicRMWOp::Clr,
rs: xreg(22),
rt: writable_xreg(23),
rn: xreg(24),
},
"1713F6F8",
"ldclral x22, x23, [x24]",
));
insns.push((
Inst::AtomicRMW {
ty: I8,
op: AtomicRMWOp::Eor,
rs: xreg(25),
rt: writable_xreg(26),
rn: xreg(27),
},
"7A23F938",
"ldeoralb w25, w26, [x27]",
));
insns.push((
Inst::AtomicRMW {
ty: I16,
op: AtomicRMWOp::Eor,
rs: xreg(28),
rt: writable_xreg(29),
rn: xreg(30),
},
"DD23FC78",
"ldeoralh w28, fp, [lr]",
));
insns.push((
Inst::AtomicRMW {
ty: I32,
op: AtomicRMWOp::Eor,
rs: xreg(29),
rt: writable_xreg(28),
rn: xreg(27),
},
"7C23FDB8",
"ldeoral fp, w28, [x27]",
));
insns.push((
Inst::AtomicRMW {
ty: I64,
op: AtomicRMWOp::Eor,
rs: xreg(26),
rt: writable_xreg(25),
rn: xreg(24),
},
"1923FAF8",
"ldeoral x26, x25, [x24]",
));
insns.push((
Inst::AtomicRMW {
ty: I8,
op: AtomicRMWOp::Set,
rs: xreg(23),
rt: writable_xreg(22),
rn: xreg(21),
},
"B632F738",
"ldsetalb w23, w22, [x21]",
));
insns.push((
Inst::AtomicRMW {
ty: I16,
op: AtomicRMWOp::Set,
rs: xreg(20),
rt: writable_xreg(19),
rn: xreg(18),
},
"5332F478",
"ldsetalh w20, w19, [x18]",
));
insns.push((
Inst::AtomicRMW {
ty: I32,
op: AtomicRMWOp::Set,
rs: xreg(17),
rt: writable_xreg(16),
rn: xreg(15),
},
"F031F1B8",
"ldsetal w17, w16, [x15]",
));
insns.push((
Inst::AtomicRMW {
ty: I64,
op: AtomicRMWOp::Set,
rs: xreg(14),
rt: writable_xreg(13),
rn: xreg(12),
},
"8D31EEF8",
"ldsetal x14, x13, [x12]",
));
insns.push((
Inst::AtomicRMW {
ty: I8,
op: AtomicRMWOp::Smax,
rs: xreg(11),
rt: writable_xreg(10),
rn: xreg(9),
},
"2A41EB38",
"ldsmaxalb w11, w10, [x9]",
));
insns.push((
Inst::AtomicRMW {
ty: I16,
op: AtomicRMWOp::Smax,
rs: xreg(8),
rt: writable_xreg(7),
rn: xreg(6),
},
"C740E878",
"ldsmaxalh w8, w7, [x6]",
));
insns.push((
Inst::AtomicRMW {
ty: I32,
op: AtomicRMWOp::Smax,
rs: xreg(5),
rt: writable_xreg(4),
rn: xreg(3),
},
"6440E5B8",
"ldsmaxal w5, w4, [x3]",
));
insns.push((
Inst::AtomicRMW {
ty: I64,
op: AtomicRMWOp::Smax,
rs: xreg(2),
rt: writable_xreg(1),
rn: xreg(0),
},
"0140E2F8",
"ldsmaxal x2, x1, [x0]",
));
insns.push((
Inst::AtomicRMW {
ty: I8,
op: AtomicRMWOp::Smin,
rs: xreg(1),
rt: writable_xreg(2),
rn: xreg(3),
},
"6250E138",
"ldsminalb w1, w2, [x3]",
));
insns.push((
Inst::AtomicRMW {
ty: I16,
op: AtomicRMWOp::Smin,
rs: xreg(4),
rt: writable_xreg(5),
rn: xreg(6),
},
"C550E478",
"ldsminalh w4, w5, [x6]",
));
insns.push((
Inst::AtomicRMW {
ty: I32,
op: AtomicRMWOp::Smin,
rs: xreg(7),
rt: writable_xreg(8),
rn: xreg(9),
},
"2851E7B8",
"ldsminal w7, w8, [x9]",
));
insns.push((
Inst::AtomicRMW {
ty: I64,
op: AtomicRMWOp::Smin,
rs: xreg(10),
rt: writable_xreg(11),
rn: xreg(12),
},
"8B51EAF8",
"ldsminal x10, x11, [x12]",
));
insns.push((
Inst::AtomicRMW {
ty: I8,
op: AtomicRMWOp::Umax,
rs: xreg(13),
rt: writable_xreg(14),
rn: xreg(15),
},
"EE61ED38",
"ldumaxalb w13, w14, [x15]",
));
insns.push((
Inst::AtomicRMW {
ty: I16,
op: AtomicRMWOp::Umax,
rs: xreg(16),
rt: writable_xreg(17),
rn: xreg(18),
},
"5162F078",
"ldumaxalh w16, w17, [x18]",
));
insns.push((
Inst::AtomicRMW {
ty: I32,
op: AtomicRMWOp::Umax,
rs: xreg(19),
rt: writable_xreg(20),
rn: xreg(21),
},
"B462F3B8",
"ldumaxal w19, w20, [x21]",
));
insns.push((
Inst::AtomicRMW {
ty: I64,
op: AtomicRMWOp::Umax,
rs: xreg(22),
rt: writable_xreg(23),
rn: xreg(24),
},
"1763F6F8",
"ldumaxal x22, x23, [x24]",
));
insns.push((
Inst::AtomicRMW {
ty: I8,
op: AtomicRMWOp::Umin,
rs: xreg(16),
rt: writable_xreg(17),
rn: xreg(18),
},
"5172F038",
"lduminalb w16, w17, [x18]",
));
insns.push((
Inst::AtomicRMW {
ty: I16,
op: AtomicRMWOp::Umin,
rs: xreg(19),
rt: writable_xreg(20),
rn: xreg(21),
},
"B472F378",
"lduminalh w19, w20, [x21]",
));
insns.push((
Inst::AtomicRMW {
ty: I32,
op: AtomicRMWOp::Umin,
rs: xreg(22),
rt: writable_xreg(23),
rn: xreg(24),
},
"1773F6B8",
"lduminal w22, w23, [x24]",
));
insns.push((
Inst::AtomicRMW {
ty: I64,
op: AtomicRMWOp::Umin,
rs: xreg(25),
rt: writable_xreg(26),
rn: xreg(27),
},
"7A73F9F8",
"lduminal x25, x26, [x27]",
));
insns.push((
Inst::AtomicRMWLoop {
ty: I32,
op: inst_common::AtomicRmwOp::Xchg,
},

View File

@@ -451,6 +451,19 @@ pub enum VecShiftImmOp {
Sshr,
}
/// Atomic read-modify-write operations with acquire-release semantics
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum AtomicRMWOp {
Add,
Clr,
Eor,
Set,
Smax,
Smin,
Umax,
Umin,
}
/// An operation on the bits of a register. This can be paired with several instruction formats
/// below (see `Inst`) in any combination.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
@@ -775,11 +788,22 @@ pub enum Inst {
/// x27 (wr) old value
/// x24 (wr) scratch reg; value afterwards has no meaning
/// x28 (wr) scratch reg; value afterwards has no meaning
AtomicRMW {
AtomicRMWLoop {
ty: Type, // I8, I16, I32 or I64
op: inst_common::AtomicRmwOp,
},
/// An atomic read-modify-write operation. These instructions require the
/// Large System Extension (LSE) ISA support (FEAT_LSE). The instructions have
/// acquire-release semantics.
AtomicRMW {
op: AtomicRMWOp,
rs: Reg,
rt: Writable<Reg>,
rn: Reg,
ty: Type,
},
/// An atomic compare-and-swap operation. This instruction is sequentially consistent.
AtomicCAS {
rs: Writable<Reg>,
@@ -788,10 +812,10 @@ pub enum Inst {
ty: Type,
},
/// Similar to AtomicRMW, a compare-and-swap operation implemented using a load-linked
/// Similar to AtomicRMWLoop, a compare-and-swap operation implemented using a load-linked
/// store-conditional loop.
/// This instruction is sequentially consistent.
/// Note that the operand conventions, although very similar to AtomicRMW, are different:
/// Note that the operand conventions, although very similar to AtomicRMWLoop, are different:
///
/// x25 (rd) address
/// x26 (rd) expected value
@@ -1920,13 +1944,18 @@ fn aarch64_get_regs(inst: &Inst, collector: &mut RegUsageCollector) {
&Inst::CCmpImm { rn, .. } => {
collector.add_use(rn);
}
&Inst::AtomicRMW { .. } => {
&Inst::AtomicRMWLoop { .. } => {
collector.add_use(xreg(25));
collector.add_use(xreg(26));
collector.add_def(writable_xreg(24));
collector.add_def(writable_xreg(27));
collector.add_def(writable_xreg(28));
}
&Inst::AtomicRMW { rs, rt, rn, .. } => {
collector.add_use(rs);
collector.add_def(rt);
collector.add_use(rn);
}
&Inst::AtomicCAS { rs, rt, rn, .. } => {
collector.add_mod(rs);
collector.add_use(rt);
@@ -2562,9 +2591,19 @@ fn aarch64_map_regs<RUM: RegUsageMapper>(inst: &mut Inst, mapper: &RUM) {
&mut Inst::CCmpImm { ref mut rn, .. } => {
map_use(mapper, rn);
}
&mut Inst::AtomicRMW { .. } => {
&mut Inst::AtomicRMWLoop { .. } => {
// There are no vregs to map in this insn.
}
&mut Inst::AtomicRMW {
ref mut rs,
ref mut rt,
ref mut rn,
..
} => {
map_use(mapper, rs);
map_def(mapper, rt);
map_use(mapper, rn);
}
&mut Inst::AtomicCAS {
ref mut rs,
ref mut rt,
@@ -3618,7 +3657,31 @@ impl Inst {
let cond = cond.show_rru(mb_rru);
format!("ccmp {}, {}, {}, {}", rn, imm, nzcv, cond)
}
&Inst::AtomicRMW { ty, op, .. } => {
&Inst::AtomicRMW { rs, rt, rn, ty, op } => {
let op = match op {
AtomicRMWOp::Add => "ldaddal",
AtomicRMWOp::Clr => "ldclral",
AtomicRMWOp::Eor => "ldeoral",
AtomicRMWOp::Set => "ldsetal",
AtomicRMWOp::Smax => "ldsmaxal",
AtomicRMWOp::Umax => "ldumaxal",
AtomicRMWOp::Smin => "ldsminal",
AtomicRMWOp::Umin => "lduminal",
};
let size = OperandSize::from_ty(ty);
let rs = show_ireg_sized(rs, mb_rru, size);
let rt = show_ireg_sized(rt.to_reg(), mb_rru, size);
let rn = rn.show_rru(mb_rru);
let ty_suffix = match ty {
I8 => "b",
I16 => "h",
_ => "",
};
format!("{}{} {}, {}, [{}]", op, ty_suffix, rs, rt, rn)
}
&Inst::AtomicRMWLoop { ty, op, .. } => {
format!(
"atomically {{ {}_bits_at_[x25]) {:?}= x26 ; x27 = old_value_at_[x25]; x24,x28 = trash }}",
ty.bits(), op)

View File

@@ -1407,7 +1407,7 @@ pub(crate) fn lower_i64x2_mul<C: LowerCtx<I = Inst>>(c: &mut C, insn: IRInst) {
// rd = |dg+ch|be+af||dg+ch|be+af|
c.emit(Inst::VecRRR {
alu_op: VecALUOp::Addp,
rd: rd,
rd,
rn: rd.to_reg(),
rm: rd.to_reg(),
size: VectorSize::Size32x4,

View File

@@ -482,9 +482,9 @@ pub(crate) fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(
ctx.emit(Inst::AluRRRR {
alu_op: ALUOp3::MSub64,
rd: rd,
rd,
rn: rd.to_reg(),
rm: rm,
rm,
ra: rn,
});
} else {
@@ -1529,20 +1529,41 @@ pub(crate) fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(
let mut r_arg2 = put_input_in_reg(ctx, inputs[1], NarrowValueMode::None);
let ty_access = ty.unwrap();
assert!(is_valid_atomic_transaction_ty(ty_access));
// Make sure that both args are in virtual regs, since in effect
// we have to do a parallel copy to get them safely to the AtomicRMW input
// regs, and that's not guaranteed safe if either is in a real reg.
r_addr = ctx.ensure_in_vreg(r_addr, I64);
r_arg2 = ctx.ensure_in_vreg(r_arg2, I64);
// Move the args to the preordained AtomicRMW input regs
ctx.emit(Inst::gen_move(Writable::from_reg(xreg(25)), r_addr, I64));
ctx.emit(Inst::gen_move(Writable::from_reg(xreg(26)), r_arg2, I64));
// Now the AtomicRMW insn itself
let op = inst_common::AtomicRmwOp::from(ctx.data(insn).atomic_rmw_op().unwrap());
ctx.emit(Inst::AtomicRMW { ty: ty_access, op });
// And finally, copy the preordained AtomicRMW output reg to its destination.
ctx.emit(Inst::gen_move(r_dst, xreg(27), I64));
// Also, x24 and x28 are trashed. `fn aarch64_get_regs` must mention that.
let lse_op = match op {
AtomicRmwOp::Add => Some(AtomicRMWOp::Add),
AtomicRmwOp::And => Some(AtomicRMWOp::Clr),
AtomicRmwOp::Xor => Some(AtomicRMWOp::Eor),
AtomicRmwOp::Or => Some(AtomicRMWOp::Set),
AtomicRmwOp::Smax => Some(AtomicRMWOp::Smax),
AtomicRmwOp::Umax => Some(AtomicRMWOp::Umax),
AtomicRmwOp::Smin => Some(AtomicRMWOp::Smin),
AtomicRmwOp::Umin => Some(AtomicRMWOp::Umin),
_ => None,
};
if isa_flags.use_lse() && lse_op.is_some() {
ctx.emit(Inst::AtomicRMW {
op: lse_op.unwrap(),
rs: r_arg2,
rt: r_dst,
rn: r_addr,
ty: ty_access,
});
} else {
// Make sure that both args are in virtual regs, since in effect
// we have to do a parallel copy to get them safely to the AtomicRMW input
// regs, and that's not guaranteed safe if either is in a real reg.
r_addr = ctx.ensure_in_vreg(r_addr, I64);
r_arg2 = ctx.ensure_in_vreg(r_arg2, I64);
// Move the args to the preordained AtomicRMW input regs
ctx.emit(Inst::gen_move(Writable::from_reg(xreg(25)), r_addr, I64));
ctx.emit(Inst::gen_move(Writable::from_reg(xreg(26)), r_arg2, I64));
ctx.emit(Inst::AtomicRMWLoop { ty: ty_access, op });
// And finally, copy the preordained AtomicRMW output reg to its destination.
ctx.emit(Inst::gen_move(r_dst, xreg(27), I64));
// Also, x24 and x28 are trashed. `fn aarch64_get_regs` must mention that.
}
}
Opcode::AtomicCas => {
@@ -2144,16 +2165,11 @@ pub(crate) fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(
Opcode::Spill
| Opcode::Fill
| Opcode::FillNop
| Opcode::Regmove
| Opcode::CopySpecial
| Opcode::CopyToSsa
| Opcode::CopyNop
| Opcode::AdjustSpDown
| Opcode::AdjustSpUpImm
| Opcode::AdjustSpDownImm
| Opcode::IfcmpSp
| Opcode::Regspill
| Opcode::Regfill => {
| Opcode::IfcmpSp => {
panic!("Unused opcode should not be encountered.");
}
@@ -2376,14 +2392,22 @@ pub(crate) fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(
// cmp xm, #0
// cset xm, ne
let size = VectorSize::from_ty(ctx.input_ty(insn, 0));
let s = VectorSize::from_ty(src_ty);
let size = if s == VectorSize::Size64x2 {
// `vall_true` with 64-bit elements is handled elsewhere.
debug_assert_ne!(op, Opcode::VallTrue);
VectorSize::Size32x4
} else {
s
};
if op == Opcode::VanyTrue {
ctx.emit(Inst::VecRRR {
alu_op: VecALUOp::Umaxp,
rd: tmp,
rn: rm,
rm: rm,
rm,
size,
});
} else {
@@ -2806,9 +2830,9 @@ pub(crate) fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(
let rm = put_input_in_reg(ctx, inputs[1], NarrowValueMode::None);
ctx.emit(Inst::VecRRR {
alu_op: VecALUOp::Addp,
rd: rd,
rn: rn,
rm: rm,
rd,
rn,
rm,
size: VectorSize::from_ty(ty),
});
}
@@ -2905,42 +2929,62 @@ pub(crate) fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(
}
Opcode::FminPseudo | Opcode::FmaxPseudo => {
let ty = ctx.input_ty(insn, 0);
if ty == F32X4 || ty == F64X2 {
let rd = get_output_reg(ctx, outputs[0]).only_reg().unwrap();
let rm = put_input_in_reg(ctx, inputs[0], NarrowValueMode::None);
let rn = put_input_in_reg(ctx, inputs[1], NarrowValueMode::None);
let (ra, rb) = if op == Opcode::FminPseudo {
(rm, rn)
} else {
(rn, rm)
};
let ty = ty.unwrap();
let lane_type = ty.lane_type();
debug_assert!(lane_type == F32 || lane_type == F64);
if ty.is_vector() {
let size = VectorSize::from_ty(ty);
// pmin(a,b) => bitsel(b, a, cmpgt(a, b))
// pmax(a,b) => bitsel(b, a, cmpgt(b, a))
let r_dst = get_output_reg(ctx, outputs[0]).only_reg().unwrap();
let r_a = put_input_in_reg(ctx, inputs[0], NarrowValueMode::None);
let r_b = put_input_in_reg(ctx, inputs[1], NarrowValueMode::None);
// Since we're going to write the output register `r_dst` anyway, we might as
// well first use it to hold the comparison result. This has the slightly unusual
// Since we're going to write the output register `rd` anyway, we might as well
// first use it to hold the comparison result. This has the slightly unusual
// effect that we modify the output register in the first instruction (`fcmgt`)
// but read both the inputs again in the second instruction (`bsl`), which means
// that the output register can't be either of the input registers. Regalloc
// should handle this correctly, nevertheless.
ctx.emit(Inst::VecRRR {
alu_op: VecALUOp::Fcmgt,
rd: r_dst,
rn: if op == Opcode::FminPseudo { r_a } else { r_b },
rm: if op == Opcode::FminPseudo { r_b } else { r_a },
size: if ty == F32X4 {
VectorSize::Size32x4
} else {
VectorSize::Size64x2
},
rd,
rn: ra,
rm: rb,
size,
});
ctx.emit(Inst::VecRRR {
alu_op: VecALUOp::Bsl,
rd: r_dst,
rn: r_b,
rm: r_a,
size: VectorSize::Size8x16,
rd,
rn,
rm,
size,
});
} else {
return Err(CodegenError::Unsupported(format!(
"{}: Unsupported type: {:?}",
op, ty
)));
if lane_type == F32 {
ctx.emit(Inst::FpuCmp32 { rn: ra, rm: rb });
ctx.emit(Inst::FpuCSel32 {
rd,
rn,
rm,
cond: Cond::Gt,
});
} else {
ctx.emit(Inst::FpuCmp64 { rn: ra, rm: rb });
ctx.emit(Inst::FpuCSel64 {
rd,
rn,
rm,
cond: Cond::Gt,
});
}
}
}
@@ -3397,7 +3441,7 @@ pub(crate) fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(
ctx.emit(Inst::FpuRRR {
fpu_op: choose_32_64(in_ty, FPUOp2::Min32, FPUOp2::Min64),
rd: rtmp2,
rn: rn,
rn,
rm: rtmp1.to_reg(),
});
if in_bits == 32 {
@@ -3419,7 +3463,7 @@ pub(crate) fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(
}
}
if in_bits == 32 {
ctx.emit(Inst::FpuCmp32 { rn: rn, rm: rn });
ctx.emit(Inst::FpuCmp32 { rn, rm: rn });
ctx.emit(Inst::FpuCSel32 {
rd: rtmp2,
rn: rtmp1.to_reg(),
@@ -3427,7 +3471,7 @@ pub(crate) fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(
cond: Cond::Ne,
});
} else {
ctx.emit(Inst::FpuCmp64 { rn: rn, rm: rn });
ctx.emit(Inst::FpuCmp64 { rn, rm: rn });
ctx.emit(Inst::FpuCSel64 {
rd: rtmp2,
rn: rtmp1.to_reg(),
@@ -3516,47 +3560,6 @@ pub(crate) fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(
panic!("ALU+imm and ALU+carry ops should not appear here!");
}
#[cfg(feature = "x86")]
Opcode::X86Udivmodx
| Opcode::X86Sdivmodx
| Opcode::X86Umulx
| Opcode::X86Smulx
| Opcode::X86Cvtt2si
| Opcode::X86Fmin
| Opcode::X86Fmax
| Opcode::X86Push
| Opcode::X86Pop
| Opcode::X86Bsr
| Opcode::X86Bsf
| Opcode::X86Pblendw
| Opcode::X86Pshufd
| Opcode::X86Pshufb
| Opcode::X86Pextr
| Opcode::X86Pinsr
| Opcode::X86Insertps
| Opcode::X86Movsd
| Opcode::X86Movlhps
| Opcode::X86Palignr
| Opcode::X86Psll
| Opcode::X86Psrl
| Opcode::X86Psra
| Opcode::X86Ptest
| Opcode::X86Pmaxs
| Opcode::X86Pmaxu
| Opcode::X86Pmins
| Opcode::X86Pminu
| Opcode::X86Pmullq
| Opcode::X86Pmuludq
| Opcode::X86Punpckh
| Opcode::X86Punpckl
| Opcode::X86Vcvtudq2ps
| Opcode::X86ElfTlsGetAddr
| Opcode::X86MachoTlsGetAddr => {
panic!("x86-specific opcode in supposedly arch-neutral IR!");
}
Opcode::DummySargT => unreachable!(),
Opcode::Iabs => {
let rd = get_output_reg(ctx, outputs[0]).only_reg().unwrap();
let rn = put_input_in_reg(ctx, inputs[0], NarrowValueMode::None);

View File

@@ -11,7 +11,6 @@ use crate::machinst::{
use crate::result::CodegenResult;
use crate::settings as shared_settings;
use alloc::{boxed::Box, vec::Vec};
use core::hash::{Hash, Hasher};
use regalloc::{PrettyPrint, RealRegUniverse};
use target_lexicon::{Aarch64Architecture, Architecture, Triple};
@@ -111,11 +110,6 @@ impl MachBackend for AArch64Backend {
self.isa_flags.iter().collect()
}
fn hash_all_flags(&self, mut hasher: &mut dyn Hasher) {
self.flags.hash(&mut hasher);
self.isa_flags.hash(&mut hasher);
}
fn reg_universe(&self) -> &RealRegUniverse {
&self.reg_universe
}
@@ -126,13 +120,6 @@ impl MachBackend for AArch64Backend {
IntCC::UnsignedGreaterThanOrEqual
}
fn unsigned_sub_overflow_condition(&self) -> IntCC {
// unsigned `<`; this corresponds to the carry flag cleared on aarch64, which happens on
// underflow of a subtract (aarch64 follows a carry-cleared-on-borrow convention, the
// opposite of x86).
IntCC::UnsignedLessThan
}
#[cfg(feature = "unwind")]
fn emit_unwind_info(
&self,

View File

@@ -11,7 +11,6 @@ use crate::result::CodegenResult;
use crate::settings;
use alloc::{boxed::Box, vec::Vec};
use core::hash::{Hash, Hasher};
use regalloc::{PrettyPrint, RealRegUniverse};
use target_lexicon::{Architecture, ArmArchitecture, Triple};
@@ -101,10 +100,6 @@ impl MachBackend for Arm32Backend {
Vec::new()
}
fn hash_all_flags(&self, mut hasher: &mut dyn Hasher) {
self.flags.hash(&mut hasher);
}
fn reg_universe(&self) -> &RealRegUniverse {
&self.reg_universe
}
@@ -114,11 +109,6 @@ impl MachBackend for Arm32Backend {
IntCC::UnsignedGreaterThanOrEqual
}
fn unsigned_sub_overflow_condition(&self) -> IntCC {
// Carry flag clear.
IntCC::UnsignedLessThan
}
fn text_section_builder(&self, num_funcs: u32) -> Box<dyn TextSectionBuilder> {
Box::new(MachTextSectionBuilder::<inst::Inst>::new(num_funcs))
}

View File

@@ -1,207 +0,0 @@
//! Register constraints for instruction operands.
//!
//! An encoding recipe specifies how an instruction is encoded as binary machine code, but it only
//! works if the operands and results satisfy certain constraints. Constraints on immediate
//! operands are checked by instruction predicates when the recipe is chosen.
//!
//! It is the register allocator's job to make sure that the register constraints on value operands
//! are satisfied.
use crate::binemit::CodeOffset;
use crate::ir::{Function, Inst, ValueLoc};
use crate::isa::{RegClass, RegUnit};
use crate::regalloc::RegDiversions;
/// Register constraint for a single value operand or instruction result.
#[derive(PartialEq, Debug)]
pub struct OperandConstraint {
/// The kind of constraint.
pub kind: ConstraintKind,
/// The register class of the operand.
///
/// This applies to all kinds of constraints, but with slightly different meaning.
pub regclass: RegClass,
}
impl OperandConstraint {
/// Check if this operand constraint is satisfied by the given value location.
/// For tied constraints, this only checks the register class, not that the
/// counterpart operand has the same value location.
pub fn satisfied(&self, loc: ValueLoc) -> bool {
match self.kind {
ConstraintKind::Reg | ConstraintKind::Tied(_) => {
if let ValueLoc::Reg(reg) = loc {
self.regclass.contains(reg)
} else {
false
}
}
ConstraintKind::FixedReg(reg) | ConstraintKind::FixedTied(reg) => {
loc == ValueLoc::Reg(reg) && self.regclass.contains(reg)
}
ConstraintKind::Stack => {
if let ValueLoc::Stack(_) = loc {
true
} else {
false
}
}
}
}
}
/// The different kinds of operand constraints.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ConstraintKind {
/// This operand or result must be a register from the given register class.
Reg,
/// This operand or result must be a fixed register.
///
/// The constraint's `regclass` field is the top-level register class containing the fixed
/// register.
FixedReg(RegUnit),
/// This result value must use the same register as an input value operand.
///
/// The associated number is the index of the input value operand this result is tied to. The
/// constraint's `regclass` field is the same as the tied operand's register class.
///
/// When an (in, out) operand pair is tied, this constraint kind appears in both the `ins` and
/// the `outs` arrays. The constraint for the in operand is `Tied(out)`, and the constraint for
/// the out operand is `Tied(in)`.
Tied(u8),
/// This operand must be a fixed register, and it has a tied counterpart.
///
/// This works just like `FixedReg`, but additionally indicates that there are identical
/// input/output operands for this fixed register. For an input operand, this means that the
/// value will be clobbered by the instruction
FixedTied(RegUnit),
/// This operand must be a value in a stack slot.
///
/// The constraint's `regclass` field is the register class that would normally be used to load
/// and store values of this type.
Stack,
}
/// Value operand constraints for an encoding recipe.
#[derive(PartialEq, Clone)]
pub struct RecipeConstraints {
/// Constraints for the instruction's fixed value operands.
///
/// If the instruction takes a variable number of operands, the register constraints for those
/// operands must be computed dynamically.
///
/// - For branches and jumps, block arguments must match the expectations of the destination block.
/// - For calls and returns, the calling convention ABI specifies constraints.
pub ins: &'static [OperandConstraint],
/// Constraints for the instruction's fixed results.
///
/// If the instruction produces a variable number of results, it's probably a call and the
/// constraints must be derived from the calling convention ABI.
pub outs: &'static [OperandConstraint],
/// Are any of the input constraints `FixedReg` or `FixedTied`?
pub fixed_ins: bool,
/// Are any of the output constraints `FixedReg` or `FixedTied`?
pub fixed_outs: bool,
/// Are any of the input/output constraints `Tied` (but not `FixedTied`)?
pub tied_ops: bool,
/// Does this instruction clobber the CPU flags?
///
/// When true, SSA values of type `iflags` or `fflags` can not be live across the instruction.
pub clobbers_flags: bool,
}
impl RecipeConstraints {
/// Check that these constraints are satisfied by the operands on `inst`.
pub fn satisfied(&self, inst: Inst, divert: &RegDiversions, func: &Function) -> bool {
for (&arg, constraint) in func.dfg.inst_args(inst).iter().zip(self.ins) {
let loc = divert.get(arg, &func.locations);
if let ConstraintKind::Tied(out_index) = constraint.kind {
let out_val = func.dfg.inst_results(inst)[out_index as usize];
let out_loc = func.locations[out_val];
if loc != out_loc {
return false;
}
}
if !constraint.satisfied(loc) {
return false;
}
}
for (&arg, constraint) in func.dfg.inst_results(inst).iter().zip(self.outs) {
let loc = divert.get(arg, &func.locations);
if !constraint.satisfied(loc) {
return false;
}
}
true
}
}
/// Constraints on the range of a branch instruction.
///
/// A branch instruction usually encodes its destination as a signed n-bit offset from an origin.
/// The origin depends on the ISA and the specific instruction:
///
/// - RISC-V and ARM Aarch64 use the address of the branch instruction, `origin = 0`.
/// - x86 uses the address of the instruction following the branch, `origin = 2` for a 2-byte
/// branch instruction.
/// - ARM's A32 encoding uses the address of the branch instruction + 8 bytes, `origin = 8`.
#[derive(Clone, Copy, Debug)]
pub struct BranchRange {
/// Offset in bytes from the address of the branch instruction to the origin used for computing
/// the branch displacement. This is the destination of a branch that encodes a 0 displacement.
pub origin: u8,
/// Number of bits in the signed byte displacement encoded in the instruction. This does not
/// account for branches that can only target aligned addresses.
pub bits: u8,
}
impl BranchRange {
/// Determine if this branch range can represent the range from `branch` to `dest`, where
/// `branch` is the code offset of the branch instruction itself and `dest` is the code offset
/// of the destination block header.
///
/// This method does not detect if the range is larger than 2 GB.
pub fn contains(self, branch: CodeOffset, dest: CodeOffset) -> bool {
let d = dest.wrapping_sub(branch + CodeOffset::from(self.origin)) as i32;
let s = 32 - self.bits;
d == d << s >> s
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn branch_range() {
// ARM T1 branch.
let t1 = BranchRange { origin: 4, bits: 9 };
assert!(t1.contains(0, 0));
assert!(t1.contains(0, 2));
assert!(t1.contains(2, 0));
assert!(t1.contains(1000, 1000));
// Forward limit.
assert!(t1.contains(1000, 1258));
assert!(!t1.contains(1000, 1260));
// Backward limit
assert!(t1.contains(1000, 748));
assert!(!t1.contains(1000, 746));
}
}

View File

@@ -1,292 +0,0 @@
//! Support types for generated encoding tables.
//!
//! This module contains types and functions for working with the encoding tables generated by
//! `cranelift-codegen/meta/src/gen_encodings.rs`.
use crate::constant_hash::{probe, Table};
use crate::ir::{Function, InstructionData, Opcode, Type};
use crate::isa::{Encoding, Legalize};
use crate::settings::PredicateView;
use core::ops::Range;
/// A recipe predicate.
///
/// This is a predicate function capable of testing ISA and instruction predicates simultaneously.
///
/// A None predicate is always satisfied.
pub type RecipePredicate = Option<fn(PredicateView, &InstructionData) -> bool>;
/// An instruction predicate.
///
/// This is a predicate function that needs to be tested in addition to the recipe predicate. It
/// can't depend on ISA settings.
pub type InstPredicate = fn(&Function, &InstructionData) -> bool;
/// Legalization action to perform when no encoding can be found for an instruction.
///
/// This is an index into an ISA-specific table of legalization actions.
pub type LegalizeCode = u8;
/// Level 1 hash table entry.
///
/// One level 1 hash table is generated per CPU mode. This table is keyed by the controlling type
/// variable, using `INVALID` for non-polymorphic instructions.
///
/// The hash table values are references to level 2 hash tables, encoded as an offset in `LEVEL2`
/// where the table begins, and the binary logarithm of its length. All the level 2 hash tables
/// have a power-of-two size.
///
/// Entries are generic over the offset type. It will typically be `u32` or `u16`, depending on the
/// size of the `LEVEL2` table.
///
/// Empty entries are encoded with a `!0` value for `log2len` which will always be out of range.
/// Entries that have a `legalize` value but no level 2 table have an `offset` field that is out of
/// bounds.
pub struct Level1Entry<OffT: Into<u32> + Copy> {
pub ty: Type,
pub log2len: u8,
pub legalize: LegalizeCode,
pub offset: OffT,
}
impl<OffT: Into<u32> + Copy> Level1Entry<OffT> {
/// Get the level 2 table range indicated by this entry.
fn range(&self) -> Range<usize> {
let b = self.offset.into() as usize;
b..b + (1 << self.log2len)
}
}
impl<OffT: Into<u32> + Copy> Table<Type> for [Level1Entry<OffT>] {
fn len(&self) -> usize {
self.len()
}
fn key(&self, idx: usize) -> Option<Type> {
if self[idx].log2len != !0 {
Some(self[idx].ty)
} else {
None
}
}
}
/// Level 2 hash table entry.
///
/// The second level hash tables are keyed by `Opcode`, and contain an offset into the `ENCLISTS`
/// table where the encoding recipes for the instruction are stored.
///
/// Entries are generic over the offset type which depends on the size of `ENCLISTS`. A `u16`
/// offset allows the entries to be only 32 bits each. There is no benefit to dropping down to `u8`
/// for tiny ISAs. The entries won't shrink below 32 bits since the opcode is expected to be 16
/// bits.
///
/// Empty entries are encoded with a `NotAnOpcode` `opcode` field.
pub struct Level2Entry<OffT: Into<u32> + Copy> {
pub opcode: Option<Opcode>,
pub offset: OffT,
}
impl<OffT: Into<u32> + Copy> Table<Opcode> for [Level2Entry<OffT>] {
fn len(&self) -> usize {
self.len()
}
fn key(&self, idx: usize) -> Option<Opcode> {
self[idx].opcode
}
}
/// Two-level hash table lookup and iterator construction.
///
/// Given the controlling type variable and instruction opcode, find the corresponding encoding
/// list.
///
/// Returns an iterator that produces legal encodings for `inst`.
pub fn lookup_enclist<'a, OffT1, OffT2>(
ctrl_typevar: Type,
inst: &'a InstructionData,
func: &'a Function,
level1_table: &'static [Level1Entry<OffT1>],
level2_table: &'static [Level2Entry<OffT2>],
enclist: &'static [EncListEntry],
legalize_actions: &'static [Legalize],
recipe_preds: &'static [RecipePredicate],
inst_preds: &'static [InstPredicate],
isa_preds: PredicateView<'a>,
) -> Encodings<'a>
where
OffT1: Into<u32> + Copy,
OffT2: Into<u32> + Copy,
{
let (offset, legalize) = match probe(level1_table, ctrl_typevar, ctrl_typevar.index()) {
Err(l1idx) => {
// No level 1 entry found for the type.
// We have a sentinel entry with the default legalization code.
(!0, level1_table[l1idx].legalize)
}
Ok(l1idx) => {
// We have a valid level 1 entry for this type.
let l1ent = &level1_table[l1idx];
let offset = match level2_table.get(l1ent.range()) {
Some(l2tab) => {
let opcode = inst.opcode();
match probe(l2tab, opcode, opcode as usize) {
Ok(l2idx) => l2tab[l2idx].offset.into() as usize,
Err(_) => !0,
}
}
// The l1ent range is invalid. This means that we just have a customized
// legalization code for this type. The level 2 table is empty.
None => !0,
};
(offset, l1ent.legalize)
}
};
// Now we have an offset into `enclist` that is `!0` when no encoding list could be found.
// The default legalization code is always valid.
Encodings::new(
offset,
legalize,
inst,
func,
enclist,
legalize_actions,
recipe_preds,
inst_preds,
isa_preds,
)
}
/// Encoding list entry.
///
/// Encoding lists are represented as sequences of u16 words.
pub type EncListEntry = u16;
/// Number of bits used to represent a predicate. c.f. `meta/src/gen_encodings.rs`.
const PRED_BITS: u8 = 12;
const PRED_MASK: usize = (1 << PRED_BITS) - 1;
/// First code word representing a predicate check. c.f. `meta/src/gen_encodings.rs`.
const PRED_START: usize = 0x1000;
/// An iterator over legal encodings for the instruction.
pub struct Encodings<'a> {
// Current offset into `enclist`, or out of bounds after we've reached the end.
offset: usize,
// Legalization code to use of no encoding is found.
legalize: LegalizeCode,
inst: &'a InstructionData,
func: &'a Function,
enclist: &'static [EncListEntry],
legalize_actions: &'static [Legalize],
recipe_preds: &'static [RecipePredicate],
inst_preds: &'static [InstPredicate],
isa_preds: PredicateView<'a>,
}
impl<'a> Encodings<'a> {
/// Creates a new instance of `Encodings`.
///
/// This iterator provides search for encodings that applies to the given instruction. The
/// encoding lists are laid out such that first call to `next` returns valid entry in the list
/// or `None`.
pub fn new(
offset: usize,
legalize: LegalizeCode,
inst: &'a InstructionData,
func: &'a Function,
enclist: &'static [EncListEntry],
legalize_actions: &'static [Legalize],
recipe_preds: &'static [RecipePredicate],
inst_preds: &'static [InstPredicate],
isa_preds: PredicateView<'a>,
) -> Self {
Encodings {
offset,
inst,
func,
legalize,
isa_preds,
recipe_preds,
inst_preds,
enclist,
legalize_actions,
}
}
/// Get the legalization action that caused the enumeration of encodings to stop.
/// This can be the default legalization action for the type or a custom code for the
/// instruction.
///
/// This method must only be called after the iterator returns `None`.
pub fn legalize(&self) -> Legalize {
debug_assert_eq!(self.offset, !0, "Premature Encodings::legalize()");
self.legalize_actions[self.legalize as usize]
}
/// Check if the `rpred` recipe predicate is satisfied.
fn check_recipe(&self, rpred: RecipePredicate) -> bool {
match rpred {
Some(p) => p(self.isa_preds, self.inst),
None => true,
}
}
/// Check an instruction or isa predicate.
fn check_pred(&self, pred: usize) -> bool {
if let Some(&p) = self.inst_preds.get(pred) {
p(self.func, self.inst)
} else {
let pred = pred - self.inst_preds.len();
self.isa_preds.test(pred)
}
}
}
impl<'a> Iterator for Encodings<'a> {
type Item = Encoding;
fn next(&mut self) -> Option<Encoding> {
while let Some(entryref) = self.enclist.get(self.offset) {
let entry = *entryref as usize;
// Check for "recipe+bits".
let recipe = entry >> 1;
if let Some(&rpred) = self.recipe_preds.get(recipe) {
let bits = self.offset + 1;
if entry & 1 == 0 {
self.offset += 2; // Next entry.
} else {
self.offset = !0; // Stop.
}
if self.check_recipe(rpred) {
return Some(Encoding::new(recipe as u16, self.enclist[bits]));
}
continue;
}
// Check for "stop with legalize".
if entry < PRED_START {
self.legalize = (entry - 2 * self.recipe_preds.len()) as LegalizeCode;
self.offset = !0; // Stop.
return None;
}
// Finally, this must be a predicate entry.
let pred_entry = entry - PRED_START;
let skip = pred_entry >> PRED_BITS;
let pred = pred_entry & PRED_MASK;
if self.check_pred(pred) {
self.offset += 1;
} else if skip == 0 {
self.offset = !0; // Stop.
return None;
} else {
self.offset += 1 + skip;
}
}
None
}
}

View File

@@ -1,167 +0,0 @@
//! The `Encoding` struct.
use crate::binemit::CodeOffset;
use crate::ir::{Function, Inst};
use crate::isa::constraints::{BranchRange, RecipeConstraints};
use crate::regalloc::RegDiversions;
use core::fmt;
#[cfg(feature = "enable-serde")]
use serde::{Deserialize, Serialize};
/// Bits needed to encode an instruction as binary machine code.
///
/// The encoding consists of two parts, both specific to the target ISA: An encoding *recipe*, and
/// encoding *bits*. The recipe determines the native instruction format and the mapping of
/// operands to encoded bits. The encoding bits provide additional information to the recipe,
/// typically parts of the opcode.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct Encoding {
recipe: u16,
bits: u16,
}
impl Encoding {
/// Create a new `Encoding` containing `(recipe, bits)`.
pub fn new(recipe: u16, bits: u16) -> Self {
Self { recipe, bits }
}
/// Get the recipe number in this encoding.
pub fn recipe(self) -> usize {
self.recipe as usize
}
/// Get the recipe-specific encoding bits.
pub fn bits(self) -> u16 {
self.bits
}
/// Is this a legal encoding, or the default placeholder?
pub fn is_legal(self) -> bool {
self != Self::default()
}
}
/// The default encoding is the illegal one.
impl Default for Encoding {
fn default() -> Self {
Self::new(0xffff, 0xffff)
}
}
/// ISA-independent display of an encoding.
impl fmt::Display for Encoding {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.is_legal() {
write!(f, "{}#{:02x}", self.recipe, self.bits)
} else {
write!(f, "-")
}
}
}
/// Temporary object that holds enough context to properly display an encoding.
/// This is meant to be created by `EncInfo::display()`.
pub struct DisplayEncoding {
pub encoding: Encoding,
pub recipe_names: &'static [&'static str],
}
impl fmt::Display for DisplayEncoding {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.encoding.is_legal() {
write!(
f,
"{}#{:02x}",
self.recipe_names[self.encoding.recipe()],
self.encoding.bits
)
} else {
write!(f, "-")
}
}
}
type SizeCalculatorFn = fn(&RecipeSizing, Encoding, Inst, &RegDiversions, &Function) -> u8;
/// Returns the base size of the Recipe, assuming it's fixed. This is the default for most
/// encodings; others can be variable and longer than this base size, depending on the registers
/// they're using and use a different function, specific per platform.
pub fn base_size(
sizing: &RecipeSizing,
_: Encoding,
_: Inst,
_: &RegDiversions,
_: &Function,
) -> u8 {
sizing.base_size
}
/// Code size information for an encoding recipe.
///
/// Encoding recipes may have runtime-determined instruction size.
pub struct RecipeSizing {
/// Minimum size in bytes of instructions encoded with this recipe.
pub base_size: u8,
/// Method computing the instruction's real size, given inputs and outputs.
pub compute_size: SizeCalculatorFn,
/// Allowed branch range in this recipe, if any.
///
/// All encoding recipes for branches have exact branch range information.
pub branch_range: Option<BranchRange>,
}
/// Information about all the encodings in this ISA.
#[derive(Clone)]
pub struct EncInfo {
/// Constraints on value operands per recipe.
pub constraints: &'static [RecipeConstraints],
/// Code size information per recipe.
pub sizing: &'static [RecipeSizing],
/// Names of encoding recipes.
pub names: &'static [&'static str],
}
impl EncInfo {
/// Get the value operand constraints for `enc` if it is a legal encoding.
pub fn operand_constraints(&self, enc: Encoding) -> Option<&'static RecipeConstraints> {
self.constraints.get(enc.recipe())
}
/// Create an object that can display an ISA-dependent encoding properly.
pub fn display(&self, enc: Encoding) -> DisplayEncoding {
DisplayEncoding {
encoding: enc,
recipe_names: self.names,
}
}
/// Get the size in bytes of `inst`, if it were encoded with `enc`.
///
/// Returns 0 for illegal encodings.
pub fn byte_size(
&self,
enc: Encoding,
inst: Inst,
divert: &RegDiversions,
func: &Function,
) -> CodeOffset {
self.sizing.get(enc.recipe()).map_or(0, |s| {
let compute_size = s.compute_size;
CodeOffset::from(compute_size(&s, enc, inst, divert, func))
})
}
/// Get the branch range that is supported by `enc`, if any.
///
/// This will never return `None` for a legal branch encoding.
pub fn branch_range(&self, enc: Encoding) -> Option<BranchRange> {
self.sizing.get(enc.recipe()).and_then(|s| s.branch_range)
}
}

View File

@@ -1,12 +0,0 @@
//! Legacy ("old-style") backends that will be removed in the future.
// N.B.: the old x86-64 backend (`x86`) and the new one (`x64`) are both
// included whenever building with x86 support. The new backend is the default,
// but the old can be requested with `BackendVariant::Legacy`. However, if this
// crate is built with the `old-x86-backend` feature, then the old backend is
// default instead.
#[cfg(feature = "x86")]
pub(crate) mod x86;
#[cfg(feature = "riscv")]
pub(crate) mod riscv;

View File

@@ -1,149 +0,0 @@
//! RISC-V ABI implementation.
//!
//! This module implements the RISC-V calling convention through the primary `legalize_signature()`
//! entry point.
//!
//! This doesn't support the soft-float ABI at the moment.
use super::registers::{FPR, GPR};
use super::settings;
use crate::abi::{legalize_args, ArgAction, ArgAssigner, ValueConversion};
use crate::ir::{self, AbiParam, ArgumentExtension, ArgumentLoc, ArgumentPurpose, Type};
use crate::isa::RegClass;
use crate::regalloc::RegisterSet;
use alloc::borrow::Cow;
use core::i32;
use target_lexicon::Triple;
struct Args {
pointer_bits: u8,
pointer_bytes: u8,
pointer_type: Type,
regs: u32,
reg_limit: u32,
offset: u32,
}
impl Args {
fn new(bits: u8, enable_e: bool) -> Self {
Self {
pointer_bits: bits,
pointer_bytes: bits / 8,
pointer_type: Type::int(u16::from(bits)).unwrap(),
regs: 0,
reg_limit: if enable_e { 6 } else { 8 },
offset: 0,
}
}
}
impl ArgAssigner for Args {
fn assign(&mut self, arg: &AbiParam) -> ArgAction {
fn align(value: u32, to: u32) -> u32 {
(value + to - 1) & !(to - 1)
}
let ty = arg.value_type;
// Check for a legal type.
// RISC-V doesn't have SIMD at all, so break all vectors down.
if ty.is_vector() {
return ValueConversion::VectorSplit.into();
}
// Large integers and booleans are broken down to fit in a register.
if !ty.is_float() && ty.bits() > u16::from(self.pointer_bits) {
// Align registers and stack to a multiple of two pointers.
self.regs = align(self.regs, 2);
self.offset = align(self.offset, 2 * u32::from(self.pointer_bytes));
return ValueConversion::IntSplit.into();
}
// Small integers are extended to the size of a pointer register.
if ty.is_int() && ty.bits() < u16::from(self.pointer_bits) {
match arg.extension {
ArgumentExtension::None => {}
ArgumentExtension::Uext => return ValueConversion::Uext(self.pointer_type).into(),
ArgumentExtension::Sext => return ValueConversion::Sext(self.pointer_type).into(),
}
}
if self.regs < self.reg_limit {
// Assign to a register.
let reg = if ty.is_float() {
FPR.unit(10 + self.regs as usize)
} else {
GPR.unit(10 + self.regs as usize)
};
self.regs += 1;
ArgumentLoc::Reg(reg).into()
} else {
// Assign a stack location.
let loc = ArgumentLoc::Stack(self.offset as i32);
self.offset += u32::from(self.pointer_bytes);
debug_assert!(self.offset <= i32::MAX as u32);
loc.into()
}
}
}
/// Legalize `sig` for RISC-V.
pub fn legalize_signature(
sig: &mut Cow<ir::Signature>,
triple: &Triple,
isa_flags: &settings::Flags,
current: bool,
) {
let bits = triple.pointer_width().unwrap().bits();
let mut args = Args::new(bits, isa_flags.enable_e());
if let Some(new_params) = legalize_args(&sig.params, &mut args) {
sig.to_mut().params = new_params;
}
let mut rets = Args::new(bits, isa_flags.enable_e());
if let Some(new_returns) = legalize_args(&sig.returns, &mut rets) {
sig.to_mut().returns = new_returns;
}
if current {
let ptr = Type::int(u16::from(bits)).unwrap();
// Add the link register as an argument and return value.
//
// The `jalr` instruction implementing a return can technically accept the return address
// in any register, but a micro-architecture with a return address predictor will only
// recognize it as a return if the address is in `x1`.
let link = AbiParam::special_reg(ptr, ArgumentPurpose::Link, GPR.unit(1));
sig.to_mut().params.push(link);
sig.to_mut().returns.push(link);
}
}
/// Get register class for a type appearing in a legalized signature.
pub fn regclass_for_abi_type(ty: Type) -> RegClass {
if ty.is_float() {
FPR
} else {
GPR
}
}
pub fn allocatable_registers(_func: &ir::Function, isa_flags: &settings::Flags) -> RegisterSet {
let mut regs = RegisterSet::new();
regs.take(GPR, GPR.unit(0)); // Hard-wired 0.
// %x1 is the link register which is available for allocation.
regs.take(GPR, GPR.unit(2)); // Stack pointer.
regs.take(GPR, GPR.unit(3)); // Global pointer.
regs.take(GPR, GPR.unit(4)); // Thread pointer.
// TODO: %x8 is the frame pointer. Reserve it?
// Remove %x16 and up for RV32E.
if isa_flags.enable_e() {
for u in 16..32 {
regs.take(GPR, GPR.unit(u));
}
}
regs
}

View File

@@ -1,182 +0,0 @@
//! Emitting binary RISC-V machine code.
use crate::binemit::{bad_encoding, CodeSink, Reloc};
use crate::ir::{Function, Inst, InstructionData};
use crate::isa::{RegUnit, StackBaseMask, StackRef, TargetIsa};
use crate::predicates::is_signed_int;
use crate::regalloc::RegDiversions;
use core::u32;
include!(concat!(env!("OUT_DIR"), "/binemit-riscv.rs"));
/// R-type instructions.
///
/// 31 24 19 14 11 6
/// funct7 rs2 rs1 funct3 rd opcode
/// 25 20 15 12 7 0
///
/// Encoding bits: `opcode[6:2] | (funct3 << 5) | (funct7 << 8)`.
fn put_r<CS: CodeSink + ?Sized>(bits: u16, rs1: RegUnit, rs2: RegUnit, rd: RegUnit, sink: &mut CS) {
let bits = u32::from(bits);
let opcode5 = bits & 0x1f;
let funct3 = (bits >> 5) & 0x7;
let funct7 = (bits >> 8) & 0x7f;
let rs1 = u32::from(rs1) & 0x1f;
let rs2 = u32::from(rs2) & 0x1f;
let rd = u32::from(rd) & 0x1f;
// 0-6: opcode
let mut i = 0x3;
i |= opcode5 << 2;
i |= rd << 7;
i |= funct3 << 12;
i |= rs1 << 15;
i |= rs2 << 20;
i |= funct7 << 25;
sink.put4(i);
}
/// R-type instructions with a shift amount instead of rs2.
///
/// 31 25 19 14 11 6
/// funct7 shamt rs1 funct3 rd opcode
/// 25 20 15 12 7 0
///
/// Both funct7 and shamt contribute to bit 25. In RV64, shamt uses it for shifts > 31.
///
/// Encoding bits: `opcode[6:2] | (funct3 << 5) | (funct7 << 8)`.
fn put_rshamt<CS: CodeSink + ?Sized>(
bits: u16,
rs1: RegUnit,
shamt: i64,
rd: RegUnit,
sink: &mut CS,
) {
let bits = u32::from(bits);
let opcode5 = bits & 0x1f;
let funct3 = (bits >> 5) & 0x7;
let funct7 = (bits >> 8) & 0x7f;
let rs1 = u32::from(rs1) & 0x1f;
let shamt = shamt as u32 & 0x3f;
let rd = u32::from(rd) & 0x1f;
// 0-6: opcode
let mut i = 0x3;
i |= opcode5 << 2;
i |= rd << 7;
i |= funct3 << 12;
i |= rs1 << 15;
i |= shamt << 20;
i |= funct7 << 25;
sink.put4(i);
}
/// I-type instructions.
///
/// 31 19 14 11 6
/// imm rs1 funct3 rd opcode
/// 20 15 12 7 0
///
/// Encoding bits: `opcode[6:2] | (funct3 << 5)`
fn put_i<CS: CodeSink + ?Sized>(bits: u16, rs1: RegUnit, imm: i64, rd: RegUnit, sink: &mut CS) {
let bits = u32::from(bits);
let opcode5 = bits & 0x1f;
let funct3 = (bits >> 5) & 0x7;
let rs1 = u32::from(rs1) & 0x1f;
let rd = u32::from(rd) & 0x1f;
// 0-6: opcode
let mut i = 0x3;
i |= opcode5 << 2;
i |= rd << 7;
i |= funct3 << 12;
i |= rs1 << 15;
i |= (imm << 20) as u32;
sink.put4(i);
}
/// U-type instructions.
///
/// 31 11 6
/// imm rd opcode
/// 12 7 0
///
/// Encoding bits: `opcode[6:2] | (funct3 << 5)`
fn put_u<CS: CodeSink + ?Sized>(bits: u16, imm: i64, rd: RegUnit, sink: &mut CS) {
let bits = u32::from(bits);
let opcode5 = bits & 0x1f;
let rd = u32::from(rd) & 0x1f;
// 0-6: opcode
let mut i = 0x3;
i |= opcode5 << 2;
i |= rd << 7;
i |= imm as u32 & 0xfffff000;
sink.put4(i);
}
/// SB-type branch instructions.
///
/// 31 24 19 14 11 6
/// imm rs2 rs1 funct3 imm opcode
/// 25 20 15 12 7 0
///
/// Encoding bits: `opcode[6:2] | (funct3 << 5)`
fn put_sb<CS: CodeSink + ?Sized>(bits: u16, imm: i64, rs1: RegUnit, rs2: RegUnit, sink: &mut CS) {
let bits = u32::from(bits);
let opcode5 = bits & 0x1f;
let funct3 = (bits >> 5) & 0x7;
let rs1 = u32::from(rs1) & 0x1f;
let rs2 = u32::from(rs2) & 0x1f;
debug_assert!(is_signed_int(imm, 13, 1), "SB out of range {:#x}", imm);
let imm = imm as u32;
// 0-6: opcode
let mut i = 0x3;
i |= opcode5 << 2;
i |= funct3 << 12;
i |= rs1 << 15;
i |= rs2 << 20;
// The displacement is completely hashed up.
i |= ((imm >> 11) & 0x1) << 7;
i |= ((imm >> 1) & 0xf) << 8;
i |= ((imm >> 5) & 0x3f) << 25;
i |= ((imm >> 12) & 0x1) << 31;
sink.put4(i);
}
/// UJ-type jump instructions.
///
/// 31 11 6
/// imm rd opcode
/// 12 7 0
///
/// Encoding bits: `opcode[6:2]`
fn put_uj<CS: CodeSink + ?Sized>(bits: u16, imm: i64, rd: RegUnit, sink: &mut CS) {
let bits = u32::from(bits);
let opcode5 = bits & 0x1f;
let rd = u32::from(rd) & 0x1f;
debug_assert!(is_signed_int(imm, 21, 1), "UJ out of range {:#x}", imm);
let imm = imm as u32;
// 0-6: opcode
let mut i = 0x3;
i |= opcode5 << 2;
i |= rd << 7;
// The displacement is completely hashed up.
i |= imm & 0xff000;
i |= ((imm >> 11) & 0x1) << 20;
i |= ((imm >> 1) & 0x3ff) << 21;
i |= ((imm >> 20) & 0x1) << 31;
sink.put4(i);
}

View File

@@ -1,18 +0,0 @@
//! Encoding tables for RISC-V.
use super::registers::*;
use crate::ir;
use crate::isa;
use crate::isa::constraints::*;
use crate::isa::enc_tables::*;
use crate::isa::encoding::{base_size, RecipeSizing};
use crate::predicates;
// Include the generated encoding tables:
// - `LEVEL1_RV32`
// - `LEVEL1_RV64`
// - `LEVEL2`
// - `ENCLIST`
// - `INFO`
include!(concat!(env!("OUT_DIR"), "/encoding-riscv.rs"));
include!(concat!(env!("OUT_DIR"), "/legalize-riscv.rs"));

View File

@@ -1,304 +0,0 @@
//! RISC-V Instruction Set Architecture.
mod abi;
mod binemit;
mod enc_tables;
mod registers;
pub mod settings;
use super::super::settings as shared_settings;
#[cfg(feature = "testing_hooks")]
use crate::binemit::CodeSink;
use crate::binemit::{emit_function, MemoryCodeSink};
use crate::ir;
use crate::isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings};
use crate::isa::Builder as IsaBuilder;
use crate::isa::{EncInfo, RegClass, RegInfo, TargetIsa};
use crate::regalloc;
use alloc::{borrow::Cow, boxed::Box, vec::Vec};
use core::any::Any;
use core::fmt;
use core::hash::{Hash, Hasher};
use target_lexicon::{PointerWidth, Triple};
#[allow(dead_code)]
struct Isa {
triple: Triple,
shared_flags: shared_settings::Flags,
isa_flags: settings::Flags,
cpumode: &'static [shared_enc_tables::Level1Entry<u16>],
}
/// Get an ISA builder for creating RISC-V targets.
pub fn isa_builder(triple: Triple) -> IsaBuilder {
IsaBuilder {
triple,
setup: settings::builder(),
constructor: isa_constructor,
}
}
fn isa_constructor(
triple: Triple,
shared_flags: shared_settings::Flags,
builder: shared_settings::Builder,
) -> Box<dyn TargetIsa> {
let level1 = match triple.pointer_width().unwrap() {
PointerWidth::U16 => panic!("16-bit RISC-V unrecognized"),
PointerWidth::U32 => &enc_tables::LEVEL1_RV32[..],
PointerWidth::U64 => &enc_tables::LEVEL1_RV64[..],
};
Box::new(Isa {
triple,
isa_flags: settings::Flags::new(&shared_flags, builder),
shared_flags,
cpumode: level1,
})
}
impl TargetIsa for Isa {
fn name(&self) -> &'static str {
"riscv"
}
fn triple(&self) -> &Triple {
&self.triple
}
fn flags(&self) -> &shared_settings::Flags {
&self.shared_flags
}
fn isa_flags(&self) -> Vec<shared_settings::Value> {
self.isa_flags.iter().collect()
}
fn hash_all_flags(&self, mut hasher: &mut dyn Hasher) {
self.shared_flags.hash(&mut hasher);
self.isa_flags.hash(&mut hasher);
}
fn register_info(&self) -> RegInfo {
registers::INFO.clone()
}
fn encoding_info(&self) -> EncInfo {
enc_tables::INFO.clone()
}
fn legal_encodings<'a>(
&'a self,
func: &'a ir::Function,
inst: &'a ir::InstructionData,
ctrl_typevar: ir::Type,
) -> Encodings<'a> {
lookup_enclist(
ctrl_typevar,
inst,
func,
self.cpumode,
&enc_tables::LEVEL2[..],
&enc_tables::ENCLISTS[..],
&enc_tables::LEGALIZE_ACTIONS[..],
&enc_tables::RECIPE_PREDICATES[..],
&enc_tables::INST_PREDICATES[..],
self.isa_flags.predicate_view(),
)
}
fn legalize_signature(&self, sig: &mut Cow<ir::Signature>, current: bool) {
abi::legalize_signature(sig, &self.triple, &self.isa_flags, current)
}
fn regclass_for_abi_type(&self, ty: ir::Type) -> RegClass {
abi::regclass_for_abi_type(ty)
}
fn allocatable_registers(&self, func: &ir::Function) -> regalloc::RegisterSet {
abi::allocatable_registers(func, &self.isa_flags)
}
#[cfg(feature = "testing_hooks")]
fn emit_inst(
&self,
func: &ir::Function,
inst: ir::Inst,
divert: &mut regalloc::RegDiversions,
sink: &mut dyn CodeSink,
) {
binemit::emit_inst(func, inst, divert, sink, self)
}
fn emit_function_to_memory(&self, func: &ir::Function, sink: &mut MemoryCodeSink) {
emit_function(func, binemit::emit_inst, sink, self)
}
fn unsigned_add_overflow_condition(&self) -> ir::condcodes::IntCC {
unimplemented!()
}
fn unsigned_sub_overflow_condition(&self) -> ir::condcodes::IntCC {
unimplemented!()
}
fn as_any(&self) -> &dyn Any {
self as &dyn Any
}
}
#[cfg(test)]
mod tests {
use crate::ir::{immediates, types};
use crate::ir::{Function, InstructionData, Opcode};
use crate::isa;
use crate::settings::{self, Configurable};
use alloc::string::{String, ToString};
use core::str::FromStr;
use target_lexicon::triple;
fn encstr(isa: &dyn isa::TargetIsa, enc: Result<isa::Encoding, isa::Legalize>) -> String {
match enc {
Ok(e) => isa.encoding_info().display(e).to_string(),
Err(_) => "no encoding".to_string(),
}
}
#[test]
fn test_64bitenc() {
let shared_builder = settings::builder();
let shared_flags = settings::Flags::new(shared_builder);
let isa = isa::lookup(triple!("riscv64"))
.unwrap()
.finish(shared_flags);
let mut func = Function::new();
let block = func.dfg.make_block();
let arg64 = func.dfg.append_block_param(block, types::I64);
let arg32 = func.dfg.append_block_param(block, types::I32);
// Try to encode iadd_imm.i64 v1, -10.
let inst64 = InstructionData::BinaryImm64 {
opcode: Opcode::IaddImm,
arg: arg64,
imm: immediates::Imm64::new(-10),
};
// ADDI is I/0b00100
assert_eq!(
encstr(&*isa, isa.encode(&func, &inst64, types::I64)),
"Ii#04"
);
// Try to encode iadd_imm.i64 v1, -10000.
let inst64_large = InstructionData::BinaryImm64 {
opcode: Opcode::IaddImm,
arg: arg64,
imm: immediates::Imm64::new(-10000),
};
// Immediate is out of range for ADDI.
assert!(isa.encode(&func, &inst64_large, types::I64).is_err());
// Create an iadd_imm.i32 which is encodable in RV64.
let inst32 = InstructionData::BinaryImm64 {
opcode: Opcode::IaddImm,
arg: arg32,
imm: immediates::Imm64::new(10),
};
// ADDIW is I/0b00110
assert_eq!(
encstr(&*isa, isa.encode(&func, &inst32, types::I32)),
"Ii#06"
);
}
// Same as above, but for RV32.
#[test]
fn test_32bitenc() {
let shared_builder = settings::builder();
let shared_flags = settings::Flags::new(shared_builder);
let isa = isa::lookup(triple!("riscv32"))
.unwrap()
.finish(shared_flags);
let mut func = Function::new();
let block = func.dfg.make_block();
let arg64 = func.dfg.append_block_param(block, types::I64);
let arg32 = func.dfg.append_block_param(block, types::I32);
// Try to encode iadd_imm.i64 v1, -10.
let inst64 = InstructionData::BinaryImm64 {
opcode: Opcode::IaddImm,
arg: arg64,
imm: immediates::Imm64::new(-10),
};
// In 32-bit mode, an i64 bit add should be narrowed.
assert!(isa.encode(&func, &inst64, types::I64).is_err());
// Try to encode iadd_imm.i64 v1, -10000.
let inst64_large = InstructionData::BinaryImm64 {
opcode: Opcode::IaddImm,
arg: arg64,
imm: immediates::Imm64::new(-10000),
};
// In 32-bit mode, an i64 bit add should be narrowed.
assert!(isa.encode(&func, &inst64_large, types::I64).is_err());
// Create an iadd_imm.i32 which is encodable in RV32.
let inst32 = InstructionData::BinaryImm64 {
opcode: Opcode::IaddImm,
arg: arg32,
imm: immediates::Imm64::new(10),
};
// ADDI is I/0b00100
assert_eq!(
encstr(&*isa, isa.encode(&func, &inst32, types::I32)),
"Ii#04"
);
// Create an imul.i32 which is encodable in RV32, but only when use_m is true.
let mul32 = InstructionData::Binary {
opcode: Opcode::Imul,
args: [arg32, arg32],
};
assert!(isa.encode(&func, &mul32, types::I32).is_err());
}
#[test]
fn test_rv32m() {
let shared_builder = settings::builder();
let shared_flags = settings::Flags::new(shared_builder);
// Set the supports_m stting which in turn enables the use_m predicate that unlocks
// encodings for imul.
let mut isa_builder = isa::lookup(triple!("riscv32")).unwrap();
isa_builder.enable("supports_m").unwrap();
let isa = isa_builder.finish(shared_flags);
let mut func = Function::new();
let block = func.dfg.make_block();
let arg32 = func.dfg.append_block_param(block, types::I32);
// Create an imul.i32 which is encodable in RV32M.
let mul32 = InstructionData::Binary {
opcode: Opcode::Imul,
args: [arg32, arg32],
};
assert_eq!(
encstr(&*isa, isa.encode(&func, &mul32, types::I32)),
"R#10c"
);
}
}
impl fmt::Display for Isa {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}\n{}", self.shared_flags, self.isa_flags)
}
}

View File

@@ -1,50 +0,0 @@
//! RISC-V register descriptions.
use crate::isa::registers::{RegBank, RegClass, RegClassData, RegInfo, RegUnit};
include!(concat!(env!("OUT_DIR"), "/registers-riscv.rs"));
#[cfg(test)]
mod tests {
use super::{FPR, GPR, INFO};
use crate::isa::RegUnit;
use alloc::string::{String, ToString};
#[test]
fn unit_encodings() {
assert_eq!(INFO.parse_regunit("x0"), Some(0));
assert_eq!(INFO.parse_regunit("x31"), Some(31));
assert_eq!(INFO.parse_regunit("f0"), Some(32));
assert_eq!(INFO.parse_regunit("f31"), Some(63));
assert_eq!(INFO.parse_regunit("x32"), None);
assert_eq!(INFO.parse_regunit("f32"), None);
}
#[test]
fn unit_names() {
fn uname(ru: RegUnit) -> String {
INFO.display_regunit(ru).to_string()
}
assert_eq!(uname(0), "%x0");
assert_eq!(uname(1), "%x1");
assert_eq!(uname(31), "%x31");
assert_eq!(uname(32), "%f0");
assert_eq!(uname(33), "%f1");
assert_eq!(uname(63), "%f31");
assert_eq!(uname(64), "%INVALID64");
}
#[test]
fn classes() {
assert!(GPR.contains(GPR.unit(0)));
assert!(GPR.contains(GPR.unit(31)));
assert!(!FPR.contains(GPR.unit(0)));
assert!(!FPR.contains(GPR.unit(31)));
assert!(!GPR.contains(FPR.unit(0)));
assert!(!GPR.contains(FPR.unit(31)));
assert!(FPR.contains(FPR.unit(0)));
assert!(FPR.contains(FPR.unit(31)));
}
}

View File

@@ -1,56 +0,0 @@
//! RISC-V Settings.
use crate::settings::{self, detail, Builder, Value};
use core::fmt;
// Include code generated by `cranelift-codegen/meta/src/gen_settings.rs`. This file contains a
// public `Flags` struct with an impl for all of the settings defined in
// `cranelift-codegen/meta/src/isa/riscv/mod.rs`.
include!(concat!(env!("OUT_DIR"), "/settings-riscv.rs"));
#[cfg(test)]
mod tests {
use super::{builder, Flags};
use crate::settings::{self, Configurable};
use alloc::string::ToString;
#[test]
fn display_default() {
let shared = settings::Flags::new(settings::builder());
let b = builder();
let f = Flags::new(&shared, b);
assert_eq!(
f.to_string(),
"[riscv]\n\
supports_m = false\n\
supports_a = false\n\
supports_f = false\n\
supports_d = false\n\
enable_m = true\n\
enable_e = false\n"
);
// Predicates are not part of the Display output.
assert_eq!(f.full_float(), false);
}
#[test]
fn predicates() {
let mut sb = settings::builder();
sb.set("enable_simd", "true").unwrap();
let shared = settings::Flags::new(sb);
let mut b = builder();
b.enable("supports_f").unwrap();
b.enable("supports_d").unwrap();
let f = Flags::new(&shared, b);
assert_eq!(f.full_float(), true);
let mut sb = settings::builder();
sb.set("enable_simd", "false").unwrap();
let shared = settings::Flags::new(sb);
let mut b = builder();
b.enable("supports_f").unwrap();
b.enable("supports_d").unwrap();
let f = Flags::new(&shared, b);
assert_eq!(f.full_float(), false);
}
}

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More