Bug 1547682: Bump Cranelift to cc216b46b35a797d03c0f3e8b16a2096f1c6db61; r=lth

Differential Revision: https://phabricator.services.mozilla.com/D29343
This commit is contained in:
Benjamin Bouvier 2019-04-30 14:22:41 +02:00
parent ebd6889717
commit f1c302ad6a
65 changed files with 5283 additions and 1442 deletions

View File

@ -24,7 +24,7 @@ replace-with = "vendored-sources"
[source."https://github.com/CraneStation/Cranelift"]
git = "https://github.com/CraneStation/Cranelift"
rev = "538a0662bf90a1daa9921c10f34827ace134abf1"
rev = "cc216b46b35a797d03c0f3e8b16a2096f1c6db61"
replace-with = "vendored-sources"
[source.vendored-sources]

46
Cargo.lock generated
View File

@ -166,8 +166,8 @@ name = "baldrdash"
version = "0.1.0"
dependencies = [
"bindgen 0.49.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cranelift-codegen 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
"cranelift-wasm 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
"cranelift-codegen 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
"cranelift-wasm 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
"env_logger 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"target-lexicon 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -587,19 +587,19 @@ dependencies = [
[[package]]
name = "cranelift-bforest"
version = "0.30.0"
source = "git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1#538a0662bf90a1daa9921c10f34827ace134abf1"
source = "git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61#cc216b46b35a797d03c0f3e8b16a2096f1c6db61"
dependencies = [
"cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
"cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
]
[[package]]
name = "cranelift-codegen"
version = "0.30.0"
source = "git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1#538a0662bf90a1daa9921c10f34827ace134abf1"
source = "git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61#cc216b46b35a797d03c0f3e8b16a2096f1c6db61"
dependencies = [
"cranelift-bforest 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
"cranelift-codegen-meta 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
"cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
"cranelift-bforest 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
"cranelift-codegen-meta 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
"cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
"failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -609,22 +609,22 @@ dependencies = [
[[package]]
name = "cranelift-codegen-meta"
version = "0.30.0"
source = "git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1#538a0662bf90a1daa9921c10f34827ace134abf1"
source = "git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61#cc216b46b35a797d03c0f3e8b16a2096f1c6db61"
dependencies = [
"cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
"cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
]
[[package]]
name = "cranelift-entity"
version = "0.30.0"
source = "git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1#538a0662bf90a1daa9921c10f34827ace134abf1"
source = "git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61#cc216b46b35a797d03c0f3e8b16a2096f1c6db61"
[[package]]
name = "cranelift-frontend"
version = "0.30.0"
source = "git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1#538a0662bf90a1daa9921c10f34827ace134abf1"
source = "git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61#cc216b46b35a797d03c0f3e8b16a2096f1c6db61"
dependencies = [
"cranelift-codegen 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
"cranelift-codegen 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"target-lexicon 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -632,12 +632,12 @@ dependencies = [
[[package]]
name = "cranelift-wasm"
version = "0.30.0"
source = "git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1#538a0662bf90a1daa9921c10f34827ace134abf1"
source = "git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61#cc216b46b35a797d03c0f3e8b16a2096f1c6db61"
dependencies = [
"cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"cranelift-codegen 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
"cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
"cranelift-frontend 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)",
"cranelift-codegen 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
"cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
"cranelift-frontend 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)",
"failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -3659,12 +3659,12 @@ dependencies = [
"checksum core-text 13.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f3f46450d6f2397261af420b4ccce23807add2e45fa206410a03d66fb7f050ae"
"checksum cose 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "72fa26cb151d3ae4b70f63d67d0fed57ce04220feafafbae7f503bef7aae590d"
"checksum cose-c 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "49726015ab0ca765144fcca61e4a7a543a16b795a777fa53f554da2fffff9a94"
"checksum cranelift-bforest 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)" = "<none>"
"checksum cranelift-codegen 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)" = "<none>"
"checksum cranelift-codegen-meta 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)" = "<none>"
"checksum cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)" = "<none>"
"checksum cranelift-frontend 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)" = "<none>"
"checksum cranelift-wasm 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=538a0662bf90a1daa9921c10f34827ace134abf1)" = "<none>"
"checksum cranelift-bforest 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)" = "<none>"
"checksum cranelift-codegen 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)" = "<none>"
"checksum cranelift-codegen-meta 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)" = "<none>"
"checksum cranelift-entity 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)" = "<none>"
"checksum cranelift-frontend 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)" = "<none>"
"checksum cranelift-wasm 0.30.0 (git+https://github.com/CraneStation/Cranelift?rev=cc216b46b35a797d03c0f3e8b16a2096f1c6db61)" = "<none>"
"checksum crc 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bd5d02c0aac6bd68393ed69e00bbc2457f3e89075c6349db7189618dc4ddc1d7"
"checksum crossbeam-deque 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f739f8c5363aca78cfb059edf753d8f0d36908c348f3d8d1503f03d8b75d9cf3"
"checksum crossbeam-deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fe8153ef04a7594ded05b427ffad46ddeaf22e63fd48d42b3e1e3bb4db07cae7"

View File

@ -64,8 +64,8 @@ packed_simd = { git = "https://github.com/hsivonen/packed_simd", branch = "rust_
[patch.crates-io.cranelift-codegen]
git = "https://github.com/CraneStation/Cranelift"
rev = "538a0662bf90a1daa9921c10f34827ace134abf1"
rev = "cc216b46b35a797d03c0f3e8b16a2096f1c6db61"
[patch.crates-io.cranelift-wasm]
git = "https://github.com/CraneStation/Cranelift"
rev = "538a0662bf90a1daa9921c10f34827ace134abf1"
rev = "cc216b46b35a797d03c0f3e8b16a2096f1c6db61"

View File

@ -1 +1 @@
{"files":{"Cargo.toml":"253c80832ab598570d604ae8a8108ea9835b8eef8d9b9645408ed025ce3b574a","LICENSE":"268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5","README.md":"b123f056d0d458396679c5f7f2a16d2762af0258fcda4ac14b6655a95e5a0022","src/cdsl/formats.rs":"98ab61698ad4c1fb81541b1820bd1c1561810bdcff2796dec541c98b4b1901d7","src/cdsl/inst.rs":"d5130c1a36a4e33d1374f9867119c3f2d79c384f12afc12e7b7b4518cf1f74b3","src/cdsl/isa.rs":"dd52d35fa963494b7da892a4a04a4f9978079bb2d86c6af4273a8dfdb82bdf51","src/cdsl/mod.rs":"2d2e216f8c3a81978a5113213559a5ab659bc112b6194cbe08a752313aad7f46","src/cdsl/operands.rs":"cc579fd543e36cf8e82938db331c145b77e29855ee2aa8c5dd949678f980796d","src/cdsl/regs.rs":"b99f24c3ecb46691625dc177b4e18d53e02265bc85a2f827a8d18381fe8f39bb","src/cdsl/settings.rs":"4ddeadf1542cc2ddec0f9e6c22d1637050da519586cd9fec0243c3eab9619f82","src/cdsl/type_inference.rs":"8aedb2e99dee299abbc327ce3a604d48f161580776225d2438a54bbec5b725fe","src/cdsl/types.rs":"4cc1f20eb8383fdee6a9e7ca0f7758e563a4fb715056b5edbd4db72f8dfd471b","src/cdsl/typevar.rs":"605786e2bf367879da500327fc003a4d2a663259c2dee76c87e5b99b6f6331ee","src/constant_hash.rs":"b8acd3f8712a4999819d9d9beced2938d9940a5748ba016c182f1132d97eefab","src/error.rs":"5110a4e3c1e97396ba02d9f5abbb8af4b586f0cc4d33a5c2473f1718cc4bef05","src/gen_inst.rs":"795d30588a5e87e69f3510606c9aee1b8670d5aee5f3d54067f6c7508a67e565","src/gen_registers.rs":"a544a2b91fafe08639e39e50bea0892fda89fe2f6eaf111b2d5f3e98e4d07b86","src/gen_settings.rs":"77ee330b85a255c49247222f4d071da839b0520eddd3dc561867f7ae84e199ac","src/gen_types.rs":"3935da6c6a53f9332e06f74bc3a46270656b4d4231ad28ed2648d7b1d2774e90","src/isa/arm32/mod.rs":"6ed3be790b28d3115421be282a06b8c376295e1776c4b77243443799015ab70d","src/isa/arm64/mod.rs":"5c46082f68c958e83ffc636de893e2ff49fd8ce21ef357f359837ca48a60eaa5","src/isa/mod.rs":"fce60d19dd3c099ebee3ac5ae64a2bee363f13da9ff5a4960d3c1a0bee71d29a","src/isa/riscv/mod.rs":"785f0da2b04458793cb2d493c5e1eeb7ea339bc721df76dda69db3b49bcdfd27","src/isa/x86/instructions.rs":"bd6b02ccc79984ed4a5615ae3b20a60a4da3777495b72f771762a886f87d2335","src/isa/x86/mod.rs":"ba7c11aedb190f58432226a6dec8a125b385cc18fd2f70c46703d077904a3112","src/lib.rs":"99aec646c7b756c01544a181e9b56ba14fccfb2bce205a8c1f63fb31905630ca","src/shared/entities.rs":"e7a44d5f621d726479c3812384e78dd25e8c063d074c64d0908b3667e7d28af1","src/shared/formats.rs":"20908b1048c5e71a185de6b6ded79cdff2c26ddb38ba7b134b7a27f37e8324f3","src/shared/immediates.rs":"1e64836f82045d05da7c151e60cf1e66666af3e0c19179de3f37e72dc81e1bbd","src/shared/instructions.rs":"2a0993279b3529b2c31aa8e83589636104a005351463ec2d3b81b5ffe569d276","src/shared/mod.rs":"696c166d3c19bd84604583a7b8d7ec4f6671622ed581bfce8bee375d02067cbe","src/shared/settings.rs":"bad2dc0e1d71ee6fec6418aa79234296aa918e499a1671c3e5c1d4b0d84b6f49","src/shared/types.rs":"158d73840185e6aa8385463bbf6568efdda0c8de8284cf6b4e565f425ec5d921","src/srcgen.rs":"ad39143ae50f3b19f18a43131bdd3308852c70a9e532cc99f97624e7380b00d8","src/unique_table.rs":"bec9d48ee040216a7c9deab6d2c5050d7ce70e38482cc8957105fd7cbca3c33a"},"package":null}
{"files":{"Cargo.toml":"253c80832ab598570d604ae8a8108ea9835b8eef8d9b9645408ed025ce3b574a","LICENSE":"268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5","README.md":"b123f056d0d458396679c5f7f2a16d2762af0258fcda4ac14b6655a95e5a0022","src/cdsl/ast.rs":"3406fec29fe80c979dc938a0fddda354ed96611276068b951e2b5b23ad018338","src/cdsl/cpu_modes.rs":"57c40621115a58faa7af1c729ecaf8cda01c2b143008bde6d8c70885e7c3fd75","src/cdsl/formats.rs":"858f0a6ea62580a2812a8f2ff68dd25298b6ea4c2413657c41630a044492ea0d","src/cdsl/inst.rs":"ed31c12876ab384d0ddd2f6395a64bf3a2f847a4864147b4bb52308cdeef4980","src/cdsl/isa.rs":"201e57e580defead2d1eac82762d629de3882a6bc1c53a9726025a1eef7fd752","src/cdsl/mod.rs":"9345537d57736a00e2f47aeaab50107c481edeccaa873b2c157b0a05d02e3124","src/cdsl/operands.rs":"5217258f2c4911b5be29ca020e9f7460b85182a74d485e027a3bd8336fbf2891","src/cdsl/regs.rs":"b99f24c3ecb46691625dc177b4e18d53e02265bc85a2f827a8d18381fe8f39bb","src/cdsl/settings.rs":"885ea67d5d8346caa4937a4693d59d8eadf72c414d86b316f58712c0d3c9778b","src/cdsl/type_inference.rs":"2771631701c150e077c5dcf705c8ae8705944d86ab945ae9e7adc82f3ca5447a","src/cdsl/types.rs":"4cc1f20eb8383fdee6a9e7ca0f7758e563a4fb715056b5edbd4db72f8dfd471b","src/cdsl/typevar.rs":"bd63d403e1ab12130c484100e5bc54b32120f27163a38cee3f546f56b9819325","src/cdsl/xform.rs":"87aeb183ef4282e05c442046df4028d771be63acc3e0250def17cdca7a953477","src/constant_hash.rs":"b8acd3f8712a4999819d9d9beced2938d9940a5748ba016c182f1132d97eefab","src/error.rs":"5110a4e3c1e97396ba02d9f5abbb8af4b586f0cc4d33a5c2473f1718cc4bef05","src/gen_inst.rs":"971104519426da5eebeae2118439e2f7fe011ac0ee54f470f6311a12ae1045b9","src/gen_legalizer.rs":"268e9922f2742c611cace9763d3904f9c113f6869ee9588ed3d19b53f6d57d56","src/gen_registers.rs":"a544a2b91fafe08639e39e50bea0892fda89fe2f6eaf111b2d5f3e98e4d07b86","src/gen_settings.rs":"4469bf496f9539835fce3cd9743ac0fbc270b3029b0e6c949f406e790685199c","src/gen_types.rs":"3935da6c6a53f9332e06f74bc3a46270656b4d4231ad28ed2648d7b1d2774e90","src/isa/arm32/mod.rs":"39c168a2fc979ee1ccaddf303d590f6b50019f1a0733a426c81e9bc5d57ee90c","src/isa/arm64/mod.rs":"335e238ff1a61026c88c11b55585030960c3938cccf901ffb7d1ce3c0fa6db41","src/isa/mod.rs":"fce60d19dd3c099ebee3ac5ae64a2bee363f13da9ff5a4960d3c1a0bee71d29a","src/isa/riscv/mod.rs":"589d6517b3780c3d08379e9f6c1f46d3bfbb754b9b0a455ec041780838f01d6d","src/isa/x86/instructions.rs":"bd6b02ccc79984ed4a5615ae3b20a60a4da3777495b72f771762a886f87d2335","src/isa/x86/legalize.rs":"67316297edffbb2bdf0b19ff353a634f1f609a60bd2858410c9140ad0d7ca2bf","src/isa/x86/mod.rs":"cbc037fef238b239b7244a6e766d14dca23999161645c8e2ef91dd7e0a131c67","src/lib.rs":"8c9364c6fce73c158abfb7c88ecff01dc608a05c250e89df9bec3082773cde6d","src/shared/entities.rs":"e7a44d5f621d726479c3812384e78dd25e8c063d074c64d0908b3667e7d28af1","src/shared/formats.rs":"20908b1048c5e71a185de6b6ded79cdff2c26ddb38ba7b134b7a27f37e8324f3","src/shared/immediates.rs":"ac3653ce7f83372833136d28e1809d23b1dc65e7de29ffa26b5e381fcb94d25b","src/shared/instructions.rs":"2a0993279b3529b2c31aa8e83589636104a005351463ec2d3b81b5ffe569d276","src/shared/legalize.rs":"6fc4bebca916bf68b3289f4750ed11148365181b04d497d1d1c1913db7a6fe19","src/shared/mod.rs":"7029cb0c5f7ad59cb2a7ea5f8b91b0fbb363d0704df208dcf8c069da6bfa4c13","src/shared/settings.rs":"bad2dc0e1d71ee6fec6418aa79234296aa918e499a1671c3e5c1d4b0d84b6f49","src/shared/types.rs":"158d73840185e6aa8385463bbf6568efdda0c8de8284cf6b4e565f425ec5d921","src/srcgen.rs":"79fee2f603b33f76f7c9c8b9452c745a363d732c40c0814d84001ff3ef805677","src/unique_table.rs":"90b7203b29241a1ede70f0a3e50d96799e0b41d8f7455170d6ffb127f87f3cc3"},"package":null}

View File

@ -0,0 +1,653 @@
use crate::cdsl::formats::FormatRegistry;
use crate::cdsl::inst::{BoundInstruction, Instruction, InstructionPredicate};
use crate::cdsl::operands::{OperandKind, OperandKindFields};
use crate::cdsl::types::{LaneType, ValueType};
use crate::cdsl::typevar::{TypeSetBuilder, TypeVar};
use cranelift_entity::{entity_impl, PrimaryMap};
use std::fmt;
pub enum Expr {
Var(VarIndex),
Literal(Literal),
Apply(Apply),
}
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(),
Expr::Apply(a) => a.to_rust_code(var_pool),
}
}
}
/// An AST definition associates a set of variables with the values produced by an expression.
pub 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)
.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 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 get_mut(&mut self, index: DefIndex) -> &mut Def {
self.pool.get_mut(index).unwrap()
}
pub fn next_index(&self) -> DefIndex {
self.pool.next_key()
}
pub fn create(&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 struct DefIndex(u32);
entity_impl!(DefIndex);
#[derive(Debug, Clone)]
enum LiteralValue {
/// 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(&'static str),
/// A bitwise value of an immediate operand, used for bitwise exact floating point constants.
Bits(u64),
/// A value of an integer immediate operand.
Int(i64),
}
#[derive(Clone)]
pub struct Literal {
kind: OperandKind,
value: LiteralValue,
}
impl fmt::Debug for Literal {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(
fmt,
"Literal(kind={}, value={:?})",
self.kind.name, self.value
)
}
}
impl Literal {
pub fn enumerator_for(kind: &OperandKind, value: &'static str) -> Self {
if let OperandKindFields::ImmEnum(values) = &kind.fields {
assert!(
values.get(value).is_some(),
format!(
"nonexistent value '{}' in enumeration '{}'",
value, kind.name
)
);
} else {
panic!("enumerator is for enum values");
}
Self {
kind: kind.clone(),
value: LiteralValue::Enumerator(value),
}
}
pub fn bits(kind: &OperandKind, bits: u64) -> Self {
match kind.fields {
OperandKindFields::ImmValue => {}
_ => panic!("bits_of is for immediate scalar types"),
}
Self {
kind: kind.clone(),
value: LiteralValue::Bits(bits),
}
}
pub fn constant(kind: &OperandKind, value: i64) -> Self {
match kind.fields {
OperandKindFields::ImmValue => {}
_ => panic!("bits_of is for immediate scalar types"),
}
Self {
kind: kind.clone(),
value: LiteralValue::Int(value),
}
}
pub fn to_rust_code(&self) -> String {
let maybe_values = match &self.kind.fields {
OperandKindFields::ImmEnum(values) => Some(values),
OperandKindFields::ImmValue => None,
_ => panic!("impossible per construction"),
};
match self.value {
LiteralValue::Enumerator(value) => {
format!("{}::{}", self.kind.rust_type, maybe_values.unwrap()[value])
}
LiteralValue::Bits(bits) => format!("{}::with_bits({:#x})", self.kind.rust_type, bits),
LiteralValue::Int(val) => val.to_string(),
}
}
}
#[derive(Clone, Copy, Debug)]
pub 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 struct Var {
pub name: &'static str,
/// 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: &'static str) -> 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(),
format!("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.into()
}
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 struct VarIndex(u32);
entity_impl!(VarIndex);
pub 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: &'static str) -> VarIndex {
self.pool.push(Var::new(name))
}
}
pub enum ApplyTarget {
Inst(Instruction),
Bound(BoundInstruction),
}
impl ApplyTarget {
pub fn inst(&self) -> &Instruction {
match &self {
ApplyTarget::Inst(inst) => inst,
ApplyTarget::Bound(bound_inst) => &bound_inst.inst,
}
}
}
impl Into<ApplyTarget> for &Instruction {
fn into(self) -> ApplyTarget {
ApplyTarget::Inst(self.clone())
}
}
impl Into<ApplyTarget> for BoundInstruction {
fn into(self) -> ApplyTarget {
ApplyTarget::Bound(self)
}
}
pub fn bind(target: impl Into<ApplyTarget>, lane_type: impl Into<LaneType>) -> BoundInstruction {
let value_type = ValueType::from(lane_type.into());
let (inst, value_types) = match target.into() {
ApplyTarget::Inst(inst) => (inst, vec![value_type]),
ApplyTarget::Bound(bound_inst) => {
let mut new_value_types = bound_inst.value_types;
new_value_types.push(value_type);
(bound_inst.inst, new_value_types)
}
};
match &inst.polymorphic_info {
Some(poly) => {
assert!(
value_types.len() <= 1 + poly.other_typevars.len(),
format!("trying to bind too many types for {}", inst.name)
);
}
None => {
panic!(format!(
"trying to bind a type for {} which is not a polymorphic instruction",
inst.name
));
}
}
BoundInstruction { inst, value_types }
}
/// 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 struct Apply {
pub inst: Instruction,
pub args: Vec<Expr>,
pub value_types: Vec<ValueType>,
}
impl Apply {
pub fn new(target: ApplyTarget, args: Vec<Expr>) -> Self {
let (inst, value_types) = match target.into() {
ApplyTarget::Inst(inst) => (inst, Vec::new()),
ApplyTarget::Bound(bound_inst) => (bound_inst.inst, bound_inst.value_types),
};
// Basic check on number of arguments.
assert!(
inst.operands_in.len() == args.len(),
format!("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];
assert!(
op.kind.name == literal.kind.name,
format!(
"Passing literal of kind {} to field of wrong kind {}",
literal.kind.name, op.kind.name
)
);
}
}
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)
}
fn to_rust_code(&self, var_pool: &VarPool) -> String {
let args = self
.args
.iter()
.map(|arg| arg.to_rust_code(var_pool))
.collect::<Vec<_>>()
.join(", ");
format!("{}({})", self.inst.name, args)
}
fn inst_predicate(
&self,
format_registry: &FormatRegistry,
var_pool: &VarPool,
) -> InstructionPredicate {
let iform = format_registry.get(self.inst.format);
let mut pred = InstructionPredicate::new();
for (format_field, &op_num) in iform.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(
&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,
format_registry: &FormatRegistry,
var_pool: &VarPool,
) -> InstructionPredicate {
let mut pred = self.inst_predicate(format_registry, 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: &Vec<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 enum DummyExpr {
Var(DummyVar),
Literal(Literal),
Apply(ApplyTarget, Vec<DummyExpr>),
}
#[derive(Clone)]
pub struct DummyVar {
pub name: &'static str,
}
impl Into<DummyExpr> for DummyVar {
fn into(self) -> DummyExpr {
DummyExpr::Var(self)
}
}
impl Into<DummyExpr> for Literal {
fn into(self) -> DummyExpr {
DummyExpr::Literal(self)
}
}
pub fn var(name: &'static str) -> DummyVar {
DummyVar { name }
}
pub struct DummyDef {
pub expr: DummyExpr,
pub defined_vars: Vec<DummyVar>,
}
pub struct ExprBuilder {
expr: DummyExpr,
}
impl ExprBuilder {
pub fn apply(inst: ApplyTarget, 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,
}
}
}
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(bind($inst, $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())
}
}

View File

@ -0,0 +1,84 @@
use crate::cdsl::types::LaneType;
use crate::cdsl::xform::{TransformGroup, TransformGroupIndex, TransformGroups};
use std::collections::{HashMap, HashSet};
use std::iter::FromIterator;
pub struct CpuMode {
_name: &'static str,
default_legalize: Option<TransformGroupIndex>,
monomorphic_legalize: Option<TransformGroupIndex>,
typed_legalize: HashMap<String, TransformGroupIndex>,
}
impl CpuMode {
pub fn new(name: &'static str) -> Self {
Self {
_name: name,
default_legalize: None,
monomorphic_legalize: None,
typed_legalize: HashMap::new(),
}
}
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_type(&mut self, lane_type: impl Into<LaneType>, group: &TransformGroup) {
assert!(self
.typed_legalize
.insert(lane_type.into().to_string(), group.id)
.is_none());
}
/// 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 roots = Vec::new();
if let Some(i) = &self.default_legalize {
roots.push(*i);
}
if let Some(i) = &self.monomorphic_legalize {
roots.push(*i);
}
roots.extend(self.typed_legalize.values().cloned());
let mut set = HashSet::new();
for root in roots {
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 ret = Vec::from_iter(set);
ret.sort();
ret
}
/// 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

@ -10,10 +10,6 @@ use cranelift_entity::{entity_impl, PrimaryMap};
///
/// This corresponds to a single member of a variant of the `InstructionData`
/// data type.
///
/// :param iform: Parent `InstructionFormat`.
/// :param kind: Immediate Operand kind.
/// :param member: Member name in `InstructionData` variant.
#[derive(Debug)]
pub struct FormatField {
/// Immediate operand number in parent.
@ -22,39 +18,36 @@ pub struct FormatField {
/// Immediate operand kind.
pub kind: OperandKind,
/// Member name in InstructionDate variant.
/// Member name in InstructionData variant.
pub member: &'static str,
}
/// Every instruction opcode has a corresponding instruction format which
/// determines the number of operands and their kinds. Instruction formats are
/// identified structurally, i.e., the format of an instruction is derived from
/// the kinds of operands used in its declaration.
/// Every instruction opcode has a corresponding instruction format which determines the number of
/// operands and their kinds. Instruction formats are identified structurally, i.e., the format of
/// an instruction is derived from the kinds of operands used in its declaration.
///
/// The instruction format stores two separate lists of operands: Immediates
/// and values. Immediate operands (including entity references) are
/// represented as explicit members in the `InstructionData` variants. The
/// value operands are stored differently, depending on how many there are.
/// Beyond a certain point, instruction formats switch to an external value
/// list for storing value arguments. Value lists can hold an arbitrary number
/// of values.
/// The instruction format stores two separate lists of operands: Immediates and values. Immediate
/// operands (including entity references) are represented as explicit members in the
/// `InstructionData` variants. The value operands are stored differently, depending on how many
/// there are. Beyond a certain point, instruction formats switch to an external value list for
/// storing value arguments. Value lists can hold an arbitrary number of values.
///
/// All instruction formats must be predefined in the meta shared/formats module.
///
/// :param kinds: List of `OperandKind` objects describing the operands.
/// :param name: Instruction format name in CamelCase. This is used as a Rust
/// variant name in both the `InstructionData` and `InstructionFormat`
/// enums.
/// :param typevar_operand: Index of the value input operand that is used to
/// infer the controlling type variable. By default, this is `0`, the first
/// `value` operand. The index is relative to the values only, ignoring
/// immediate operands.
/// All instruction formats must be predefined in the meta shared/formats.rs module.
#[derive(Debug)]
pub struct InstructionFormat {
/// Instruction format name in CamelCase. This is used as a Rust variant name in both the
/// `InstructionData` and `InstructionFormat` enums.
pub name: &'static str,
pub num_value_operands: usize,
pub has_value_list: bool,
pub imm_fields: Vec<FormatField>,
/// Index of the value input operand that is used to infer the controlling type variable. By
/// default, this is `0`, the first `value` operand. The index is relative to the values only,
/// ignoring immediate operands.
pub typevar_operand: Option<usize>,
}
@ -162,7 +155,7 @@ impl InstructionFormatBuilder {
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct InstructionFormatIndex(u32);
entity_impl!(InstructionFormatIndex);

View File

@ -1,10 +1,15 @@
use crate::cdsl::camel_case;
use crate::cdsl::formats::{FormatRegistry, InstructionFormat, InstructionFormatIndex};
use crate::cdsl::formats::{
FormatField, FormatRegistry, InstructionFormat, InstructionFormatIndex,
};
use crate::cdsl::operands::Operand;
use crate::cdsl::type_inference::Constraint;
use crate::cdsl::types::ValueType;
use crate::cdsl::typevar::TypeVar;
use std::fmt;
use std::ops;
use std::rc::Rc;
use std::slice;
/// Every instruction must belong to exactly one instruction group. A given
@ -32,6 +37,13 @@ impl InstructionGroup {
pub fn iter(&self) -> slice::Iter<Instruction> {
self.instructions.iter()
}
pub fn by_name(&self, name: &'static str) -> &Instruction {
self.instructions
.iter()
.find(|inst| inst.name == name)
.expect(&format!("unexisting instruction with name {}", name))
}
}
pub struct PolymorphicInfo {
@ -40,7 +52,7 @@ pub struct PolymorphicInfo {
pub other_typevars: Vec<TypeVar>,
}
pub struct Instruction {
pub struct InstructionContent {
/// Instruction mnemonic, also becomes opcode name.
pub name: &'static str,
pub camel_name: String,
@ -53,7 +65,7 @@ pub struct Instruction {
/// Output operands. The output operands must be SSA values or `variable_args`.
pub operands_out: Vec<Operand>,
/// Instruction-specific TypeConstraints.
_constraints: Vec<Constraint>,
pub constraints: Vec<Constraint>,
/// Instruction format, automatically derived from the input operands.
pub format: InstructionFormatIndex,
@ -90,6 +102,18 @@ pub struct Instruction {
pub writes_cpu_flags: bool,
}
#[derive(Clone)]
pub struct Instruction {
content: Rc<InstructionContent>,
}
impl ops::Deref for Instruction {
type Target = InstructionContent;
fn deref(&self) -> &Self::Target {
&*self.content
}
}
impl Instruction {
pub fn snake_name(&self) -> &'static str {
if self.name == "return" {
@ -108,6 +132,17 @@ impl Instruction {
}
""
}
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(),
}
}
}
impl fmt::Display for Instruction {
@ -272,32 +307,40 @@ impl InstructionBuilder {
let writes_cpu_flags = operands_out.iter().any(|op| op.is_cpu_flags());
Instruction {
name: self.name,
camel_name: camel_case(self.name),
doc: self.doc,
operands_in,
operands_out,
_constraints: self.constraints.unwrap_or_else(Vec::new),
format: format_index,
polymorphic_info,
value_opnums,
value_results,
imm_opnums,
is_terminator: self.is_terminator,
is_branch: self.is_branch,
is_indirect_branch: self.is_indirect_branch,
is_call: self.is_call,
is_return: self.is_return,
is_ghost: self.is_ghost,
can_load: self.can_load,
can_store: self.can_store,
can_trap: self.can_trap,
other_side_effects: self.other_side_effects,
writes_cpu_flags,
content: Rc::new(InstructionContent {
name: self.name,
camel_name: camel_case(self.name),
doc: self.doc,
operands_in,
operands_out,
constraints: self.constraints.unwrap_or_else(Vec::new),
format: format_index,
polymorphic_info,
value_opnums,
value_results,
imm_opnums,
is_terminator: self.is_terminator,
is_branch: self.is_branch,
is_indirect_branch: self.is_indirect_branch,
is_call: self.is_call,
is_return: self.is_return,
is_ghost: self.is_ghost,
can_load: self.can_load,
can_store: self.can_store,
can_trap: self.can_trap,
other_side_effects: self.other_side_effects,
writes_cpu_flags,
}),
}
}
}
#[derive(Clone)]
pub struct BoundInstruction {
pub inst: Instruction,
pub value_types: Vec<ValueType>,
}
/// Check if this instruction is polymorphic, and verify its use of type variables.
fn verify_polymorphic(
operands_in: &Vec<Operand>,
@ -456,3 +499,102 @@ fn verify_ctrl_typevar(
Ok(other_typevars)
}
/// A basic node in an instruction predicate: either an atom, or an AND of two conditions.
pub enum InstructionPredicateNode {
/// Is the field member (first member) equal to the actual argument (which name is the second
/// field)?
IsFieldEqual(String, String),
/// 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),
/// A combination of two other predicates.
And(Vec<InstructionPredicateNode>),
}
impl InstructionPredicateNode {
fn rust_predicate(&self) -> String {
match self {
InstructionPredicateNode::IsFieldEqual(field_name, arg) => {
let new_args = vec![field_name.clone(), arg.clone()];
format!("crate::predicates::is_equal({})", new_args.join(", "))
}
InstructionPredicateNode::TypeVarCheck(index, value_type_name) => format!(
"func.dfg.value_type(args[{}]) == {}",
index, value_type_name
),
InstructionPredicateNode::CtrlTypeVarCheck(value_type_name) => {
format!("func.dfg.ctrl_typevar(inst) == {}", value_type_name)
}
InstructionPredicateNode::And(nodes) => nodes
.iter()
.map(|x| x.rust_predicate())
.collect::<Vec<_>>()
.join(" &&\n"),
}
}
}
pub struct InstructionPredicate {
node: Option<InstructionPredicateNode>,
}
impl InstructionPredicate {
pub fn new() -> Self {
Self { node: None }
}
pub fn new_typevar_check(
inst: &Instruction,
type_var: &TypeVar,
value_type: &ValueType,
) -> InstructionPredicateNode {
let index = inst
.value_opnums
.iter()
.enumerate()
.filter(|(_, &op_num)| inst.operands_in[op_num].type_var().unwrap() == type_var)
.next()
.unwrap()
.0;
InstructionPredicateNode::TypeVarCheck(index, value_type.rust_name())
}
pub fn new_is_field_equal(
format_field: &FormatField,
imm_value: String,
) -> InstructionPredicateNode {
InstructionPredicateNode::IsFieldEqual(format_field.member.into(), imm_value)
}
pub fn new_ctrl_typevar_check(value_type: &ValueType) -> InstructionPredicateNode {
InstructionPredicateNode::CtrlTypeVarCheck(value_type.rust_name())
}
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,
_ => vec![node],
},
_ => Vec::new(),
};
and_nodes.push(new_node);
self.node = Some(InstructionPredicateNode::And(and_nodes));
self
}
pub fn rust_predicate(&self) -> String {
match &self.node {
Some(root) => root.rust_predicate(),
None => "true".into(),
}
}
}

View File

@ -1,12 +1,18 @@
use crate::cdsl::cpu_modes::CpuMode;
use crate::cdsl::inst::InstructionGroup;
use crate::cdsl::regs::IsaRegs;
use crate::cdsl::settings::SettingGroup;
use crate::cdsl::xform::{TransformGroupIndex, TransformGroups};
use std::collections::HashSet;
use std::iter::FromIterator;
pub struct TargetIsa {
pub name: &'static str,
pub instructions: InstructionGroup,
pub settings: SettingGroup,
pub regs: IsaRegs,
pub cpu_modes: Vec<CpuMode>,
}
impl TargetIsa {
@ -15,12 +21,41 @@ impl TargetIsa {
instructions: InstructionGroup,
settings: SettingGroup,
regs: IsaRegs,
cpu_modes: Vec<CpuMode>,
) -> Self {
Self {
name,
instructions,
settings,
regs,
cpu_modes,
}
}
/// 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 cpu_mode in &self.cpu_modes {
set.extend(cpu_mode.transitive_transform_groups(all_groups));
}
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> {
let mut set = HashSet::new();
for cpu_mode in &self.cpu_modes {
set.extend(cpu_mode.direct_transform_groups());
}
let mut vec = Vec::from_iter(set);
vec.sort();
vec
}
}

View File

@ -3,6 +3,9 @@
//! 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 formats;
pub mod inst;
pub mod isa;
@ -12,6 +15,7 @@ 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]
@ -19,6 +23,15 @@ macro_rules! predicate {
($a:ident && $($b:tt)*) => {
PredicateNode::And(Box::new($a.into()), Box::new(predicate!($($b)*)))
};
(!$a:ident && $($b:tt)*) => {
PredicateNode::And(
Box::new(PredicateNode::Not(Box::new($a.into()))),
Box::new(predicate!($($b)*))
)
};
(!$a:ident) => {
PredicateNode::Not(Box::new($a.into()))
};
($a:ident) => {
$a.into()
};

View File

@ -134,7 +134,7 @@ pub struct OperandKind {
/// The camel-cased name of an operand kind is also the Rust type used to represent it.
pub rust_type: String,
fields: OperandKindFields,
pub fields: OperandKindFields,
}
impl OperandKind {

View File

@ -153,7 +153,7 @@ impl SettingGroup {
/// This is the basic information needed to track the specific parts of a setting when building
/// them.
pub enum ProtoSpecificSetting {
Bool(bool, u8),
Bool(bool),
Enum(Vec<&'static str>),
Num(u8),
}
@ -169,6 +169,7 @@ struct ProtoSetting {
pub enum PredicateNode {
OwnedBool(BoolSettingIndex),
SharedBool(&'static str, &'static str),
Not(Box<PredicateNode>),
And(Box<PredicateNode>, Box<PredicateNode>),
}
@ -198,10 +199,16 @@ impl PredicateNode {
PredicateNode::And(ref lhs, ref rhs) => {
format!("{} && {}", lhs.render(group), rhs.render(group))
}
PredicateNode::Not(ref node) => format!("!({})", node.render(group)),
}
}
}
struct ProtoPredicate {
pub name: &'static str,
node: PredicateNode,
}
pub struct Predicate {
pub name: &'static str,
node: PredicateNode,
@ -218,8 +225,7 @@ pub struct SettingGroupBuilder {
name: &'static str,
settings: Vec<ProtoSetting>,
presets: Vec<Preset>,
predicates: Vec<Predicate>,
predicate_number: u8,
predicates: Vec<ProtoPredicate>,
}
impl SettingGroupBuilder {
@ -229,7 +235,6 @@ impl SettingGroupBuilder {
settings: Vec::new(),
presets: Vec::new(),
predicates: Vec::new(),
predicate_number: 0,
}
}
@ -256,13 +261,7 @@ impl SettingGroupBuilder {
self.predicates.len() == 0,
"predicates must be added after the boolean settings"
);
let predicate_number = self.predicate_number;
self.predicate_number += 1;
self.add_setting(
name,
comment,
ProtoSpecificSetting::Bool(default, predicate_number),
);
self.add_setting(name, comment, ProtoSpecificSetting::Bool(default));
BoolSettingIndex(self.settings.len() - 1)
}
@ -280,9 +279,7 @@ impl SettingGroupBuilder {
}
pub fn add_predicate(&mut self, name: &'static str, node: PredicateNode) {
let number = self.predicate_number;
self.predicate_number += 1;
self.predicates.push(Predicate { name, node, number });
self.predicates.push(ProtoPredicate { name, node });
}
pub fn add_preset(&mut self, name: &'static str, args: Vec<PresetType>) -> PresetIndex {
@ -307,8 +304,8 @@ impl SettingGroupBuilder {
/// 1. Byte-sized settings like `NumSetting` and `EnumSetting`.
/// 2. `BoolSetting` settings.
/// 3. Precomputed named predicates.
/// 4. Other numbered predicates, including anonymous predicates and parent
/// predicates that need to be accessible by number.
/// 4. Other numbered predicates, including parent predicates that need to be accessible by
/// number.
///
/// Set `self.settings_size` to the length of the byte vector prefix that
/// contains the settings. All bytes after that are computed, not
@ -318,9 +315,6 @@ impl SettingGroupBuilder {
/// 2. in the list above.
///
/// Assign `byte_offset` and `bit_offset` fields in all settings.
///
/// After calling this method, no more settings can be added, but
/// additional predicates can be made accessible with `number_predicate()`.
pub fn finish(self) -> SettingGroup {
let mut group = SettingGroup {
name: self.name,
@ -353,12 +347,12 @@ impl SettingGroupBuilder {
group.bool_start_byte_offset = byte_offset;
let mut predicate_number = 0;
// Then the boolean settings.
for s in &self.settings {
let (default, predicate_number) = match s.specific {
ProtoSpecificSetting::Bool(default, predicate_number) => {
(default, predicate_number)
}
let default = match s.specific {
ProtoSpecificSetting::Bool(default) => default,
ProtoSpecificSetting::Enum(_) | ProtoSpecificSetting::Num(_) => continue,
};
group.settings.push(Setting {
@ -371,6 +365,7 @@ impl SettingGroupBuilder {
predicate_number,
}),
});
predicate_number += 1;
}
assert!(
@ -379,7 +374,22 @@ impl SettingGroupBuilder {
);
group.settings_size = group.byte_size();
group.predicates.extend(self.predicates);
// Sort predicates by name to ensure the same order as the Python code.
let mut predicates = self.predicates;
predicates.sort_by_key(|predicate| predicate.name);
group
.predicates
.extend(predicates.into_iter().map(|predicate| {
let number = predicate_number;
predicate_number += 1;
return Predicate {
name: predicate.name,
node: predicate.node,
number,
};
}));
group.presets.extend(self.presets);
group

View File

@ -1,5 +1,658 @@
use crate::cdsl::typevar::TypeVar;
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;
#[derive(Hash, PartialEq, Eq)]
pub enum Constraint {
/// Constraint specifying that a type var tv1 must be wider than or equal to type var tv2 at
/// runtime. This requires that:
/// 1) They have the same number of lanes
/// 2) In a lane tv1 has at least as many bits as tv2.
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).len() == 0 {
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 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()
.find(|&item| item == &constraint)
.is_some()
{
return;
}
// Check extra conditions for InTypeset constraints.
if let Constraint::InTypeset(tv, _) = &constraint {
assert!(tv.base.is_none());
assert!(tv.name.starts_with("typeof_"));
}
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_"),
format!("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.
return 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(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(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() {
assert!(vars_tv.contains(arg));
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().clone();
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 fn infer_transform(
src: DefIndex,
dst: &Vec<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().clone())
})
.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

@ -1,4 +1,7 @@
use std::collections::BTreeSet;
use std::cell::RefCell;
use std::collections::{BTreeSet, HashSet};
use std::fmt;
use std::hash;
use std::iter::FromIterator;
use std::ops;
use std::rc::Rc;
@ -27,25 +30,25 @@ pub struct TypeVarContent {
/// Type set associated to the type variable.
/// This field must remain private; use `get_typeset()` or `get_raw_typeset()` to get the
/// information you want.
type_set: Rc<TypeSet>,
type_set: TypeSet,
pub base: Option<TypeVarParent>,
}
#[derive(Clone, Debug)]
pub struct TypeVar {
content: Rc<TypeVarContent>,
content: Rc<RefCell<TypeVarContent>>,
}
impl TypeVar {
pub fn new(name: impl Into<String>, doc: impl Into<String>, type_set: TypeSet) -> Self {
Self {
content: Rc::new(TypeVarContent {
content: Rc::new(RefCell::new(TypeVarContent {
name: name.into(),
doc: doc.into(),
type_set: Rc::new(type_set),
type_set,
base: None,
}),
})),
}
}
@ -86,20 +89,37 @@ impl TypeVar {
TypeVar::new(name, doc, builder.finish())
}
/// Returns this typevar's type set, maybe computing it from the parent.
fn get_typeset(&self) -> Rc<TypeSet> {
// TODO Can this be done in a non-lazy way in derived() and we can remove this function and
// the one below?
match &self.content.base {
Some(base) => Rc::new(base.type_var.get_typeset().image(base.derived_func)),
None => self.content.type_set.clone(),
/// Get a fresh copy of self, named after `name`. Can only be called on non-derived typevars.
pub fn copy_from(other: &TypeVar, name: String) -> TypeVar {
assert!(
other.base.is_none(),
"copy_from() can only be called on non-derived type variables"
);
TypeVar {
content: Rc::new(RefCell::new(TypeVarContent {
name,
doc: "".into(),
type_set: other.type_set.clone(),
base: None,
})),
}
}
/// Returns the typeset for this TV. If the TV is derived, computes it recursively from the
/// derived function and the base's typeset.
/// Note this can't be done non-lazily in the constructor, because the TypeSet of the base may
/// change over time.
pub fn get_typeset(&self) -> TypeSet {
match &self.base {
Some(base) => base.type_var.get_typeset().image(base.derived_func),
None => self.type_set.clone(),
}
}
/// Returns this typevar's type set, assuming this type var has no parent.
pub fn get_raw_typeset(&self) -> &TypeSet {
assert_eq!(self.content.type_set, self.get_typeset());
&*self.content.type_set
assert_eq!(self.type_set, self.get_typeset());
&self.type_set
}
/// If the associated typeset has a single type return it. Otherwise return None.
@ -114,7 +134,7 @@ impl TypeVar {
/// Get the free type variable controlling this one.
pub fn free_typevar(&self) -> Option<TypeVar> {
match &self.content.base {
match &self.base {
Some(base) => base.type_var.free_typevar(),
None => {
match self.singleton_type() {
@ -127,7 +147,7 @@ impl TypeVar {
}
/// Create a type variable that is a function of another.
fn derived(&self, derived_func: DerivedFunc) -> TypeVar {
pub fn derived(&self, derived_func: DerivedFunc) -> TypeVar {
let ts = self.get_typeset();
// Safety checks to avoid over/underflows.
@ -179,7 +199,7 @@ impl TypeVar {
}
return TypeVar {
content: Rc::new(TypeVarContent {
content: Rc::new(RefCell::new(TypeVarContent {
name: format!("{}({})", derived_func.name(), self.name),
doc: "".into(),
type_set: ts,
@ -187,7 +207,7 @@ impl TypeVar {
type_var: self.clone(),
derived_func,
}),
}),
})),
};
}
@ -212,6 +232,52 @@ impl TypeVar {
pub fn to_bitvec(&self) -> TypeVar {
return self.derived(DerivedFunc::ToBitVec);
}
/// 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!(
"{}.{}()",
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 {
@ -225,24 +291,46 @@ impl Into<TypeVar> for ValueType {
}
}
// Hash TypeVars by pointers.
// There might be a better way to do this, but since TypeVar's content (namely TypeSet) can be
// mutated, it makes sense to use pointer equality/hashing here.
impl hash::Hash for TypeVar {
fn hash<H: hash::Hasher>(&self, h: &mut H) {
match &self.base {
Some(base) => {
base.type_var.hash(h);
base.derived_func.hash(h);
}
None => {
(&**self as *const TypeVarContent).hash(h);
}
}
}
}
impl PartialEq for TypeVar {
fn eq(&self, other: &TypeVar) -> bool {
match (&self.content.base, &other.content.base) {
(Some(base1), Some(base2)) => base1.type_var.eq(&base2.type_var),
match (&self.base, &other.base) {
(Some(base1), Some(base2)) => {
base1.type_var.eq(&base2.type_var) && base1.derived_func == base2.derived_func
}
(None, None) => Rc::ptr_eq(&self.content, &other.content),
_ => false,
}
}
}
// Allow TypeVar as map keys, based on pointer equality (see also above PartialEq impl).
impl Eq for TypeVar {}
impl ops::Deref for TypeVar {
type Target = TypeVarContent;
fn deref(&self) -> &Self::Target {
&*self.content
unsafe { self.content.as_ptr().as_ref().unwrap() }
}
}
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, Hash, PartialEq)]
pub enum DerivedFunc {
LaneOf,
AsBool,
@ -265,9 +353,20 @@ impl DerivedFunc {
DerivedFunc::ToBitVec => "to_bitvec",
}
}
/// 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),
_ => None,
}
}
}
#[derive(Debug)]
#[derive(Debug, Hash)]
pub struct TypeVarParent {
pub type_var: TypeVar,
pub derived_func: DerivedFunc,
@ -301,7 +400,7 @@ macro_rules! num_set {
};
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct TypeSet {
pub lanes: NumSet,
pub ints: NumSet,
@ -331,7 +430,7 @@ impl TypeSet {
}
/// Return the number of concrete types represented by this typeset.
fn size(&self) -> usize {
pub fn size(&self) -> usize {
self.lanes.len()
* (self.ints.len() + self.floats.len() + self.bools.len() + self.bitvecs.len())
+ self.specials.len()
@ -486,6 +585,175 @@ impl TypeSet {
assert_eq!(types.len(), 1);
return 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.bitvecs = NumSet::new();
copy.lanes =
NumSet::from_iter((0..MAX_LANES.trailing_zeros() + 1).map(|i| u16::pow(2, i)));
copy
}
DerivedFunc::AsBool => {
let mut copy = self.clone();
copy.bitvecs = NumSet::new();
if self.bools.contains(&1) {
copy.ints = NumSet::from_iter(vec![8, 16, 32, 64]);
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::ToBitVec => {
let all_lanes = range_to_set(Some(1..MAX_LANES));
let all_ints = range_to_set(Some(8..MAX_BITS));
let all_floats = range_to_set(Some(32..64));
let all_bools = range_to_set(Some(1..MAX_BITS));
let mut lanes = range_to_set(Some(1..MAX_LANES));
let mut ints = range_to_set(Some(8..MAX_BITS));
let mut floats = range_to_set(Some(32..64));
let mut bools = range_to_set(Some(1..MAX_BITS));
for &l in &all_lanes {
for &i in &all_ints {
if self.bitvecs.contains(&(i * l)) {
lanes.insert(l);
ints.insert(i);
}
}
for &f in &all_floats {
if self.bitvecs.contains(&(f * l)) {
lanes.insert(l);
floats.insert(f);
}
}
for &b in &all_bools {
if self.bitvecs.contains(&(b * l)) {
lanes.insert(l);
bools.insert(b);
}
}
}
let bitvecs = NumSet::new();
let specials = Vec::new();
TypeSet::new(lanes, ints, floats, bools, bitvecs, specials)
}
}
}
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.bitvecs = &self.bitvecs & &other.bitvecs;
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.bitvecs.is_subset(&other.bitvecs)
&& {
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)
}
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)
}
}
fn set_wider_or_equal(s1: &NumSet, s2: &NumSet) -> bool {
s1.len() > 0 && s2.len() > 0 && s1.iter().min() >= s2.iter().max()
}
fn set_narrower(s1: &NumSet, s2: &NumSet) -> bool {
s1.len() > 0 && s2.len() > 0 && s1.iter().min() < s2.iter().max()
}
impl fmt::Debug for TypeSet {
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(fmt, "TypeSet(")?;
let mut subsets = Vec::new();
if !self.lanes.is_empty() {
subsets.push(format!(
"lanes={{{}}}",
Vec::from_iter(self.lanes.iter().map(|x| x.to_string())).join(", ")
));
}
if !self.ints.is_empty() {
subsets.push(format!(
"ints={{{}}}",
Vec::from_iter(self.ints.iter().map(|x| x.to_string())).join(", ")
));
}
if !self.floats.is_empty() {
subsets.push(format!(
"floats={{{}}}",
Vec::from_iter(self.floats.iter().map(|x| x.to_string())).join(", ")
));
}
if !self.bools.is_empty() {
subsets.push(format!(
"bools={{{}}}",
Vec::from_iter(self.bools.iter().map(|x| x.to_string())).join(", ")
));
}
if !self.bitvecs.is_empty() {
subsets.push(format!(
"bitvecs={{{}}}",
Vec::from_iter(self.bitvecs.iter().map(|x| x.to_string())).join(", ")
));
}
if !self.specials.is_empty() {
subsets.push(format!(
"specials={{{}}}",
Vec::from_iter(self.specials.iter().map(|x| x.to_string())).join(", ")
));
}
write!(fmt, "{})", subsets.join(", "))?;
Ok(())
}
}
pub struct TypeSetBuilder {
@ -563,6 +831,18 @@ impl TypeSetBuilder {
self.specials,
)
}
pub fn all() -> TypeSet {
TypeSetBuilder::new()
.ints(Interval::All)
.floats(Interval::All)
.bools(Interval::All)
.simd_lanes(Interval::All)
.bitvecs(Interval::All)
.specials(ValueType::all_special_types().collect())
.includes_scalars(true)
.finish()
}
}
#[derive(PartialEq)]
@ -805,6 +1085,136 @@ fn test_forward_images() {
);
}
#[test]
fn test_backward_images() {
let empty_set = TypeSetBuilder::new().finish();
// LaneOf.
assert_eq!(
TypeSetBuilder::new()
.simd_lanes(1..1)
.ints(8..8)
.floats(32..32)
.finish()
.preimage(DerivedFunc::LaneOf),
TypeSetBuilder::new()
.simd_lanes(Interval::All)
.ints(8..8)
.floats(32..32)
.finish()
);
assert_eq!(empty_set.preimage(DerivedFunc::LaneOf), empty_set);
// AsBool.
assert_eq!(
TypeSetBuilder::new()
.simd_lanes(1..4)
.bools(1..64)
.finish()
.preimage(DerivedFunc::AsBool),
TypeSetBuilder::new()
.simd_lanes(1..4)
.ints(Interval::All)
.bools(Interval::All)
.floats(Interval::All)
.finish()
);
// Double vector.
assert_eq!(
TypeSetBuilder::new()
.simd_lanes(1..1)
.ints(8..8)
.finish()
.preimage(DerivedFunc::DoubleVector)
.size(),
0
);
assert_eq!(
TypeSetBuilder::new()
.simd_lanes(1..16)
.ints(8..16)
.floats(32..32)
.finish()
.preimage(DerivedFunc::DoubleVector),
TypeSetBuilder::new()
.simd_lanes(1..8)
.ints(8..16)
.floats(32..32)
.finish(),
);
// Half vector.
assert_eq!(
TypeSetBuilder::new()
.simd_lanes(256..256)
.ints(8..8)
.finish()
.preimage(DerivedFunc::HalfVector)
.size(),
0
);
assert_eq!(
TypeSetBuilder::new()
.simd_lanes(64..128)
.bools(1..32)
.finish()
.preimage(DerivedFunc::HalfVector),
TypeSetBuilder::new()
.simd_lanes(128..256)
.bools(1..32)
.finish(),
);
// Half width.
assert_eq!(
TypeSetBuilder::new()
.ints(64..64)
.floats(64..64)
.bools(64..64)
.finish()
.preimage(DerivedFunc::HalfWidth)
.size(),
0
);
assert_eq!(
TypeSetBuilder::new()
.simd_lanes(64..256)
.bools(1..64)
.finish()
.preimage(DerivedFunc::HalfWidth),
TypeSetBuilder::new()
.simd_lanes(64..256)
.bools(16..64)
.finish(),
);
// Double width.
assert_eq!(
TypeSetBuilder::new()
.ints(8..8)
.floats(32..32)
.bools(1..8)
.finish()
.preimage(DerivedFunc::DoubleWidth)
.size(),
0
);
assert_eq!(
TypeSetBuilder::new()
.simd_lanes(1..16)
.ints(8..16)
.floats(32..64)
.finish()
.preimage(DerivedFunc::DoubleWidth),
TypeSetBuilder::new()
.simd_lanes(1..16)
.ints(8..8)
.floats(32..32)
.finish()
);
}
#[test]
#[should_panic]
fn test_typeset_singleton_panic_nonsingleton_types() {

View File

@ -0,0 +1,416 @@
use crate::cdsl::ast::{
Apply, DefIndex, DefPool, DummyDef, DummyExpr, Expr, PatternPosition, VarIndex, VarPool,
};
use crate::cdsl::inst::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 struct Transform {
pub src: DefIndex,
pub dst: Vec<DefIndex>,
pub var_pool: VarPool,
pub def_pool: DefPool,
pub type_env: TypeEnvironment,
}
type SymbolTable = HashMap<&'static str, 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 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,
)[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,
);
// Sanity checks.
for &var_index in &input_vars {
assert!(
var_pool.get(var_index).is_input(),
format!("'{:?}' used as both input and def", var_pool.get(var_index))
);
}
assert!(
input_vars.len() == num_src_inputs,
format!(
"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,
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(),
format!("{:?} not defined in the destination pattern", defined_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 = match symbol_table.get(var.name) {
Some(&existing_var) => existing_var,
None => {
// Materialize the variable.
let new_var = var_pool.create(var.name);
symbol_table.insert(var.name, new_var);
defined_vars.push(new_var);
new_var
}
};
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,
) -> 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"
);
let mut args = Vec::new();
for (i, arg) in dummy_args.into_iter().enumerate() {
match arg {
DummyExpr::Var(var) => {
let own_var = match symbol_table.get(var.name) {
Some(&own_var) => {
let var = var_pool.get(own_var);
assert!(
var.is_input() || var.get_def(position).is_some(),
format!("{:?} used as both input and def", var)
);
own_var
}
None => {
// First time we're using this variable.
let own_var = var_pool.create(var.name);
symbol_table.insert(var.name, own_var);
input_vars.push(own_var);
own_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::Apply(..) => {
panic!("Recursive apply is not allowed.");
}
}
}
Apply::new(apply_target, args)
}
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,
) -> Vec<DefIndex> {
let mut new_defs = Vec::new();
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,
);
let new_apply = rewrite_expr(position, dummy_def.expr, symbol_table, input_vars, var_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(new_apply, new_defined_vars));
}
new_defs
}
/// A group of related transformations.
pub 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 struct TransformGroupIndex(u32);
entity_impl!(TransformGroupIndex);
pub 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(),
format!(
"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 finish_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 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,
format!("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]
}
pub fn get_mut(&mut self, id: TransformGroupIndex) -> &mut TransformGroup {
self.groups.get_mut(id).unwrap()
}
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!(format!("transform group with name {} not found", name));
}
}
#[test]
#[should_panic]
fn test_double_custom_legalization() {
use crate::cdsl::formats::{FormatRegistry, InstructionFormatBuilder};
use crate::cdsl::inst::InstructionBuilder;
let mut format = FormatRegistry::new();
format.insert(InstructionFormatBuilder::new("nullary"));
let dummy_inst = InstructionBuilder::new("dummy", "doc").finish(&format);
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

@ -665,7 +665,7 @@ fn typeset_to_string(ts: &TypeSet) -> String {
}
/// Generate the table of ValueTypeSets described by type_sets.
fn gen_typesets_table(type_sets: &UniqueTable<TypeSet>, fmt: &mut Formatter) {
pub fn gen_typesets_table(type_sets: &UniqueTable<TypeSet>, fmt: &mut Formatter) {
if type_sets.len() == 0 {
return;
}

View File

@ -0,0 +1,578 @@
use crate::cdsl::ast::{Def, DefPool, VarPool};
use crate::cdsl::formats::FormatRegistry;
use crate::cdsl::isa::TargetIsa;
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,
format_registry: &FormatRegistry,
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 = format_registry.get(inst.format);
fmt.comment(format!(
"Unwrap {}",
def.to_comment_string(&transform.var_pool)
));
// Extract the Var arguments.
let arg_names = apply
.args
.iter()
.map(|arg| match arg.maybe_var() {
Some(var_index) => var_pool.get(var_index).name,
None => "_",
})
.collect::<Vec<_>>()
.join(", ");
fmtln!(
fmt,
"let ({}, predicate) = if let crate::ir::InstructionData::{} {{",
arg_names,
iform.name
);
fmt.indent(|fmt| {
// Fields are encoded directly.
for field in &iform.imm_fields {
fmtln!(fmt, "{},", field.member);
}
if iform.num_value_operands == 1 {
fmt.line("arg,");
} else if iform.has_value_list || iform.num_value_operands > 1 {
fmt.line("ref args,");
}
fmt.line("..");
fmt.outdented_line("} = pos.func.dfg[inst] {");
fmt.line("let func = &pos.func;");
if iform.has_value_list {
fmt.line("let args = args.as_slice(&func.dfg.value_lists);");
} else if iform.num_value_operands == 1 {
fmt.line("let args = [arg];")
}
// Generate the values for the tuple.
fmt.line("(");
fmt.indent(|fmt| {
for (op_num, op) in inst.operands_in.iter().enumerate() {
if op.is_immediate() {
let n = inst.imm_opnums.iter().position(|&i| i == op_num).unwrap();
fmtln!(fmt, "{},", iform.imm_fields[n].member);
} else if op.is_value() {
let n = inst.value_opnums.iter().position(|&i| i == op_num).unwrap();
fmtln!(fmt, "func.dfg.resolve_aliases(args[{}]),", n);
}
}
// Evaluate the instruction predicate if any.
fmt.multi_line(
&apply
.inst_predicate_with_ctrl_typevar(format_registry, var_pool)
.rust_predicate(),
);
});
fmt.line(")");
fmt.outdented_line("} else {");
fmt.line(r#"unreachable!("bad instruction format")"#);
});
fmtln!(fmt, "};");
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.len() > 0 {
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)
));
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, 'b>(
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)
.collect::<Vec<_>>();
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>(
transform: &'a Transform,
format_registry: &FormatRegistry,
type_sets: &mut UniqueTable<'a, TypeSet>,
fmt: &mut Formatter,
) {
// Unwrap the source instruction, create local variables for the input variables.
let replace_inst = unwrap_inst(&transform, format_registry, fmt);
// Emit any runtime checks; these will rebind `predicate` emitted by unwrap_inst().
for constraint in &transform.type_env.constraints {
emit_runtime_typecheck(constraint, type_sets, fmt);
}
// Guard the actual expansion by `predicate`.
fmt.line("if predicate {");
fmt.indent(|fmt| {
// 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 the destination pattern.
for &def_index in &transform.dst {
emit_dst_inst(
transform.def_pool.get(def_index),
&transform.def_pool,
&transform.var_pool,
fmt,
);
}
// 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);");
}
fmt.line("return true;");
});
fmt.line("}");
}
fn gen_transform_group<'a>(
group: &'a TransformGroup,
format_registry: &FormatRegistry,
transform_groups: &TransformGroups,
type_sets: &mut UniqueTable<'a, TypeSet>,
fmt: &mut Formatter,
) {
fmt.doc_comment(group.doc);
fmt.line("#[allow(unused_variables,unused_assignments,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: &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(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| {
for transform in inst_to_transforms.get(camel_name).unwrap() {
gen_transform(transform, format_registry, type_sets, fmt);
}
});
fmtln!(fmt, "}");
fmt.empty_line();
}
// Emit the custom transforms. The Rust compiler will complain about any overlap with
// the normal transforms.
for (inst_camel_name, func_name) in &group.custom_legalizes {
fmtln!(fmt, "ir::Opcode::{} => {{", inst_camel_name);
fmt.indent(|fmt| {
fmtln!(fmt, "{}(inst, pos.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, pos.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,
format_registry: &FormatRegistry,
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,
format_registry,
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 fn generate(
isas: &Vec<TargetIsa>,
format_registry: &FormatRegistry,
transform_groups: &TransformGroups,
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,
format_registry,
transform_groups,
&mut shared_group_names,
&mut fmt,
);
fmt.update_file(format!("{}-{}.rs", filename_prefix, isa.name), out_dir)?;
}
// Generate shared legalize groups.
let mut fmt = Formatter::new();
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,
format_registry,
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

@ -288,7 +288,8 @@ fn gen_descriptors(group: &SettingGroup, fmt: &mut Formatter) {
});
fmtln!(fmt, "},");
descriptor_index_map.insert(SettingOrPreset::Preset(preset), idx);
let whole_idx = idx + group.settings.len();
descriptor_index_map.insert(SettingOrPreset::Preset(preset), whole_idx);
}
});
fmtln!(fmt, "];");
@ -304,20 +305,9 @@ fn gen_descriptors(group: &SettingGroup, fmt: &mut Formatter) {
// Generate hash table.
let mut hash_entries: Vec<SettingOrPreset> = Vec::new();
hash_entries.extend(
group
.settings
.iter()
.map(|x| SettingOrPreset::Setting(x))
.collect::<Vec<SettingOrPreset>>(),
);
hash_entries.extend(
group
.presets
.iter()
.map(|x| SettingOrPreset::Preset(x))
.collect::<Vec<SettingOrPreset>>(),
);
hash_entries.extend(group.settings.iter().map(|x| SettingOrPreset::Setting(x)));
hash_entries.extend(group.presets.iter().map(|x| SettingOrPreset::Preset(x)));
let hash_table = generate_table(&hash_entries, |entry| simple_hash(entry.name()));
fmtln!(fmt, "static HASH_TABLE: [u16; {}] = [", hash_table.len());
fmt.indent(|fmt| {
@ -341,7 +331,7 @@ fn gen_descriptors(group: &SettingGroup, fmt: &mut Formatter) {
fmtln!(
fmt,
"static PRESETS: [(u8, u8); {}] = [",
group.presets.len()
group.presets.len() * (group.settings_size as usize)
);
fmt.indent(|fmt| {
for preset in &group.presets {

View File

@ -1,7 +1,9 @@
use crate::cdsl::cpu_modes::CpuMode;
use crate::cdsl::inst::InstructionGroup;
use crate::cdsl::isa::TargetIsa;
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 {
@ -52,5 +54,16 @@ pub fn define(shared_defs: &mut SharedDefinitions) -> TargetIsa {
let inst_group = InstructionGroup::new("arm32", "arm32 specific instruction set");
TargetIsa::new("arm32", inst_group, settings, regs)
// CPU modes for 32-bit ARM and Thumb2.
let mut a32 = CpuMode::new("A32");
let mut t32 = CpuMode::new("T32");
// TODO refine these.
let narrow = shared_defs.transform_groups.by_name("narrow");
a32.legalize_default(narrow);
t32.legalize_default(narrow);
let cpu_modes = vec![a32, t32];
TargetIsa::new("arm32", inst_group, settings, regs, cpu_modes)
}

View File

@ -1,7 +1,9 @@
use crate::cdsl::cpu_modes::CpuMode;
use crate::cdsl::inst::InstructionGroup;
use crate::cdsl::isa::TargetIsa;
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 {
@ -48,5 +50,13 @@ pub fn define(shared_defs: &mut SharedDefinitions) -> TargetIsa {
let inst_group = InstructionGroup::new("arm64", "arm64 specific instruction set");
TargetIsa::new("arm64", inst_group, settings, regs)
let mut a64 = CpuMode::new("A64");
// TODO refine these.
let narrow = shared_defs.transform_groups.by_name("narrow");
a64.legalize_default(narrow);
let cpu_modes = vec![a64];
TargetIsa::new("arm64", inst_group, settings, regs, cpu_modes)
}

View File

@ -1,7 +1,11 @@
use crate::cdsl::cpu_modes::CpuMode;
use crate::cdsl::inst::InstructionGroup;
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;
fn define_settings(shared: &SettingGroup) -> SettingGroup {
@ -37,7 +41,7 @@ fn define_settings(shared: &SettingGroup) -> SettingGroup {
setting.add_bool(
"enable_e",
"Enable the 'RV32E' instruction set with only 16 registers",
true,
false,
);
let shared_enable_atomics = shared.get_bool("enable_atomics");
@ -84,5 +88,26 @@ pub fn define(shared_defs: &mut SharedDefinitions) -> TargetIsa {
let inst_group = InstructionGroup::new("riscv", "riscv specific instruction set");
TargetIsa::new("riscv", inst_group, settings, regs)
// 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 = shared_defs.transform_groups.by_name("narrow");
rv_32.legalize_monomorphic(expand);
rv_32.legalize_default(narrow);
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);
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 cpu_modes = vec![rv_32, rv_64];
TargetIsa::new("riscv", inst_group, settings, regs, cpu_modes)
}

View File

@ -0,0 +1,293 @@
use crate::cdsl::ast::{bind, var, ExprBuilder, Literal};
use crate::cdsl::inst::InstructionGroup;
use crate::cdsl::xform::TransformGroupBuilder;
use crate::shared::types::Int::{I32, I64};
use crate::shared::Definitions as SharedDefinitions;
pub fn define(shared: &mut SharedDefinitions, x86_instructions: &InstructionGroup) {
let mut group = 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);
// 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 isub = insts.by_name("isub");
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 udiv = insts.by_name("udiv");
let umulhi = insts.by_name("umulhi");
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");
// List of immediates.
let floatcc = shared.operand_kinds.by_name("floatcc");
let imm64 = shared.operand_kinds.by_name("imm64");
let intcc = shared.operand_kinds.by_name("intcc");
// 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.
group.custom_legalize(sdiv, "expand_sdivrem");
group.custom_legalize(srem, "expand_sdivrem");
group.custom_legalize(udiv, "expand_udivrem");
group.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");
group.legalize(
def!(res_hi = umulhi(x, y)),
vec![def!((res_lo, res_hi) = x86_umulx(x, y))],
);
group.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(floatcc, "eq");
let floatcc_ord = Literal::enumerator_for(floatcc, "ord");
let floatcc_ueq = Literal::enumerator_for(floatcc, "ueq");
let floatcc_ne = Literal::enumerator_for(floatcc, "ne");
let floatcc_uno = Literal::enumerator_for(floatcc, "uno");
let floatcc_one = Literal::enumerator_for(floatcc, "one");
// Equality needs an explicit `ord` test which checks the parity bit.
group.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)),
],
);
group.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(floatcc, "lt");
let floatcc_gt = &Literal::enumerator_for(floatcc, "gt");
let floatcc_le = &Literal::enumerator_for(floatcc, "le");
let floatcc_ge = &Literal::enumerator_for(floatcc, "ge");
let floatcc_ugt = &Literal::enumerator_for(floatcc, "ugt");
let floatcc_ult = &Literal::enumerator_for(floatcc, "ult");
let floatcc_uge = &Literal::enumerator_for(floatcc, "uge");
let floatcc_ule = &Literal::enumerator_for(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),
] {
group.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.
group.custom_legalize(fmin, "expand_minmax");
group.custom_legalize(fmax, "expand_minmax");
// Conversions from unsigned need special handling.
group.custom_legalize(fcvt_from_uint, "expand_fcvt_from_uint");
// Conversions from float to int can trap and modify the control flow graph.
group.custom_legalize(fcvt_to_sint, "expand_fcvt_to_sint");
group.custom_legalize(fcvt_to_uint, "expand_fcvt_to_uint");
group.custom_legalize(fcvt_to_sint_sat, "expand_fcvt_to_sint_sat");
group.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(intcc, "eq");
let imm64_minus_one = Literal::constant(imm64, -1);
let imm64_63 = Literal::constant(imm64, 63);
group.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(imm64, 31);
group.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(imm64, 64);
group.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(imm64, 32);
group.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 qv1 = var("qv1");
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 qv16 = var("qv16");
let qc77 = var("qc77");
#[allow(non_snake_case)]
let qc0F = var("qc0F");
let qc01 = var("qc01");
let imm64_1 = Literal::constant(imm64, 1);
let imm64_4 = Literal::constant(imm64, 4);
group.legalize(
def!(qv16 = popcnt.I64(qv1)),
vec![
def!(qv3 = ushr_imm(qv1, imm64_1)),
def!(qc77 = iconst(Literal::constant(imm64, 0x7777777777777777))),
def!(qv4 = band(qv3, qc77)),
def!(qv5 = isub(qv1, 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(imm64, 0x0F0F0F0F0F0F0F0F))),
def!(qv14 = band(qv13, qc0F)),
def!(qc01 = iconst(Literal::constant(imm64, 0x0101010101010101))),
def!(qv15 = imul(qv14, qc01)),
def!(qv16 = ushr_imm(qv15, Literal::constant(imm64, 56))),
],
);
let lv1 = var("lv1");
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 lv16 = var("lv16");
let lc77 = var("lc77");
#[allow(non_snake_case)]
let lc0F = var("lc0F");
let lc01 = var("lc01");
group.legalize(
def!(lv16 = popcnt.I32(lv1)),
vec![
def!(lv3 = ushr_imm(lv1, imm64_1)),
def!(lc77 = iconst(Literal::constant(imm64, 0x77777777))),
def!(lv4 = band(lv3, lc77)),
def!(lv5 = isub(lv1, 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(imm64, 0x0F0F0F0F))),
def!(lv14 = band(lv13, lc0F)),
def!(lc01 = iconst(Literal::constant(imm64, 0x01010101))),
def!(lv15 = imul(lv14, lc01)),
def!(lv16 = ushr_imm(lv15, Literal::constant(imm64, 24))),
],
);
group.finish_and_add_to(&mut shared.transform_groups);
}

View File

@ -1,12 +1,17 @@
mod instructions;
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::Bool::B1;
use crate::shared::types::Float::{F32, F64};
use crate::shared::types::Int::{I16, I32, I64, I8};
use crate::shared::Definitions as SharedDefinitions;
fn define_settings(_shared: &SettingGroup) -> SettingGroup {
mod instructions;
mod legalize;
fn define_settings(shared: &SettingGroup) -> SettingGroup {
let mut settings = SettingGroupBuilder::new("x86");
// CPUID.01H:ECX
@ -42,6 +47,26 @@ fn define_settings(_shared: &SettingGroup) -> SettingGroup {
settings.add_predicate("use_bmi1", predicate!(has_bmi1));
settings.add_predicate("use_lznct", predicate!(has_lzcnt));
// Some shared boolean values are used in x86 instruction predicates, so we need to group them
// in the same TargetIsa, for compabitibity with code generated by meta-python.
// TODO Once all the meta generation code has been migrated from Python to Rust, we can put it
// back in the shared SettingGroup, and use it in x86 instruction predicates.
let is_pic = shared.get_bool("is_pic");
let allones_funcaddrs = shared.get_bool("allones_funcaddrs");
settings.add_predicate("is_pic", predicate!(is_pic));
settings.add_predicate("not_is_pic", predicate!(!is_pic));
settings.add_predicate(
"all_ones_funcaddrs_and_not_is_pic",
predicate!(allones_funcaddrs && !is_pic),
);
settings.add_predicate(
"not_all_ones_funcaddrs_and_not_is_pic",
predicate!(!allones_funcaddrs && !is_pic),
);
// Presets corresponding to x86 CPUs.
settings.add_preset("baseline", preset!());
let nehalem = settings.add_preset(
"nehalem",
@ -118,6 +143,37 @@ pub fn define(shared_defs: &mut SharedDefinitions) -> TargetIsa {
let regs = define_registers();
let inst_group = instructions::define(&shared_defs.format_registry);
legalize::define(shared_defs, &inst_group);
TargetIsa::new("x86", inst_group, settings, regs)
// 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 narrow = shared_defs.transform_groups.by_name("narrow");
let widen = shared_defs.transform_groups.by_name("widen");
let x86_expand = shared_defs.transform_groups.by_name("x86_expand");
x86_32.legalize_monomorphic(expand_flags);
x86_32.legalize_default(narrow);
x86_32.legalize_type(B1, expand_flags);
x86_32.legalize_type(I8, widen);
x86_32.legalize_type(I16, widen);
x86_32.legalize_type(I32, x86_expand);
x86_32.legalize_type(F32, x86_expand);
x86_32.legalize_type(F64, x86_expand);
x86_64.legalize_monomorphic(expand_flags);
x86_64.legalize_default(narrow);
x86_64.legalize_type(B1, expand_flags);
x86_64.legalize_type(I8, widen);
x86_64.legalize_type(I16, widen);
x86_64.legalize_type(I32, x86_expand);
x86_64.legalize_type(I64, x86_expand);
x86_64.legalize_type(F32, x86_expand);
x86_64.legalize_type(F64, x86_expand);
let cpu_modes = vec![x86_64, x86_32];
TargetIsa::new("x86", inst_group, settings, regs, cpu_modes)
}

View File

@ -6,6 +6,7 @@ pub mod error;
pub mod isa;
mod gen_inst;
mod gen_legalizer;
mod gen_registers;
mod gen_settings;
mod gen_types;
@ -26,7 +27,7 @@ pub fn generate(isas: &Vec<isa::Isa>, out_dir: &str) -> Result<(), error::Error>
gen_settings::generate(
&shared_defs.settings,
gen_settings::ParentGroup::None,
"new_settings.rs",
"settings.rs",
&out_dir,
)?;
gen_types::generate("types.rs", &out_dir)?;
@ -45,12 +46,20 @@ pub fn generate(isas: &Vec<isa::Isa>, out_dir: &str) -> Result<(), error::Error>
&out_dir,
)?;
gen_legalizer::generate(
&isas,
&shared_defs.format_registry,
&shared_defs.transform_groups,
"legalize",
&out_dir,
)?;
for isa in isas {
gen_registers::generate(&isa, &format!("registers-{}.rs", isa.name), &out_dir)?;
gen_settings::generate(
&isa.settings,
gen_settings::ParentGroup::Shared,
&format!("new_settings-{}", isa.name),
&format!("settings-{}.rs", isa.name),
&out_dir,
)?;
}

View File

@ -71,10 +71,10 @@ pub fn define() -> Vec<OperandKind> {
let mut intcc_values = HashMap::new();
intcc_values.insert("eq", "Equal");
intcc_values.insert("ne", "NotEqual");
intcc_values.insert("sge", "UnsignedGreaterThanOrEqual");
intcc_values.insert("sgt", "UnsignedGreaterThan");
intcc_values.insert("sle", "UnsignedLessThanOrEqual");
intcc_values.insert("slt", "UnsignedLessThan");
intcc_values.insert("sge", "SignedGreaterThanOrEqual");
intcc_values.insert("sgt", "SignedGreaterThan");
intcc_values.insert("sle", "SignedLessThanOrEqual");
intcc_values.insert("slt", "SignedLessThan");
intcc_values.insert("uge", "UnsignedGreaterThanOrEqual");
intcc_values.insert("ugt", "UnsignedGreaterThan");
intcc_values.insert("ule", "UnsignedLessThanOrEqual");

View File

@ -0,0 +1,785 @@
use crate::cdsl::ast::{bind, var, ExprBuilder, Literal};
use crate::cdsl::inst::{Instruction, InstructionGroup};
use crate::cdsl::xform::{TransformGroupBuilder, TransformGroups};
use crate::shared::OperandKinds;
use crate::shared::types::Float::{F32, F64};
use crate::shared::types::Int::{I16, I32, I64, I8};
pub fn define(insts: &InstructionGroup, immediates: &OperandKinds) -> TransformGroups {
let mut narrow = TransformGroupBuilder::new(
"narrow",
r#"
Legalize instructions by narrowing.
The transformations in the 'narrow' group work by expressing
instructions in terms of smaller types. Operations on vector types are
expressed in terms of vector types with fewer lanes, and integer
operations are expressed in terms of smaller integer types.
"#,
);
let mut widen = TransformGroupBuilder::new(
"widen",
r#"
Legalize instructions by widening.
The transformations in the 'widen' group work by expressing
instructions in terms of larger types.
"#,
);
let mut expand = TransformGroupBuilder::new(
"expand",
r#"
Legalize instructions by expansion.
Rewrite instructions in terms of other instructions, generally
operating on the same types as the original instructions.
"#,
);
// List of instructions.
let band = insts.by_name("band");
let band_imm = insts.by_name("band_imm");
let band_not = insts.by_name("band_not");
let bint = insts.by_name("bint");
let bitrev = insts.by_name("bitrev");
let bnot = insts.by_name("bnot");
let bor = insts.by_name("bor");
let bor_imm = insts.by_name("bor_imm");
let bor_not = insts.by_name("bor_not");
let br_icmp = insts.by_name("br_icmp");
let br_table = insts.by_name("br_table");
let bxor = insts.by_name("bxor");
let bxor_imm = insts.by_name("bxor_imm");
let bxor_not = insts.by_name("bxor_not");
let cls = insts.by_name("cls");
let clz = insts.by_name("clz");
let ctz = insts.by_name("ctz");
let fabs = insts.by_name("fabs");
let f32const = insts.by_name("f32const");
let f64const = insts.by_name("f64const");
let fcopysign = insts.by_name("fcopysign");
let fneg = insts.by_name("fneg");
let iadd = insts.by_name("iadd");
let iadd_carry = insts.by_name("iadd_carry");
let iadd_cin = insts.by_name("iadd_cin");
let iadd_cout = insts.by_name("iadd_cout");
let iadd_imm = insts.by_name("iadd_imm");
let icmp = insts.by_name("icmp");
let icmp_imm = insts.by_name("icmp_imm");
let iconcat = insts.by_name("iconcat");
let iconst = insts.by_name("iconst");
let ifcmp = insts.by_name("ifcmp");
let ifcmp_imm = insts.by_name("ifcmp_imm");
let imul = insts.by_name("imul");
let imul_imm = insts.by_name("imul_imm");
let ireduce = insts.by_name("ireduce");
let irsub_imm = insts.by_name("irsub_imm");
let ishl = insts.by_name("ishl");
let ishl_imm = insts.by_name("ishl_imm");
let isplit = insts.by_name("isplit");
let istore8 = insts.by_name("istore8");
let istore16 = insts.by_name("istore16");
let isub = insts.by_name("isub");
let isub_bin = insts.by_name("isub_bin");
let isub_borrow = insts.by_name("isub_borrow");
let isub_bout = insts.by_name("isub_bout");
let load = insts.by_name("load");
let popcnt = insts.by_name("popcnt");
let rotl = insts.by_name("rotl");
let rotl_imm = insts.by_name("rotl_imm");
let rotr = insts.by_name("rotr");
let rotr_imm = insts.by_name("rotr_imm");
let sdiv = insts.by_name("sdiv");
let sdiv_imm = insts.by_name("sdiv_imm");
let select = insts.by_name("select");
let sextend = insts.by_name("sextend");
let sshr = insts.by_name("sshr");
let sshr_imm = insts.by_name("sshr_imm");
let srem = insts.by_name("srem");
let srem_imm = insts.by_name("srem_imm");
let store = insts.by_name("store");
let udiv = insts.by_name("udiv");
let udiv_imm = insts.by_name("udiv_imm");
let uextend = insts.by_name("uextend");
let uload8 = insts.by_name("uload8");
let uload16 = insts.by_name("uload16");
let ushr = insts.by_name("ushr");
let ushr_imm = insts.by_name("ushr_imm");
let urem = insts.by_name("urem");
let urem_imm = insts.by_name("urem_imm");
let trapif = insts.by_name("trapif");
let trapnz = insts.by_name("trapnz");
let trapz = insts.by_name("trapz");
// Custom expansions for memory objects.
expand.custom_legalize(insts.by_name("global_value"), "expand_global_value");
expand.custom_legalize(insts.by_name("heap_addr"), "expand_heap_addr");
expand.custom_legalize(insts.by_name("table_addr"), "expand_table_addr");
// Custom expansions for calls.
expand.custom_legalize(insts.by_name("call"), "expand_call");
// Custom expansions that need to change the CFG.
// TODO: Add sufficient XForm syntax that we don't need to hand-code these.
expand.custom_legalize(trapz, "expand_cond_trap");
expand.custom_legalize(trapnz, "expand_cond_trap");
expand.custom_legalize(br_table, "expand_br_table");
expand.custom_legalize(select, "expand_select");
// Custom expansions for floating point constants.
// These expansions require bit-casting or creating constant pool entries.
expand.custom_legalize(f32const, "expand_fconst");
expand.custom_legalize(f64const, "expand_fconst");
// Custom expansions for stack memory accesses.
expand.custom_legalize(insts.by_name("stack_load"), "expand_stack_load");
expand.custom_legalize(insts.by_name("stack_store"), "expand_stack_store");
// List of immediates.
let imm64 = immediates.by_name("imm64");
let ieee32 = immediates.by_name("ieee32");
let ieee64 = immediates.by_name("ieee64");
let intcc = immediates.by_name("intcc");
// List of variables to reuse in patterns.
let x = var("x");
let y = var("y");
let z = var("z");
let a = var("a");
let a1 = var("a1");
let a2 = var("a2");
let a3 = var("a3");
let a4 = var("a4");
let b = var("b");
let b1 = var("b1");
let b2 = var("b2");
let b3 = var("b3");
let b4 = var("b4");
let b_in = var("b_in");
let b_int = var("b_int");
let c = var("c");
let c1 = var("c1");
let c2 = var("c2");
let c3 = var("c3");
let c4 = var("c4");
let c_in = var("c_in");
let c_int = var("c_int");
let d = var("d");
let d1 = var("d1");
let d2 = var("d2");
let d3 = var("d3");
let d4 = var("d4");
let e = var("e");
let e1 = var("e1");
let e2 = var("e2");
let e3 = var("e3");
let e4 = var("e4");
let f = var("f");
let f1 = var("f1");
let f2 = var("f2");
let xl = var("xl");
let xh = var("xh");
let yl = var("yl");
let yh = var("yh");
let al = var("al");
let ah = var("ah");
let cc = var("cc");
let ptr = var("ptr");
let flags = var("flags");
let offset = var("off");
narrow.legalize(
def!(a = iadd(x, y)),
vec![
def!((xl, xh) = isplit(x)),
def!((yl, yh) = isplit(y)),
def!((al, c) = iadd_cout(xl, yl)),
def!(ah = iadd_cin(xh, yh, c)),
def!(a = iconcat(al, ah)),
],
);
narrow.legalize(
def!(a = isub(x, y)),
vec![
def!((xl, xh) = isplit(x)),
def!((yl, yh) = isplit(y)),
def!((al, b) = isub_bout(xl, yl)),
def!(ah = isub_bin(xh, yh, b)),
def!(a = iconcat(al, ah)),
],
);
for &bin_op in &[band, bor, bxor] {
narrow.legalize(
def!(a = bin_op(x, y)),
vec![
def!((xl, xh) = isplit(x)),
def!((yl, yh) = isplit(y)),
def!(al = bin_op(xl, yl)),
def!(ah = bin_op(xh, yh)),
def!(a = iconcat(al, ah)),
],
);
}
narrow.legalize(
def!(a = select(c, x, y)),
vec![
def!((xl, xh) = isplit(x)),
def!((yl, yh) = isplit(y)),
def!(al = select(c, xl, yl)),
def!(ah = select(c, xh, yh)),
def!(a = iconcat(al, ah)),
],
);
// Widen instructions with one input operand.
for &op in &[bnot, popcnt] {
for &int_ty in &[I8, I16] {
widen.legalize(
def!(a = op.int_ty(b)),
vec![
def!(x = uextend.I32(b)),
def!(z = op.I32(x)),
def!(a = ireduce.int_ty(z)),
],
);
}
}
// Widen instructions with two input operands.
let mut widen_two_arg = |signed: bool, op: &Instruction| {
for &int_ty in &[I8, I16] {
let sign_ext_op = if signed { sextend } else { uextend };
widen.legalize(
def!(a = op.int_ty(b, c)),
vec![
def!(x = sign_ext_op.I32(b)),
def!(y = sign_ext_op.I32(c)),
def!(z = op.I32(x, y)),
def!(a = ireduce.int_ty(z)),
],
);
}
};
for bin_op in &[
iadd, isub, imul, udiv, urem, band, bor, bxor, band_not, bor_not, bxor_not,
] {
widen_two_arg(false, bin_op);
}
for bin_op in &[sdiv, srem] {
widen_two_arg(true, bin_op);
}
// Widen instructions using immediate operands.
let mut widen_imm = |signed: bool, op: &Instruction| {
for &int_ty in &[I8, I16] {
let sign_ext_op = if signed { sextend } else { uextend };
widen.legalize(
def!(a = op.int_ty(b, c)),
vec![
def!(x = sign_ext_op.I32(b)),
def!(z = op.I32(x, c)),
def!(a = ireduce.int_ty(z)),
],
);
}
};
for bin_op in &[
iadd_imm, imul_imm, udiv_imm, urem_imm, band_imm, bor_imm, bxor_imm, irsub_imm,
] {
widen_imm(false, bin_op);
}
for bin_op in &[sdiv_imm, srem_imm] {
widen_imm(true, bin_op);
}
for &(int_ty, num) in &[(I8, 24), (I16, 16)] {
let imm = Literal::constant(imm64, -num);
widen.legalize(
def!(a = clz.int_ty(b)),
vec![
def!(c = uextend.I32(b)),
def!(d = clz.I32(c)),
def!(e = iadd_imm(d, imm)),
def!(a = ireduce.int_ty(e)),
],
);
widen.legalize(
def!(a = cls.int_ty(b)),
vec![
def!(c = sextend.I32(b)),
def!(d = cls.I32(c)),
def!(e = iadd_imm(d, imm)),
def!(a = ireduce.int_ty(e)),
],
);
}
for &(int_ty, num) in &[(I8, 1 << 8), (I16, 1 << 16)] {
let num = Literal::constant(imm64, num);
widen.legalize(
def!(a = ctz.int_ty(b)),
vec![
def!(c = uextend.I32(b)),
// When `b` is zero, returns the size of x in bits.
def!(d = bor_imm(c, num)),
def!(e = ctz.I32(d)),
def!(a = ireduce.int_ty(e)),
],
);
}
// iconst
for &int_ty in &[I8, I16] {
widen.legalize(
def!(a = iconst.int_ty(b)),
vec![def!(c = iconst.I32(b)), def!(a = ireduce.int_ty(c))],
);
}
for &extend_op in &[uextend, sextend] {
// The sign extension operators have two typevars: the result has one and controls the
// instruction, then the input has one.
let bound = bind(bind(extend_op, I16), I8);
widen.legalize(
def!(a = bound(b)),
vec![def!(c = extend_op.I32(b)), def!(a = ireduce(c))],
);
}
widen.legalize(
def!(store.I8(flags, a, ptr, offset)),
vec![
def!(b = uextend.I32(a)),
def!(istore8(flags, b, ptr, offset)),
],
);
widen.legalize(
def!(store.I16(flags, a, ptr, offset)),
vec![
def!(b = uextend.I32(a)),
def!(istore16(flags, b, ptr, offset)),
],
);
widen.legalize(
def!(a = load.I8(flags, ptr, offset)),
vec![
def!(b = uload8.I32(flags, ptr, offset)),
def!(a = ireduce(b)),
],
);
widen.legalize(
def!(a = load.I16(flags, ptr, offset)),
vec![
def!(b = uload16.I32(flags, ptr, offset)),
def!(a = ireduce(b)),
],
);
for &int_ty in &[I8, I16] {
widen.legalize(
def!(br_table.int_ty(x, y, z)),
vec![def!(b = uextend.I32(x)), def!(br_table(b, y, z))],
);
}
for &int_ty in &[I8, I16] {
widen.legalize(
def!(a = bint.int_ty(b)),
vec![def!(x = bint.I32(b)), def!(a = ireduce.int_ty(x))],
);
}
for &int_ty in &[I8, I16] {
for &op in &[ishl, ishl_imm, ushr, ushr_imm] {
widen.legalize(
def!(a = op.int_ty(b, c)),
vec![
def!(x = uextend.I32(b)),
def!(z = op.I32(x, c)),
def!(a = ireduce.int_ty(z)),
],
);
}
for &op in &[sshr, sshr_imm] {
widen.legalize(
def!(a = op.int_ty(b, c)),
vec![
def!(x = sextend.I32(b)),
def!(z = op.I32(x, c)),
def!(a = ireduce.int_ty(z)),
],
);
}
for cc in &["eq", "ne", "ugt", "ult", "uge", "ule"] {
let w_cc = Literal::enumerator_for(intcc, cc);
widen.legalize(
def!(a = icmp_imm.int_ty(w_cc, b, c)),
vec![def!(x = uextend.I32(b)), def!(a = icmp_imm(w_cc, x, c))],
);
widen.legalize(
def!(a = icmp.int_ty(w_cc, b, c)),
vec![
def!(x = uextend.I32(b)),
def!(y = uextend.I32(c)),
def!(a = icmp.I32(w_cc, x, y)),
],
);
}
for cc in &["sgt", "slt", "sge", "sle"] {
let w_cc = Literal::enumerator_for(intcc, cc);
widen.legalize(
def!(a = icmp_imm.int_ty(w_cc, b, c)),
vec![def!(x = sextend.I32(b)), def!(a = icmp_imm(w_cc, x, c))],
);
widen.legalize(
def!(a = icmp.int_ty(w_cc, b, c)),
vec![
def!(x = sextend.I32(b)),
def!(y = sextend.I32(c)),
def!(a = icmp(w_cc, x, y)),
],
);
}
}
// Expand integer operations with carry for RISC architectures that don't have
// the flags.
let intcc_ult = Literal::enumerator_for(intcc, "ult");
expand.legalize(
def!((a, c) = iadd_cout(x, y)),
vec![def!(a = iadd(x, y)), def!(c = icmp(intcc_ult, a, x))],
);
let intcc_ugt = Literal::enumerator_for(intcc, "ugt");
expand.legalize(
def!((a, b) = isub_bout(x, y)),
vec![def!(a = isub(x, y)), def!(b = icmp(intcc_ugt, a, x))],
);
expand.legalize(
def!(a = iadd_cin(x, y, c)),
vec![
def!(a1 = iadd(x, y)),
def!(c_int = bint(c)),
def!(a = iadd(a1, c_int)),
],
);
expand.legalize(
def!(a = isub_bin(x, y, b)),
vec![
def!(a1 = isub(x, y)),
def!(b_int = bint(b)),
def!(a = isub(a1, b_int)),
],
);
expand.legalize(
def!((a, c) = iadd_carry(x, y, c_in)),
vec![
def!((a1, c1) = iadd_cout(x, y)),
def!(c_int = bint(c_in)),
def!((a, c2) = iadd_cout(a1, c_int)),
def!(c = bor(c1, c2)),
],
);
expand.legalize(
def!((a, b) = isub_borrow(x, y, b_in)),
vec![
def!((a1, b1) = isub_bout(x, y)),
def!(b_int = bint(b_in)),
def!((a, b2) = isub_bout(a1, b_int)),
def!(b = bor(b1, b2)),
],
);
// Expansions for immediate operands that are out of range.
for &(inst_imm, inst) in &[
(iadd_imm, iadd),
(imul_imm, imul),
(sdiv_imm, sdiv),
(udiv_imm, udiv),
(srem_imm, srem),
(urem_imm, urem),
(band_imm, band),
(bor_imm, bor),
(bxor_imm, bxor),
(ifcmp_imm, ifcmp),
] {
expand.legalize(
def!(a = inst_imm(x, y)),
vec![def!(a1 = iconst(y)), def!(a = inst(x, a1))],
);
}
expand.legalize(
def!(a = irsub_imm(y, x)),
vec![def!(a1 = iconst(x)), def!(a = isub(a1, y))],
);
// Rotates and shifts.
for &(inst_imm, inst) in &[
(rotl_imm, rotl),
(rotr_imm, rotr),
(ishl_imm, ishl),
(sshr_imm, sshr),
(ushr_imm, ushr),
] {
expand.legalize(
def!(a = inst_imm(x, y)),
vec![def!(a1 = iconst.I32(y)), def!(a = inst(x, a1))],
);
}
expand.legalize(
def!(a = icmp_imm(cc, x, y)),
vec![def!(a1 = iconst(y)), def!(a = icmp(cc, x, a1))],
);
//# Expansions for *_not variants of bitwise ops.
for &(inst_not, inst) in &[(band_not, band), (bor_not, bor), (bxor_not, bxor)] {
expand.legalize(
def!(a = inst_not(x, y)),
vec![def!(a1 = bnot(y)), def!(a = inst(x, a1))],
);
}
//# Expand bnot using xor.
let minus_one = Literal::constant(imm64, -1);
expand.legalize(
def!(a = bnot(x)),
vec![def!(y = iconst(minus_one)), def!(a = bxor(x, y))],
);
//# Expand bitrev
//# Adapted from Stack Overflow.
//# https://stackoverflow.com/questions/746171/most-efficient-algorithm-for-bit-reversal-from-msb-lsb-to-lsb-msb-in-c
let imm64_1 = Literal::constant(imm64, 1);
let imm64_2 = Literal::constant(imm64, 2);
let imm64_4 = Literal::constant(imm64, 4);
widen.legalize(
def!(a = bitrev.I8(x)),
vec![
def!(a1 = band_imm(x, Literal::constant(imm64, 0xaa))),
def!(a2 = ushr_imm(a1, imm64_1)),
def!(a3 = band_imm(x, Literal::constant(imm64, 0x55))),
def!(a4 = ishl_imm(a3, imm64_1)),
def!(b = bor(a2, a4)),
def!(b1 = band_imm(b, Literal::constant(imm64, 0xcc))),
def!(b2 = ushr_imm(b1, imm64_2)),
def!(b3 = band_imm(b, Literal::constant(imm64, 0x33))),
def!(b4 = ishl_imm(b3, imm64_2)),
def!(c = bor(b2, b4)),
def!(c1 = band_imm(c, Literal::constant(imm64, 0xf0))),
def!(c2 = ushr_imm(c1, imm64_4)),
def!(c3 = band_imm(c, Literal::constant(imm64, 0x0f))),
def!(c4 = ishl_imm(c3, imm64_4)),
def!(a = bor(c2, c4)),
],
);
let imm64_8 = Literal::constant(imm64, 8);
widen.legalize(
def!(a = bitrev.I16(x)),
vec![
def!(a1 = band_imm(x, Literal::constant(imm64, 0xaaaa))),
def!(a2 = ushr_imm(a1, imm64_1)),
def!(a3 = band_imm(x, Literal::constant(imm64, 0x5555))),
def!(a4 = ishl_imm(a3, imm64_1)),
def!(b = bor(a2, a4)),
def!(b1 = band_imm(b, Literal::constant(imm64, 0xcccc))),
def!(b2 = ushr_imm(b1, imm64_2)),
def!(b3 = band_imm(b, Literal::constant(imm64, 0x3333))),
def!(b4 = ishl_imm(b3, imm64_2)),
def!(c = bor(b2, b4)),
def!(c1 = band_imm(c, Literal::constant(imm64, 0xf0f0))),
def!(c2 = ushr_imm(c1, imm64_4)),
def!(c3 = band_imm(c, Literal::constant(imm64, 0x0f0f))),
def!(c4 = ishl_imm(c3, imm64_4)),
def!(d = bor(c2, c4)),
def!(d1 = band_imm(d, Literal::constant(imm64, 0xff00))),
def!(d2 = ushr_imm(d1, imm64_8)),
def!(d3 = band_imm(d, Literal::constant(imm64, 0x00ff))),
def!(d4 = ishl_imm(d3, imm64_8)),
def!(a = bor(d2, d4)),
],
);
let imm64_16 = Literal::constant(imm64, 16);
expand.legalize(
def!(a = bitrev.I32(x)),
vec![
def!(a1 = band_imm(x, Literal::constant(imm64, 0xaaaaaaaa))),
def!(a2 = ushr_imm(a1, imm64_1)),
def!(a3 = band_imm(x, Literal::constant(imm64, 0x55555555))),
def!(a4 = ishl_imm(a3, imm64_1)),
def!(b = bor(a2, a4)),
def!(b1 = band_imm(b, Literal::constant(imm64, 0xcccccccc))),
def!(b2 = ushr_imm(b1, imm64_2)),
def!(b3 = band_imm(b, Literal::constant(imm64, 0x33333333))),
def!(b4 = ishl_imm(b3, imm64_2)),
def!(c = bor(b2, b4)),
def!(c1 = band_imm(c, Literal::constant(imm64, 0xf0f0f0f0))),
def!(c2 = ushr_imm(c1, imm64_4)),
def!(c3 = band_imm(c, Literal::constant(imm64, 0x0f0f0f0f))),
def!(c4 = ishl_imm(c3, imm64_4)),
def!(d = bor(c2, c4)),
def!(d1 = band_imm(d, Literal::constant(imm64, 0xff00ff00))),
def!(d2 = ushr_imm(d1, imm64_8)),
def!(d3 = band_imm(d, Literal::constant(imm64, 0x00ff00ff))),
def!(d4 = ishl_imm(d3, imm64_8)),
def!(e = bor(d2, d4)),
def!(e1 = ushr_imm(e, imm64_16)),
def!(e2 = ishl_imm(e, imm64_16)),
def!(a = bor(e1, e2)),
],
);
#[allow(overflowing_literals)]
let imm64_0xaaaaaaaaaaaaaaaa = Literal::constant(imm64, 0xaaaaaaaaaaaaaaaa);
let imm64_0x5555555555555555 = Literal::constant(imm64, 0x5555555555555555);
#[allow(overflowing_literals)]
let imm64_0xcccccccccccccccc = Literal::constant(imm64, 0xcccccccccccccccc);
let imm64_0x3333333333333333 = Literal::constant(imm64, 0x3333333333333333);
#[allow(overflowing_literals)]
let imm64_0xf0f0f0f0f0f0f0f0 = Literal::constant(imm64, 0xf0f0f0f0f0f0f0f0);
let imm64_0x0f0f0f0f0f0f0f0f = Literal::constant(imm64, 0x0f0f0f0f0f0f0f0f);
#[allow(overflowing_literals)]
let imm64_0xff00ff00ff00ff00 = Literal::constant(imm64, 0xff00ff00ff00ff00);
let imm64_0x00ff00ff00ff00ff = Literal::constant(imm64, 0x00ff00ff00ff00ff);
#[allow(overflowing_literals)]
let imm64_0xffff0000ffff0000 = Literal::constant(imm64, 0xffff0000ffff0000);
let imm64_0x0000ffff0000ffff = Literal::constant(imm64, 0x0000ffff0000ffff);
let imm64_32 = Literal::constant(imm64, 32);
expand.legalize(
def!(a = bitrev.I64(x)),
vec![
def!(a1 = band_imm(x, imm64_0xaaaaaaaaaaaaaaaa)),
def!(a2 = ushr_imm(a1, imm64_1)),
def!(a3 = band_imm(x, imm64_0x5555555555555555)),
def!(a4 = ishl_imm(a3, imm64_1)),
def!(b = bor(a2, a4)),
def!(b1 = band_imm(b, imm64_0xcccccccccccccccc)),
def!(b2 = ushr_imm(b1, imm64_2)),
def!(b3 = band_imm(b, imm64_0x3333333333333333)),
def!(b4 = ishl_imm(b3, imm64_2)),
def!(c = bor(b2, b4)),
def!(c1 = band_imm(c, imm64_0xf0f0f0f0f0f0f0f0)),
def!(c2 = ushr_imm(c1, imm64_4)),
def!(c3 = band_imm(c, imm64_0x0f0f0f0f0f0f0f0f)),
def!(c4 = ishl_imm(c3, imm64_4)),
def!(d = bor(c2, c4)),
def!(d1 = band_imm(d, imm64_0xff00ff00ff00ff00)),
def!(d2 = ushr_imm(d1, imm64_8)),
def!(d3 = band_imm(d, imm64_0x00ff00ff00ff00ff)),
def!(d4 = ishl_imm(d3, imm64_8)),
def!(e = bor(d2, d4)),
def!(e1 = band_imm(e, imm64_0xffff0000ffff0000)),
def!(e2 = ushr_imm(e1, imm64_16)),
def!(e3 = band_imm(e, imm64_0x0000ffff0000ffff)),
def!(e4 = ishl_imm(e3, imm64_16)),
def!(f = bor(e2, e4)),
def!(f1 = ushr_imm(f, imm64_32)),
def!(f2 = ishl_imm(f, imm64_32)),
def!(a = bor(f1, f2)),
],
);
// Floating-point sign manipulations.
for &(ty, const_inst, minus_zero) in &[
(F32, f32const, &Literal::bits(ieee32, 0x80000000)),
(F64, f64const, &Literal::bits(ieee64, 0x8000000000000000)),
] {
expand.legalize(
def!(a = fabs.ty(x)),
vec![def!(b = const_inst(minus_zero)), def!(a = band_not(x, b))],
);
expand.legalize(
def!(a = fneg.ty(x)),
vec![def!(b = const_inst(minus_zero)), def!(a = bxor(x, b))],
);
expand.legalize(
def!(a = fcopysign.ty(x, y)),
vec![
def!(b = const_inst(minus_zero)),
def!(a1 = band_not(x, b)),
def!(a2 = band(y, b)),
def!(a = bor(a1, a2)),
],
);
}
expand.custom_legalize(br_icmp, "expand_br_icmp");
let mut groups = TransformGroups::new();
narrow.finish_and_add_to(&mut groups);
let expand_id = expand.finish_and_add_to(&mut groups);
// Expansions using CPU flags.
let mut expand_flags = TransformGroupBuilder::new(
"expand_flags",
r#"
Instruction expansions for architectures with flags.
Expand some instructions using CPU flags, then fall back to the normal
expansions. Not all architectures support CPU flags, so these patterns
are kept separate.
"#,
)
.chain_with(expand_id);
let imm64_0 = Literal::constant(imm64, 0);
let intcc_ne = Literal::enumerator_for(intcc, "ne");
let intcc_eq = Literal::enumerator_for(intcc, "eq");
expand_flags.legalize(
def!(trapnz(x, c)),
vec![
def!(a = ifcmp_imm(x, imm64_0)),
def!(trapif(intcc_ne, a, c)),
],
);
expand_flags.legalize(
def!(trapz(x, c)),
vec![
def!(a = ifcmp_imm(x, imm64_0)),
def!(trapif(intcc_eq, a, c)),
],
);
expand_flags.finish_and_add_to(&mut groups);
// XXX The order of declarations unfortunately matters to be compatible with the Python code.
// When it's all migrated, we can put this next to the narrow/expand finish_and_add_to calls
// above.
widen.finish_and_add_to(&mut groups);
groups
}

View File

@ -4,6 +4,7 @@ pub mod entities;
pub mod formats;
pub mod immediates;
pub mod instructions;
pub mod legalize;
pub mod settings;
pub mod types;
@ -11,12 +12,14 @@ use crate::cdsl::formats::FormatRegistry;
use crate::cdsl::inst::InstructionGroup;
use crate::cdsl::operands::OperandKind;
use crate::cdsl::settings::SettingGroup;
use crate::cdsl::xform::TransformGroups;
pub struct Definitions {
pub settings: SettingGroup,
pub instructions: InstructionGroup,
pub operand_kinds: OperandKinds,
pub format_registry: FormatRegistry,
pub transform_groups: TransformGroups,
}
pub struct OperandKinds(Vec<OperandKind>);
@ -50,10 +53,14 @@ pub fn define() -> Definitions {
let immediates = OperandKinds(immediates::define());
let entities = OperandKinds(entities::define());
let format_registry = formats::define(&immediates, &entities);
let instructions = instructions::define(&format_registry, &immediates, &entities);
let transform_groups = legalize::define(&instructions, &immediates);
Definitions {
settings: settings::define(),
instructions: instructions::define(&format_registry, &immediates, &entities),
instructions,
operand_kinds: immediates,
format_registry,
transform_groups,
}
}

View File

@ -79,10 +79,10 @@ 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_push();
let s = self.get_indent();
fn get_outdent(&mut self) -> String {
self.indent_pop();
let s = self.get_indent();
self.indent_push();
s
}
@ -98,8 +98,8 @@ impl Formatter {
}
/// Emit a line outdented one level.
pub fn _outdented_line(&mut self, s: &str) {
let new_line = format!("{}{}", self._get_outdent(), s);
pub fn outdented_line(&mut self, s: &str) {
let new_line = format!("{}{}\n", self.get_outdent(), s);
self.lines.push(new_line);
}

View File

@ -31,6 +31,9 @@ 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()
}

File diff suppressed because one or more lines are too long

View File

@ -5,10 +5,8 @@
from __future__ import absolute_import
import argparse
import isa
import gen_settings
import gen_build_deps
import gen_encoding
import gen_legalizer
import gen_binemit
try:
@ -44,9 +42,7 @@ def main():
isas = isa.all_isas()
number_all_instructions(isas)
gen_settings.generate(isas, out_dir)
gen_encoding.generate(isas, out_dir)
gen_legalizer.generate(isas, out_dir)
gen_binemit.generate(isas, out_dir)
gen_build_deps.generate()

View File

@ -162,19 +162,19 @@ class Var(Atom):
Values that are defined only in the destination pattern.
"""
def __init__(self, name, typevar=None):
# type: (str, TypeVar) -> None
def __init__(self, name):
# type: (str) -> None
self.name = name
# The `Def` defining this variable in a source pattern.
self.src_def = None # type: Def
# The `Def` defining this variable in a destination pattern.
self.dst_def = None # type: Def
# TypeVar representing the type of this variable.
self.typevar = typevar # type: TypeVar
self.typevar = None # type: TypeVar
# The original 'typeof(x)' type variable that was created for this Var.
# This one doesn't change. `self.typevar` above may be changed to
# another typevar by type inference.
self.original_typevar = self.typevar # type: TypeVar
self.original_typevar = None # type: TypeVar
def __str__(self):
# type: () -> str

View File

@ -229,16 +229,25 @@ class SettingGroup(object):
.format(self, SettingGroup._current))
SettingGroup._current = None
if globs:
# Ensure that named predicates are ordered in a deterministic way
# that the Rust crate may simply reproduce, by pushing entries into
# a vector that we'll sort by name later.
named_predicates = []
for name, obj in globs.items():
if isinstance(obj, Setting):
assert obj.name is None, obj.name
obj.name = name
if isinstance(obj, Predicate):
self.named_predicates[name] = obj
named_predicates.append((name, obj))
if isinstance(obj, Preset):
assert obj.name is None, obj.name
obj.name = name
named_predicates.sort(key=lambda x: x[0])
for (name, obj) in named_predicates:
self.named_predicates[name] = obj
self.layout()
@staticmethod

View File

@ -1,468 +0,0 @@
"""
Generate legalizer transformations.
The transformations defined in the `cranelift.legalize` module are all of the
macro-expansion form where the input pattern is a single instruction. We
generate a Rust function for each `XFormGroup` which takes a `Cursor` pointing
at the instruction to be legalized. The expanded destination pattern replaces
the input instruction.
"""
from __future__ import absolute_import
from srcgen import Formatter
from collections import defaultdict
from base import instructions
from cdsl.ast import Var
from cdsl.ti import ti_rtl, TypeEnv, get_type_env, TypesEqual,\
InTypeset, WiderOrEq
from unique_table import UniqueTable
from cdsl.typevar import TypeVar
try:
from typing import Sequence, List, Dict, Set, DefaultDict # noqa
from cdsl.isa import TargetISA # noqa
from cdsl.ast import Def, VarAtomMap # noqa
from cdsl.xform import XForm, XFormGroup # noqa
from cdsl.typevar import TypeSet # noqa
from cdsl.ti import TypeConstraint # noqa
except ImportError:
pass
# TypeSet indexes are encoded in 8 bits, with `0xff` reserved.
typeset_limit = 0xff
def gen_typesets_table(fmt, type_sets):
# type: (Formatter, UniqueTable) -> None
"""
Generate the table of ValueTypeSets described by type_sets.
"""
if len(type_sets.table) == 0:
return
fmt.comment('Table of value type sets.')
assert len(type_sets.table) <= typeset_limit, "Too many type sets"
with fmt.indented(
'const TYPE_SETS: [ir::instructions::ValueTypeSet; {}] = ['
.format(len(type_sets.table)), '];'):
for ts in type_sets.table:
with fmt.indented('ir::instructions::ValueTypeSet {', '},'):
ts.emit_fields(fmt)
def get_runtime_typechecks(xform):
# type: (XForm) -> List[TypeConstraint]
"""
Given a XForm build a list of runtime type checks necessary to determine
if it applies. We have 2 types of runtime checks:
1) typevar tv belongs to typeset T - needed for free tvs whose
typeset is constrained by their use in the dst pattern
2) tv1 == tv2 where tv1 and tv2 are derived TVs - caused by unification
of non-bijective functions
"""
check_l = [] # type: List[TypeConstraint]
# 1) Perform ti only on the source RTL. Accumulate any free tvs that have a
# different inferred type in src, compared to the type inferred for both
# src and dst.
symtab = {} # type: VarAtomMap
src_copy = xform.src.copy(symtab)
src_typenv = get_type_env(ti_rtl(src_copy, TypeEnv()))
for v in xform.ti.vars:
if not v.has_free_typevar():
continue
# In rust the local variable containing a free TV associated with var v
# has name typeof_v. We rely on the python TVs having the same name.
assert "typeof_{}".format(v) == xform.ti[v].name
if v not in symtab:
# We can have singleton vars defined only on dst. Ignore them
assert v.get_typevar().singleton_type() is not None
continue
inner_v = symtab[v]
assert isinstance(inner_v, Var)
src_ts = src_typenv[inner_v].get_typeset()
xform_ts = xform.ti[v].get_typeset()
assert xform_ts.issubset(src_ts)
if src_ts != xform_ts:
check_l.append(InTypeset(xform.ti[v], xform_ts))
# 2,3) Add any constraints that appear in xform.ti
check_l.extend(xform.ti.constraints)
return check_l
def emit_runtime_typecheck(check, fmt, type_sets):
# type: (TypeConstraint, Formatter, UniqueTable) -> None
"""
Emit rust code for the given check.
The emitted code is a statement redefining the `predicate` variable like
this:
let predicate = predicate && ...
"""
def build_derived_expr(tv):
# type: (TypeVar) -> str
"""
Build an expression of type Option<Type> corresponding to a concrete
type transformed by the sequence of derivation functions in tv.
We are using Option<Type>, as some constraints may cause an
over/underflow on patterns that do not match them. We want to capture
this without panicking at runtime.
"""
if not tv.is_derived:
assert tv.name.startswith('typeof_')
return "Some({})".format(tv.name)
base_exp = build_derived_expr(tv.base)
if (tv.derived_func == TypeVar.LANEOF):
return "{}.map(|t: crate::ir::Type| t.lane_type())"\
.format(base_exp)
elif (tv.derived_func == TypeVar.ASBOOL):
return "{}.map(|t: crate::ir::Type| t.as_bool())".format(base_exp)
elif (tv.derived_func == TypeVar.HALFWIDTH):
return "{}.and_then(|t: crate::ir::Type| t.half_width())"\
.format(base_exp)
elif (tv.derived_func == TypeVar.DOUBLEWIDTH):
return "{}.and_then(|t: crate::ir::Type| t.double_width())"\
.format(base_exp)
elif (tv.derived_func == TypeVar.HALFVECTOR):
return "{}.and_then(|t: crate::ir::Type| t.half_vector())"\
.format(base_exp)
elif (tv.derived_func == TypeVar.DOUBLEVECTOR):
return "{}.and_then(|t: crate::ir::Type| t.by(2))".format(base_exp)
else:
assert False, "Unknown derived function {}".format(tv.derived_func)
if (isinstance(check, InTypeset)):
assert not check.tv.is_derived
tv = check.tv.name
if check.ts not in type_sets.index:
type_sets.add(check.ts)
ts = type_sets.index[check.ts]
fmt.comment("{} must belong to {}".format(tv, check.ts))
fmt.format(
'let predicate = predicate && TYPE_SETS[{}].contains({});',
ts, tv)
elif (isinstance(check, TypesEqual)):
with fmt.indented(
'let predicate = predicate && match ({}, {}) {{'
.format(build_derived_expr(check.tv1),
build_derived_expr(check.tv2)), '};'):
fmt.line('(Some(a), Some(b)) => a == b,')
fmt.comment('On overflow, constraint doesn\'t appply')
fmt.line('_ => false,')
elif (isinstance(check, WiderOrEq)):
with fmt.indented(
'let predicate = predicate && match ({}, {}) {{'
.format(build_derived_expr(check.tv1),
build_derived_expr(check.tv2)), '};'):
fmt.line('(Some(a), Some(b)) => a.wider_or_equal(b),')
fmt.comment('On overflow, constraint doesn\'t appply')
fmt.line('_ => false,')
else:
assert False, "Unknown check {}".format(check)
def unwrap_inst(iref, node, fmt):
# type: (str, Def, Formatter) -> bool
"""
Given a `Def` node, emit code that extracts all the instruction fields from
`pos.func.dfg[iref]`.
Create local variables named after the `Var` instances in `node`.
Also create a local variable named `predicate` with the value of the
evaluated instruction predicate, or `true` if the node has no predicate.
:param iref: Name of the `Inst` reference to unwrap.
:param node: `Def` node providing variable names.
:returns: True if the instruction arguments were not detached, expecting a
replacement instruction to overwrite the original.
"""
fmt.comment('Unwrap {}'.format(node))
expr = node.expr
iform = expr.inst.format
nvops = iform.num_value_operands
# The tuple of locals to extract is the `Var` instances in `expr.args`.
arg_names = tuple(
arg.name if isinstance(arg, Var) else '_' for arg in expr.args)
with fmt.indented(
'let ({}, predicate) = if let crate::ir::InstructionData::{} {{'
.format(', '.join(map(str, arg_names)), iform.name), '};'):
# Fields are encoded directly.
for f in iform.imm_fields:
fmt.line('{},'.format(f.member))
if nvops == 1:
fmt.line('arg,')
elif iform.has_value_list or nvops > 1:
fmt.line('ref args,')
fmt.line('..')
fmt.outdented_line('} = pos.func.dfg[inst] {')
fmt.line('let func = &pos.func;')
if iform.has_value_list:
fmt.line('let args = args.as_slice(&func.dfg.value_lists);')
elif nvops == 1:
fmt.line('let args = [arg];')
# Generate the values for the tuple.
with fmt.indented('(', ')'):
for opnum, op in enumerate(expr.inst.ins):
if op.is_immediate():
n = expr.inst.imm_opnums.index(opnum)
fmt.format('{},', iform.imm_fields[n].member)
elif op.is_value():
n = expr.inst.value_opnums.index(opnum)
fmt.format('func.dfg.resolve_aliases(args[{}]),', n)
# Evaluate the instruction predicate, if any.
instp = expr.inst_predicate_with_ctrl_typevar()
fmt.line(instp.rust_predicate(0) if instp else 'true')
fmt.outdented_line('} else {')
fmt.line('unreachable!("bad instruction format")')
# Get the types of any variables where it is needed.
for opnum in expr.inst.value_opnums:
v = expr.args[opnum]
if isinstance(v, Var) and v.has_free_typevar():
fmt.format('let typeof_{0} = pos.func.dfg.value_type({0});', v)
# If the node has results, detach the values.
# Place the values in locals.
replace_inst = False
if len(node.defs) > 0:
if node.defs == node.defs[0].dst_def.defs:
# Special case: The instruction replacing node defines the exact
# same values.
fmt.comment(
'Results handled by {}.'
.format(node.defs[0].dst_def))
replace_inst = True
else:
# Boring case: Detach the result values, capture them in locals.
for d in node.defs:
fmt.line('let {};'.format(d))
with fmt.indented('{', '}'):
fmt.line('let r = pos.func.dfg.inst_results(inst);')
for i in range(len(node.defs)):
fmt.line('{} = r[{}];'.format(node.defs[i], i))
for d in node.defs:
if d.has_free_typevar():
fmt.line(
'let typeof_{0} = pos.func.dfg.value_type({0});'
.format(d))
return replace_inst
def wrap_tup(seq):
# type: (Sequence[object]) -> str
tup = tuple(map(str, seq))
if len(tup) == 1:
return tup[0]
else:
return '({})'.format(', '.join(tup))
def is_value_split(node):
# type: (Def) -> bool
"""
Determine if `node` represents one of the value splitting instructions:
`isplit` or `vsplit. These instructions are lowered specially by the
`legalize::split` module.
"""
if len(node.defs) != 2:
return False
return node.expr.inst in (instructions.isplit, instructions.vsplit)
def emit_dst_inst(node, fmt):
# type: (Def, Formatter) -> None
replaced_inst = None # type: str
if is_value_split(node):
# Split instructions are not emitted with the builder, but by calling
# special functions in the `legalizer::split` module. These functions
# will eliminate concat-split patterns.
fmt.line('let curpos = pos.position();')
fmt.line('let srcloc = pos.srcloc();')
fmt.format(
'let {} = split::{}(pos.func, cfg, curpos, srcloc, {});',
wrap_tup(node.defs),
node.expr.inst.snake_name(),
node.expr.args[0])
else:
if len(node.defs) == 0:
# This node doesn't define any values, so just insert the new
# instruction.
builder = 'pos.ins()'
else:
src_def0 = node.defs[0].src_def
if src_def0 and node.defs == src_def0.defs:
# The replacement instruction defines the exact same values as
# the source pattern. Unwrapping would have left the results
# intact.
# Replace the whole instruction.
builder = 'let {} = pos.func.dfg.replace(inst)'.format(
wrap_tup(node.defs))
replaced_inst = 'inst'
else:
# Insert a new instruction.
builder = 'let {} = pos.ins()'.format(wrap_tup(node.defs))
# We may want to reuse some of the detached output values.
if len(node.defs) == 1 and node.defs[0].is_output():
# Reuse the single source result value.
builder += '.with_result({})'.format(node.defs[0])
elif any(d.is_output() for d in node.defs):
# We have some output values to be reused.
array = ', '.join(
('Some({})'.format(d) if d.is_output()
else 'None')
for d in node.defs)
builder += '.with_results([{}])'.format(array)
fmt.line('{}.{};'.format(builder, node.expr.rust_builder(node.defs)))
# If we just replaced an instruction, we need to bump the cursor so
# following instructions are inserted *after* the replaced instruction.
if replaced_inst:
with fmt.indented(
'if pos.current_inst() == Some({}) {{'
.format(replaced_inst), '}'):
fmt.line('pos.next_inst();')
def gen_xform(xform, fmt, type_sets):
# type: (XForm, Formatter, UniqueTable) -> None
"""
Emit code for `xform`, assuming that the opcode of xform's root instruction
has already been matched.
`inst: Inst` is the variable to be replaced. It is pointed to by `pos:
Cursor`.
`dfg: DataFlowGraph` is available and mutable.
"""
# Unwrap the source instruction, create local variables for the input
# variables.
replace_inst = unwrap_inst('inst', xform.src.rtl[0], fmt)
# Emit any runtime checks.
# These will rebind `predicate` emitted by unwrap_inst().
for check in get_runtime_typechecks(xform):
emit_runtime_typecheck(check, fmt, type_sets)
# Guard the actual expansion by `predicate`.
with fmt.indented('if predicate {', '}'):
# If we're going to delete `inst`, we need to detach its results first
# so they can be reattached during pattern expansion.
if not replace_inst:
fmt.line('pos.func.dfg.clear_results(inst);')
# Emit the destination pattern.
for dst in xform.dst.rtl:
emit_dst_inst(dst, fmt)
# Delete the original instruction if we didn't have an opportunity to
# replace it.
if not replace_inst:
fmt.line('let removed = pos.remove_inst();')
fmt.line('debug_assert_eq!(removed, inst);')
fmt.line('return true;')
def gen_xform_group(xgrp, fmt, type_sets):
# type: (XFormGroup, Formatter, UniqueTable) -> None
fmt.doc_comment("Legalize `inst`.")
fmt.line('#[allow(unused_variables,unused_assignments,non_snake_case)]')
with fmt.indented('pub fn {}('.format(xgrp.name)):
fmt.line('inst: crate::ir::Inst,')
fmt.line('func: &mut crate::ir::Function,')
fmt.line('cfg: &mut crate::flowgraph::ControlFlowGraph,')
fmt.line('isa: &crate::isa::TargetIsa,')
with fmt.indented(') -> bool {', '}'):
fmt.line('use crate::ir::InstBuilder;')
fmt.line('use crate::cursor::{Cursor, FuncCursor};')
fmt.line('let mut pos = FuncCursor::new(func).at_inst(inst);')
fmt.line('pos.use_srcloc(inst);')
# Group the xforms by opcode so we can generate a big switch.
# Preserve ordering.
xforms = defaultdict(list) # type: DefaultDict[str, List[XForm]]
for xform in xgrp.xforms:
inst = xform.src.rtl[0].expr.inst
xforms[inst.camel_name].append(xform)
with fmt.indented('{', '}'):
with fmt.indented('match pos.func.dfg[inst].opcode() {', '}'):
for camel_name in sorted(xforms.keys()):
with fmt.indented(
'ir::Opcode::{} => {{'.format(camel_name), '}'):
for xform in xforms[camel_name]:
gen_xform(xform, fmt, type_sets)
# Emit the custom transforms. The Rust compiler will complain
# about any overlap with the normal xforms.
for inst, funcname in xgrp.custom.items():
with fmt.indented(
'ir::Opcode::{} => {{'
.format(inst.camel_name), '}'):
fmt.format('{}(inst, pos.func, cfg, isa);', funcname)
fmt.line('return true;')
# We'll assume there are uncovered opcodes.
fmt.line('_ => {},')
# If we fall through, nothing was expanded. Call the chain if any.
if xgrp.chain:
fmt.format('{}(inst, pos.func, cfg, isa)', xgrp.chain.rust_name())
else:
fmt.line('false')
def gen_isa(isa, fmt, shared_groups):
# type: (TargetISA, Formatter, Set[XFormGroup]) -> None
"""
Generate legalization functions for `isa` and add any shared `XFormGroup`s
encountered to `shared_groups`.
Generate `TYPE_SETS` and `LEGALIZE_ACTION` tables.
"""
type_sets = UniqueTable()
for xgrp in isa.legalize_codes.keys():
if xgrp.isa is None:
shared_groups.add(xgrp)
else:
assert xgrp.isa == isa
gen_xform_group(xgrp, fmt, type_sets)
gen_typesets_table(fmt, type_sets)
with fmt.indented(
'pub static LEGALIZE_ACTIONS: [isa::Legalize; {}] = ['
.format(len(isa.legalize_codes)), '];'):
for xgrp in isa.legalize_codes.keys():
fmt.format('{},', xgrp.rust_name())
def generate(isas, out_dir):
# type: (Sequence[TargetISA], str) -> None
shared_groups = set() # type: Set[XFormGroup]
for isa in isas:
fmt = Formatter()
gen_isa(isa, fmt, shared_groups)
fmt.update_file('legalize-{}.rs'.format(isa.name), out_dir)
# Shared xform groups.
fmt = Formatter()
type_sets = UniqueTable()
for xgrp in sorted(shared_groups, key=lambda g: g.name):
gen_xform_group(xgrp, fmt, type_sets)
gen_typesets_table(fmt, type_sets)
fmt.update_file('legalizer.rs', out_dir)

View File

@ -1,335 +0,0 @@
"""
Generate sources with settings.
"""
from __future__ import absolute_import
import srcgen
from unique_table import UniqueSeqTable
import constant_hash
from cdsl import camel_case
from cdsl.settings import BoolSetting, NumSetting, EnumSetting
from base import settings
try:
from typing import Sequence, Set, Tuple, List, Union, TYPE_CHECKING # noqa
if TYPE_CHECKING:
from cdsl.isa import TargetISA # noqa
from cdsl.settings import Setting, Preset, SettingGroup # noqa
from cdsl.predicates import Predicate, PredContext # noqa
except ImportError:
pass
def gen_to_and_from_str(ty, values, fmt):
# type: (str, Tuple[str, ...], srcgen.Formatter) -> None
"""
Emit Display and FromStr implementations for enum settings.
"""
with fmt.indented('impl fmt::Display for {} {{'.format(ty), '}'):
with fmt.indented(
'fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {',
'}'):
with fmt.indented('f.write_str(match *self {', '})'):
for v in values:
fmt.line('{}::{} => "{}",'
.format(ty, camel_case(v), v))
with fmt.indented('impl str::FromStr for {} {{'.format(ty), '}'):
fmt.line('type Err = ();')
with fmt.indented(
'fn from_str(s: &str) -> Result<Self, Self::Err> {',
'}'):
with fmt.indented('match s {', '}'):
for v in values:
fmt.line('"{}" => Ok({}::{}),'
.format(v, ty, camel_case(v)))
fmt.line('_ => Err(()),')
def gen_enum_types(sgrp, fmt):
# type: (SettingGroup, srcgen.Formatter) -> None
"""
Emit enum types for any enum settings.
"""
for setting in sgrp.settings:
if not isinstance(setting, EnumSetting):
continue
ty = camel_case(setting.name)
fmt.doc_comment('Values for `{}`.'.format(setting))
fmt.line('#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]')
with fmt.indented('pub enum {} {{'.format(ty), '}'):
for v in setting.values:
fmt.doc_comment('`{}`.'.format(v))
fmt.line(camel_case(v) + ',')
gen_to_and_from_str(ty, setting.values, fmt)
def gen_getter(setting, sgrp, fmt):
# type: (Setting, SettingGroup, srcgen.Formatter) -> None
"""
Emit a getter function for `setting`.
"""
fmt.doc_comment(setting.__doc__)
if isinstance(setting, BoolSetting):
proto = 'pub fn {}(&self) -> bool'.format(setting.name)
with fmt.indented(proto + ' {', '}'):
fmt.line(
'self.numbered_predicate({})'
.format(sgrp.predicate_number[setting]))
elif isinstance(setting, NumSetting):
proto = 'pub fn {}(&self) -> u8'.format(setting.name)
with fmt.indented(proto + ' {', '}'):
fmt.line('self.bytes[{}]'.format(setting.byte_offset))
elif isinstance(setting, EnumSetting):
ty = camel_case(setting.name)
proto = 'pub fn {}(&self) -> {}'.format(setting.name, ty)
with fmt.indented(proto + ' {', '}'):
m = srcgen.Match('self.bytes[{}]'.format(setting.byte_offset))
for i, v in enumerate(setting.values):
m.arm(str(i), [], '{}::{}'.format(ty, camel_case(v)))
m.arm('_', [], 'panic!("Invalid enum value")')
fmt.match(m)
else:
raise AssertionError("Unknown setting kind")
def gen_pred_getter(name, pred, sgrp, fmt):
# type: (str, Predicate, SettingGroup, srcgen.Formatter) -> None
"""
Emit a getter for a named pre-computed predicate.
"""
fmt.doc_comment('Computed predicate `{}`.'.format(pred.rust_predicate(0)))
proto = 'pub fn {}(&self) -> bool'.format(name)
with fmt.indented(proto + ' {', '}'):
fmt.line(
'self.numbered_predicate({})'
.format(sgrp.predicate_number[pred]))
def gen_getters(sgrp, fmt):
# type: (SettingGroup, srcgen.Formatter) -> None
"""
Emit getter functions for all the settings in fmt.
"""
fmt.doc_comment("User-defined settings.")
fmt.line('#[allow(dead_code)]')
with fmt.indented('impl Flags {', '}'):
fmt.doc_comment('Get a view of the boolean predicates.')
with fmt.indented(
'pub fn predicate_view(&self) -> '
'crate::settings::PredicateView {', '}'):
fmt.format(
'crate::settings::PredicateView::new(&self.bytes[{}..])',
sgrp.boolean_offset)
if sgrp.settings:
fmt.doc_comment('Dynamic numbered predicate getter.')
with fmt.indented(
'fn numbered_predicate(&self, p: usize) -> bool {', '}'):
fmt.line(
'self.bytes[{} + p / 8] & (1 << (p % 8)) != 0'
.format(sgrp.boolean_offset))
for setting in sgrp.settings:
gen_getter(setting, sgrp, fmt)
for name, pred in sgrp.named_predicates.items():
gen_pred_getter(name, pred, sgrp, fmt)
def gen_descriptors(sgrp, fmt):
# type: (SettingGroup, srcgen.Formatter) -> None
"""
Generate the DESCRIPTORS, ENUMERATORS, and PRESETS tables.
"""
enums = UniqueSeqTable()
with fmt.indented(
'static DESCRIPTORS: [detail::Descriptor; {}] = ['
.format(len(sgrp.settings) + len(sgrp.presets)),
'];'):
for idx, setting in enumerate(sgrp.settings):
setting.descriptor_index = idx
with fmt.indented('detail::Descriptor {', '},'):
fmt.line('name: "{}",'.format(setting.name))
fmt.line('offset: {},'.format(setting.byte_offset))
if isinstance(setting, BoolSetting):
fmt.line(
'detail: detail::Detail::Bool {{ bit: {} }},'
.format(setting.bit_offset))
elif isinstance(setting, NumSetting):
fmt.line('detail: detail::Detail::Num,')
elif isinstance(setting, EnumSetting):
offs = enums.add(setting.values)
fmt.line(
'detail: detail::Detail::Enum ' +
'{{ last: {}, enumerators: {} }},'
.format(len(setting.values)-1, offs))
else:
raise AssertionError("Unknown setting kind")
for idx, preset in enumerate(sgrp.presets):
preset.descriptor_index = len(sgrp.settings) + idx
with fmt.indented('detail::Descriptor {', '},'):
fmt.line('name: "{}",'.format(preset.name))
fmt.line('offset: {},'.format(idx * sgrp.settings_size))
fmt.line('detail: detail::Detail::Preset,')
with fmt.indented(
'static ENUMERATORS: [&str; {}] = ['
.format(len(enums.table)),
'];'):
for txt in enums.table:
fmt.line('"{}",'.format(txt))
def hash_setting(s):
# type: (Union[Setting, Preset]) -> int
return constant_hash.simple_hash(s.name)
hash_elems = [] # type: List[Union[Setting, Preset]]
hash_elems.extend(sgrp.settings)
hash_elems.extend(sgrp.presets)
hash_table = constant_hash.compute_quadratic(hash_elems, hash_setting)
with fmt.indented(
'static HASH_TABLE: [u16; {}] = ['
.format(len(hash_table)),
'];'):
for h in hash_table:
if h is None:
fmt.line('0xffff,')
else:
fmt.line('{},'.format(h.descriptor_index))
with fmt.indented(
'static PRESETS: [(u8, u8); {}] = ['
.format(len(sgrp.presets) * sgrp.settings_size),
'];'):
for preset in sgrp.presets:
fmt.comment(preset.name)
for mask, value in preset.layout():
fmt.format('(0b{:08b}, 0b{:08b}),', mask, value)
def gen_template(sgrp, fmt):
# type: (SettingGroup, srcgen.Formatter) -> None
"""
Emit a Template constant.
"""
v = [0] * sgrp.settings_size
for setting in sgrp.settings:
v[setting.byte_offset] |= setting.default_byte()
with fmt.indented(
'static TEMPLATE: detail::Template = detail::Template {', '};'):
fmt.line('name: "{}",'.format(sgrp.name))
fmt.line('descriptors: &DESCRIPTORS,')
fmt.line('enumerators: &ENUMERATORS,')
fmt.line('hash_table: &HASH_TABLE,')
vs = ', '.join('{:#04x}'.format(x) for x in v)
fmt.line('defaults: &[{}],'.format(vs))
fmt.line('presets: &PRESETS,')
fmt.doc_comment(
'Create a `settings::Builder` for the {} settings group.'
.format(sgrp.name))
with fmt.indented('pub fn builder() -> Builder {', '}'):
fmt.line('Builder::new(&TEMPLATE)')
def gen_display(sgrp, fmt):
# type: (SettingGroup, srcgen.Formatter) -> None
"""
Generate the Display impl for Flags.
"""
with fmt.indented('impl fmt::Display for Flags {', '}'):
with fmt.indented(
'fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {',
'}'):
fmt.line('writeln!(f, "[{}]")?;'.format(sgrp.name))
with fmt.indented('for d in &DESCRIPTORS {', '}'):
with fmt.indented('if !d.detail.is_preset() {', '}'):
fmt.line('write!(f, "{} = ", d.name)?;')
fmt.line(
'TEMPLATE.format_toml_value(d.detail, ' +
'self.bytes[d.offset as usize], f)?;')
fmt.line('writeln!(f)?;')
fmt.line('Ok(())')
def gen_constructor(sgrp, fmt):
# type: (SettingGroup, srcgen.Formatter) -> None
"""
Generate a Flags constructor.
"""
with fmt.indented('impl Flags {', '}'):
args = 'builder: Builder'
if sgrp.parent:
p = sgrp.parent
args = '{}: &{}::Flags, {}'.format(p.name, p.qual_mod, args)
fmt.doc_comment('Create flags {} settings group.'.format(sgrp.name))
fmt.line('#[allow(unused_variables)]')
with fmt.indented(
'pub fn new({}) -> Self {{'.format(args), '}'):
fmt.line('let bvec = builder.state_for("{}");'.format(sgrp.name))
fmt.line(
'let mut {} = Self {{ bytes: [0; {}] }};'
.format(sgrp.name, sgrp.byte_size()))
fmt.line(
'debug_assert_eq!(bvec.len(), {});'
.format(sgrp.settings_size))
fmt.line(
'{}.bytes[0..{}].copy_from_slice(&bvec);'
.format(sgrp.name, sgrp.settings_size))
# Now compute the predicates.
for pred, number in sgrp.predicate_number.items():
# Don't compute our own settings.
if number < sgrp.boolean_settings:
continue
fmt.comment('Precompute #{}.'.format(number))
with fmt.indented(
'if {} {{'.format(pred.rust_predicate(0)),
'}'):
fmt.line(
'{}.bytes[{}] |= 1 << {};'
.format(
sgrp.name,
sgrp.boolean_offset + number // 8,
number % 8))
fmt.line(sgrp.name)
def gen_group(sgrp, fmt):
# type: (SettingGroup, srcgen.Formatter) -> None
"""
Generate a Flags struct representing `sgrp`.
"""
fmt.line('#[derive(Clone)]')
fmt.doc_comment('Flags group `{}`.'.format(sgrp.name))
with fmt.indented('pub struct Flags {', '}'):
fmt.line('bytes: [u8; {}],'.format(sgrp.byte_size()))
gen_constructor(sgrp, fmt)
gen_enum_types(sgrp, fmt)
gen_getters(sgrp, fmt)
gen_descriptors(sgrp, fmt)
gen_template(sgrp, fmt)
gen_display(sgrp, fmt)
def generate(isas, out_dir):
# type: (Sequence[TargetISA], str) -> None
# Generate shared settings.
fmt = srcgen.Formatter()
settings.group.qual_mod = 'settings'
gen_group(settings.group, fmt)
fmt.update_file('settings.rs', out_dir)
# Generate ISA-specific settings.
for isa in isas:
isa.settings.qual_mod = 'isa::{}::settings'.format(
isa.settings.name)
fmt = srcgen.Formatter()
gen_group(isa.settings, fmt)
fmt.update_file('settings-{}.rs'.format(isa.name), out_dir)

View File

@ -3,7 +3,7 @@ x86 Encodings.
"""
from __future__ import absolute_import
from cdsl.predicates import IsZero32BitFloat, IsZero64BitFloat
from cdsl.predicates import IsUnsignedInt, Not, And
from cdsl.predicates import IsUnsignedInt
from base.predicates import IsColocatedFunc, IsColocatedData, LengthEquals
from base import instructions as base
from base import types
@ -15,8 +15,8 @@ from . import settings as cfg
from . import instructions as x86
from .legalize import x86_expand
from base.legalize import narrow, widen, expand_flags
from base.settings import allones_funcaddrs, is_pic
from .settings import use_sse41
from .settings import use_sse41, not_all_ones_funcaddrs_and_not_is_pic, \
all_ones_funcaddrs_and_not_is_pic, is_pic, not_is_pic
try:
from typing import TYPE_CHECKING, Any # noqa
@ -407,15 +407,15 @@ enc_both(base.regspill.f64, r.fregspill32, 0xf2, 0x0f, 0x11)
# Non-PIC, all-ones funcaddresses.
X86_32.enc(base.func_addr.i32, *r.fnaddr4(0xb8),
isap=And(Not(allones_funcaddrs), Not(is_pic)))
isap=not_all_ones_funcaddrs_and_not_is_pic)
X86_64.enc(base.func_addr.i64, *r.fnaddr8.rex(0xb8, w=1),
isap=And(Not(allones_funcaddrs), Not(is_pic)))
isap=not_all_ones_funcaddrs_and_not_is_pic)
# Non-PIC, all-zeros funcaddresses.
X86_32.enc(base.func_addr.i32, *r.allones_fnaddr4(0xb8),
isap=And(allones_funcaddrs, Not(is_pic)))
isap=all_ones_funcaddrs_and_not_is_pic)
X86_64.enc(base.func_addr.i64, *r.allones_fnaddr8.rex(0xb8, w=1),
isap=And(allones_funcaddrs, Not(is_pic)))
isap=all_ones_funcaddrs_and_not_is_pic)
# 64-bit, colocated, both PIC and non-PIC. Use the lea instruction's
# pc-relative field.
@ -432,9 +432,9 @@ X86_64.enc(base.func_addr.i64, *r.got_fnaddr8.rex(0x8b, w=1),
# Non-PIC
X86_32.enc(base.symbol_value.i32, *r.gvaddr4(0xb8),
isap=Not(is_pic))
isap=not_is_pic)
X86_64.enc(base.symbol_value.i64, *r.gvaddr8.rex(0xb8, w=1),
isap=Not(is_pic))
isap=not_is_pic)
# PIC, colocated
X86_64.enc(base.symbol_value.i64, *r.pcrel_gvaddr8.rex(0x8d, w=1),

View File

@ -3,7 +3,7 @@ x86 settings.
"""
from __future__ import absolute_import
from cdsl.settings import SettingGroup, BoolSetting, Preset
from cdsl.predicates import And
from cdsl.predicates import And, Not
import base.settings as shared
from .defs import ISA
@ -35,6 +35,13 @@ use_popcnt = And(has_popcnt, has_sse42)
use_bmi1 = And(has_bmi1)
use_lzcnt = And(has_lzcnt)
is_pic = And(shared.is_pic)
not_is_pic = Not(shared.is_pic)
all_ones_funcaddrs_and_not_is_pic = And(shared.allones_funcaddrs,
Not(shared.is_pic))
not_all_ones_funcaddrs_and_not_is_pic = And(Not(shared.allones_funcaddrs),
Not(shared.is_pic))
# Presets corresponding to x86 CPUs.
baseline = Preset()

View File

@ -1,196 +0,0 @@
import doctest
import gen_legalizer
from unittest import TestCase
from srcgen import Formatter
from gen_legalizer import get_runtime_typechecks, emit_runtime_typecheck
from base.instructions import vselect, vsplit, isplit, iconcat, vconcat, \
iconst, b1, icmp, copy, sextend, uextend, ireduce, fdemote, fpromote # noqa
from base.legalize import narrow, expand # noqa
from base.immediates import intcc # noqa
from cdsl.typevar import TypeVar, TypeSet
from cdsl.ast import Var, Def # noqa
from cdsl.xform import Rtl, XForm # noqa
from cdsl.ti import ti_rtl, subst, TypeEnv, get_type_env # noqa
from unique_table import UniqueTable
from functools import reduce
try:
from typing import Callable, TYPE_CHECKING, Iterable, Any # noqa
if TYPE_CHECKING:
CheckProducer = Callable[[UniqueTable], str]
except ImportError:
TYPE_CHECKING = False
def load_tests(loader, tests, ignore):
# type: (Any, Any, Any) -> Any
tests.addTests(doctest.DocTestSuite(gen_legalizer))
return tests
def format_check(typesets, s, *args):
# type: (...) -> str
def transform(x):
# type: (Any) -> str
if isinstance(x, TypeSet):
return str(typesets.index[x])
elif isinstance(x, TypeVar):
assert not x.is_derived
return x.name
else:
return str(x)
dummy_s = s # type: str
args = tuple(map(lambda x: transform(x), args))
return dummy_s.format(*args)
def typeset_check(v, ts):
# type: (Var, TypeSet) -> CheckProducer
return lambda typesets: format_check(
typesets,
'let predicate = predicate && TYPE_SETS[{}].contains(typeof_{});\n',
ts, v)
def equiv_check(tv1, tv2):
# type: (str, str) -> CheckProducer
return lambda typesets: format_check(
typesets,
'let predicate = predicate && match ({}, {}) {{\n'
' (Some(a), Some(b)) => a == b,\n'
' _ => false,\n'
'}};\n', tv1, tv2)
def wider_check(tv1, tv2):
# type: (str, str) -> CheckProducer
return lambda typesets: format_check(
typesets,
'let predicate = predicate && match ({}, {}) {{\n'
' (Some(a), Some(b)) => a.wider_or_equal(b),\n'
' _ => false,\n'
'}};\n', tv1, tv2)
def sequence(*args):
# type: (...) -> CheckProducer
dummy = args # type: Iterable[CheckProducer]
def sequenceF(typesets):
# type: (UniqueTable) -> str
def strconcat(acc, el):
# type: (str, CheckProducer) -> str
return acc + el(typesets)
return reduce(strconcat, dummy, "")
return sequenceF
class TestRuntimeChecks(TestCase):
def setUp(self):
# type: () -> None
self.v0 = Var("v0")
self.v1 = Var("v1")
self.v2 = Var("v2")
self.v3 = Var("v3")
self.v4 = Var("v4")
self.v5 = Var("v5")
self.v6 = Var("v6")
self.v7 = Var("v7")
self.v8 = Var("v8")
self.v9 = Var("v9")
self.imm0 = Var("imm0")
self.IxN_nonscalar = TypeVar("IxN_nonscalar", "", ints=True,
scalars=False, simd=True)
self.TxN = TypeVar("TxN", "", ints=True, bools=True, floats=True,
scalars=False, simd=True)
self.b1 = TypeVar.singleton(b1)
def check_yo_check(self, xform, expected_f):
# type: (XForm, CheckProducer) -> None
fmt = Formatter()
type_sets = UniqueTable()
for check in get_runtime_typechecks(xform):
emit_runtime_typecheck(check, fmt, type_sets)
# Remove comments
got = "".join([l for l in fmt.lines if not l.strip().startswith("//")])
expected = expected_f(type_sets)
self.assertEqual(got, expected)
def test_width_check(self):
# type: () -> None
x = XForm(Rtl(self.v0 << copy(self.v1)),
Rtl((self.v2, self.v3) << isplit(self.v1),
self.v0 << iconcat(self.v2, self.v3)))
WideInt = TypeSet(lanes=(1, 256), ints=(16, 64))
self.check_yo_check(x, typeset_check(self.v1, WideInt))
def test_lanes_check(self):
# type: () -> None
x = XForm(Rtl(self.v0 << copy(self.v1)),
Rtl((self.v2, self.v3) << vsplit(self.v1),
self.v0 << vconcat(self.v2, self.v3)))
WideVec = TypeSet(lanes=(2, 256), ints=(8, 64), floats=(32, 64),
bools=(1, 64))
self.check_yo_check(x, typeset_check(self.v1, WideVec))
def test_vselect_imm(self):
# type: () -> None
ts = TypeSet(lanes=(2, 256), ints=True, floats=True, bools=(8, 64))
r = Rtl(
self.v0 << iconst(self.imm0),
self.v1 << icmp(intcc.eq, self.v2, self.v0),
self.v5 << vselect(self.v1, self.v3, self.v4),
)
x = XForm(r, r)
tv2_exp = 'Some({}).map(|t: crate::ir::Type| t.as_bool())'\
.format(self.v2.get_typevar().name)
tv3_exp = 'Some({}).map(|t: crate::ir::Type| t.as_bool())'\
.format(self.v3.get_typevar().name)
self.check_yo_check(
x, sequence(typeset_check(self.v3, ts),
equiv_check(tv2_exp, tv3_exp)))
def test_reduce_extend(self):
# type: () -> None
r = Rtl(
self.v1 << uextend(self.v0),
self.v2 << ireduce(self.v1),
self.v3 << sextend(self.v2),
)
x = XForm(r, r)
tv0_exp = 'Some({})'.format(self.v0.get_typevar().name)
tv1_exp = 'Some({})'.format(self.v1.get_typevar().name)
tv2_exp = 'Some({})'.format(self.v2.get_typevar().name)
tv3_exp = 'Some({})'.format(self.v3.get_typevar().name)
self.check_yo_check(
x, sequence(wider_check(tv1_exp, tv0_exp),
wider_check(tv1_exp, tv2_exp),
wider_check(tv3_exp, tv2_exp)))
def test_demote_promote(self):
# type: () -> None
r = Rtl(
self.v1 << fpromote(self.v0),
self.v2 << fdemote(self.v1),
self.v3 << fpromote(self.v2),
)
x = XForm(r, r)
tv0_exp = 'Some({})'.format(self.v0.get_typevar().name)
tv1_exp = 'Some({})'.format(self.v1.get_typevar().name)
tv2_exp = 'Some({})'.format(self.v2.get_typevar().name)
tv3_exp = 'Some({})'.format(self.v3.get_typevar().name)
self.check_yo_check(
x, sequence(wider_check(tv1_exp, tv0_exp),
wider_check(tv1_exp, tv2_exp),
wider_check(tv3_exp, tv2_exp)))

View File

@ -29,6 +29,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 std::vec::Vec;
@ -331,4 +332,13 @@ impl Context {
self.verify_locations_if(isa)?;
Ok(code_size)
}
/// Builds ranges and location for specified value labels.
pub fn build_value_labels_ranges(&self, isa: &TargetIsa) -> CodegenResult<ValueLabelsRanges> {
Ok(build_value_labels_ranges::<ComparableSourceLoc>(
&self.func,
&self.regalloc,
isa,
))
}
}

View File

@ -55,7 +55,7 @@ pub fn magic_u32(d: u32) -> MU32 {
q1 = u32::wrapping_add(u32::wrapping_mul(2, q1), 1);
r1 = u32::wrapping_sub(u32::wrapping_mul(2, r1), nc);
} else {
q1 = 2 * q1;
q1 = u32::wrapping_mul(2, q1);
r1 = 2 * r1;
}
if r2 + 1 >= d - r2 {
@ -101,7 +101,7 @@ pub fn magic_u64(d: u64) -> MU64 {
q1 = u64::wrapping_add(u64::wrapping_mul(2, q1), 1);
r1 = u64::wrapping_sub(u64::wrapping_mul(2, r1), nc);
} else {
q1 = 2 * q1;
q1 = u64::wrapping_mul(2, q1);
r1 = 2 * r1;
}
if r2 + 1 >= d - r2 {
@ -522,73 +522,129 @@ mod tests {
#[test]
fn test_magic_generators_dont_panic() {
// The point of this is to check that the magic number generators
// don't panic with integer wraparounds, especially at boundary
// cases for their arguments. The actual results are thrown away.
let mut total: u64 = 0;
// don't panic with integer wraparounds, especially at boundary cases
// for their arguments. The actual results are thrown away, although
// we force `total` to be used, so that rustc can't optimise the
// entire computation away.
// Testing UP magic_u32
let mut total: u64 = 0;
for x in 2..(200 * 1000u32) {
let m = magic_u32(x);
total = total ^ (m.mul_by as u64);
total = total + (m.shift_by as u64);
total = total - (if m.do_add { 123 } else { 456 });
total = total + (if m.do_add { 123 } else { 456 });
}
assert_eq!(total, 1747815691);
assert_eq!(total, 2481999609);
total = 0;
// Testing MIDPOINT magic_u32
for x in 0x8000_0000u32 - 10 * 1000u32..0x8000_0000u32 + 10 * 1000u32 {
let m = magic_u32(x);
total = total ^ (m.mul_by as u64);
total = total + (m.shift_by as u64);
total = total + (if m.do_add { 123 } else { 456 });
}
assert_eq!(total, 2399809723);
total = 0;
// Testing DOWN magic_u32
for x in 0..(200 * 1000u32) {
let m = magic_u32(0xFFFF_FFFFu32 - x);
total = total ^ (m.mul_by as u64);
total = total + (m.shift_by as u64);
total = total - (if m.do_add { 123 } else { 456 });
total = total + (if m.do_add { 123 } else { 456 });
}
assert_eq!(total, 2210292772);
assert_eq!(total, 271138267);
// Testing UP magic_u64
total = 0;
for x in 2..(200 * 1000u64) {
let m = magic_u64(x);
total = total ^ m.mul_by;
total = total + (m.shift_by as u64);
total = total - (if m.do_add { 123 } else { 456 });
total = total + (if m.do_add { 123 } else { 456 });
}
assert_eq!(total, 7430004084791260605);
assert_eq!(total, 7430004086976261161);
total = 0;
// Testing MIDPOINT magic_u64
for x in 0x8000_0000_0000_0000u64 - 10 * 1000u64..0x8000_0000_0000_0000u64 + 10 * 1000u64 {
let m = magic_u64(x);
total = total ^ m.mul_by;
total = total + (m.shift_by as u64);
total = total + (if m.do_add { 123 } else { 456 });
}
assert_eq!(total, 10312117246769520603);
// Testing DOWN magic_u64
total = 0;
for x in 0..(200 * 1000u64) {
let m = magic_u64(0xFFFF_FFFF_FFFF_FFFFu64 - x);
total = total ^ m.mul_by;
total = total + (m.shift_by as u64);
total = total - (if m.do_add { 123 } else { 456 });
total = total + (if m.do_add { 123 } else { 456 });
}
assert_eq!(total, 7547519887519825919);
assert_eq!(total, 1126603594357269734);
// Testing UP magic_s32
total = 0;
for x in 0..(200 * 1000i32) {
let m = magic_s32(-0x8000_0000i32 + x);
total = total ^ (m.mul_by as u64);
total = total + (m.shift_by as u64);
}
assert_eq!(total, 10899224186731671235);
assert_eq!(total, 18446744069953376812);
total = 0;
// Testing MIDPOINT magic_s32
for x in 0..(200 * 1000i32) {
let x2 = -100 * 1000i32 + x;
if x2 != -1 && x2 != 0 && x2 != 1 {
let m = magic_s32(x2);
total = total ^ (m.mul_by as u64);
total = total + (m.shift_by as u64);
}
}
assert_eq!(total, 351839350);
// Testing DOWN magic_s32
total = 0;
for x in 0..(200 * 1000i32) {
let m = magic_s32(0x7FFF_FFFFi32 - x);
total = total ^ (m.mul_by as u64);
total = total + (m.shift_by as u64);
}
assert_eq!(total, 7547519887517897369);
assert_eq!(total, 18446744072916880714);
// Testing UP magic_s64
total = 0;
for x in 0..(200 * 1000i64) {
let m = magic_s64(-0x8000_0000_0000_0000i64 + x);
total = total ^ (m.mul_by as u64);
total = total + (m.shift_by as u64);
}
assert_eq!(total, 8029756891368555163);
assert_eq!(total, 17929885647724831014);
total = 0;
// Testing MIDPOINT magic_s64
for x in 0..(200 * 1000i64) {
let x2 = -100 * 1000i64 + x;
if x2 != -1 && x2 != 0 && x2 != 1 {
let m = magic_s64(x2);
total = total ^ (m.mul_by as u64);
total = total + (m.shift_by as u64);
}
}
assert_eq!(total, 18106042338125661964);
// Testing DOWN magic_s64
total = 0;
for x in 0..(200 * 1000i64) {
let m = magic_s64(0x7FFF_FFFF_FFFF_FFFFi64 - x);
total = total ^ (m.mul_by as u64);
total = total + (m.shift_by as u64);
}
// Force `total` -- and hence, the entire computation -- to
// be used, so that rustc can't optimise it out.
assert_eq!(total, 7547519887532559585u64);
assert_eq!(total, 563301797155560970);
}
}

View File

@ -6,7 +6,10 @@ use crate::ir::builder::ReplaceBuilder;
use crate::ir::extfunc::ExtFuncData;
use crate::ir::instructions::{BranchInfo, CallInfo, InstructionData};
use crate::ir::types;
use crate::ir::{Ebb, FuncRef, Inst, SigRef, Signature, Type, Value, ValueList, ValueListPool};
use crate::ir::{
Ebb, FuncRef, Inst, SigRef, Signature, Type, Value, ValueLabelAssignments, ValueList,
ValueListPool,
};
use crate::isa::TargetIsa;
use crate::packed_option::ReservedValue;
use crate::write::write_operands;
@ -15,6 +18,7 @@ use core::iter;
use core::mem;
use core::ops::{Index, IndexMut};
use core::u16;
use std::collections::HashMap;
/// A data flow graph defines all instructions and extended basic blocks in a function as well as
/// the data flow dependencies between them. The DFG also tracks values which can be either
@ -60,6 +64,9 @@ pub struct DataFlowGraph {
/// External function references. These are functions that can be called directly.
pub ext_funcs: PrimaryMap<FuncRef, ExtFuncData>,
/// Saves Value labels.
pub values_labels: Option<HashMap<Value, ValueLabelAssignments>>,
}
impl DataFlowGraph {
@ -73,6 +80,7 @@ impl DataFlowGraph {
values: PrimaryMap::new(),
signatures: PrimaryMap::new(),
ext_funcs: PrimaryMap::new(),
values_labels: None,
}
}
@ -85,6 +93,7 @@ impl DataFlowGraph {
self.values.clear();
self.signatures.clear();
self.ext_funcs.clear();
self.values_labels = None;
}
/// Get the total number of instructions created in this function, whether they are currently
@ -117,6 +126,13 @@ impl DataFlowGraph {
pub fn num_values(&self) -> usize {
self.values.len()
}
/// Starts collection of debug information.
pub fn collect_debug_info(&mut self) {
if self.values_labels.is_none() {
self.values_labels = Some(HashMap::new());
}
}
}
/// Resolve value aliases.

View File

@ -15,6 +15,7 @@ use crate::ir::{EbbOffsets, InstEncodings, SourceLocs, StackSlots, ValueLocation
use crate::ir::{JumpTableOffsets, JumpTables};
use crate::isa::{CallConv, EncInfo, Encoding, Legalize, TargetIsa};
use crate::regalloc::RegDiversions;
use crate::value_label::ValueLabelsRanges;
use crate::write::write_function;
use core::fmt;
@ -155,7 +156,15 @@ impl Function {
/// Return an object that can display this function with correct ISA-specific annotations.
pub fn display<'a, I: Into<Option<&'a TargetIsa>>>(&'a self, isa: I) -> DisplayFunction<'a> {
DisplayFunction(self, isa.into())
DisplayFunction(self, isa.into().into())
}
/// Return an object that can display this function with correct ISA-specific annotations.
pub fn display_with<'a>(
&'a self,
annotations: DisplayFunctionAnnotations<'a>,
) -> DisplayFunction<'a> {
DisplayFunction(self, annotations)
}
/// Find a presumed unique special-purpose function parameter value.
@ -202,26 +211,58 @@ impl Function {
pub fn encode(&self, inst: ir::Inst, isa: &TargetIsa) -> Result<Encoding, Legalize> {
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();
}
}
/// Additional annotations for function display.
pub struct DisplayFunctionAnnotations<'a> {
/// Enable ISA annotations.
pub isa: Option<&'a TargetIsa>,
/// Enable value labels annotations.
pub value_ranges: Option<&'a ValueLabelsRanges>,
}
impl<'a> DisplayFunctionAnnotations<'a> {
fn default() -> Self {
DisplayFunctionAnnotations {
isa: None,
value_ranges: None,
}
}
}
impl<'a> From<Option<&'a TargetIsa>> for DisplayFunctionAnnotations<'a> {
fn from(isa: Option<&'a TargetIsa>) -> DisplayFunctionAnnotations {
DisplayFunctionAnnotations {
isa,
value_ranges: None,
}
}
}
/// Wrapper type capable of displaying a `Function` with correct ISA annotations.
pub struct DisplayFunction<'a>(&'a Function, Option<&'a TargetIsa>);
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, &self.1)
}
}
impl fmt::Display for Function {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write_function(fmt, self, None)
write_function(fmt, self, &DisplayFunctionAnnotations::default())
}
}
impl fmt::Debug for Function {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write_function(fmt, self, None)
write_function(fmt, self, &DisplayFunctionAnnotations::default())
}
}

View File

@ -32,7 +32,7 @@ pub use crate::ir::extfunc::{
AbiParam, ArgumentExtension, ArgumentPurpose, ExtFuncData, Signature,
};
pub use crate::ir::extname::ExternalName;
pub use crate::ir::function::Function;
pub use crate::ir::function::{DisplayFunctionAnnotations, Function};
pub use crate::ir::globalvalue::GlobalValueData;
pub use crate::ir::heap::{HeapData, HeapStyle};
pub use crate::ir::instructions::{
@ -51,7 +51,7 @@ pub use crate::ir::types::Type;
pub use crate::ir::valueloc::{ArgumentLoc, ValueLoc};
use crate::binemit;
use crate::entity::{PrimaryMap, SecondaryMap};
use crate::entity::{entity_impl, PrimaryMap, SecondaryMap};
use crate::isa;
/// Map of value locations.
@ -71,3 +71,34 @@ pub type JumpTableOffsets = SecondaryMap<JumpTable, binemit::CodeOffset>;
/// Source locations for instructions.
pub type SourceLocs = SecondaryMap<Inst, SourceLoc>;
/// Marked with a label value.
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct ValueLabel(u32);
entity_impl!(ValueLabel, "val");
/// A label of a Value.
#[derive(Debug, Clone)]
pub struct ValueLabelStart {
/// Source location when it is in effect
pub from: SourceLoc,
/// The label index.
pub label: ValueLabel,
}
/// Value label assignements: label starts or value aliases.
#[derive(Debug, Clone)]
pub enum ValueLabelAssignments {
/// Original value labels assigned at transform.
Starts(std::vec::Vec<ValueLabelStart>),
/// A value alias to original value.
Alias {
/// Source location when it is in effect
from: SourceLoc,
/// The label index.
value: Value,
},
}

View File

@ -105,13 +105,13 @@ pub struct RecipeConstraints {
/// constraints must be derived from the calling convention ABI.
pub outs: &'static [OperandConstraint],
/// Are any of the input constraints `FixedReg`?
/// Are any of the input constraints `FixedReg` or `FixedTied`?
pub fixed_ins: bool,
/// Are any of the output constraints `FixedReg`?
/// Are any of the output constraints `FixedReg` or `FixedTied`?
pub fixed_outs: bool,
/// Are there any tied operands?
/// Are any of the input/output constraints `Tied` (but not `FixedTied`)?
pub tied_ops: bool,
/// Does this instruction clobber the CPU flags?

View File

@ -4,7 +4,7 @@ use super::registers::*;
use crate::bitset::BitSet;
use crate::cursor::{Cursor, FuncCursor};
use crate::flowgraph::ControlFlowGraph;
use crate::ir::condcodes::IntCC;
use crate::ir::condcodes::{FloatCC, IntCC};
use crate::ir::{self, Function, Inst, InstBuilder};
use crate::isa;
use crate::isa::constraints::*;
@ -91,6 +91,25 @@ fn size_plus_maybe_sib_or_offset_for_in_reg_1(
sizing.base_size + additional_size_if(1, inst, divert, func, needs_sib_byte_or_offset)
}
/// If the value's definition is a constant immediate, returns its unpacked value, or None
/// otherwise.
fn maybe_iconst_imm(pos: &FuncCursor, value: ir::Value) -> Option<i64> {
if let ir::ValueDef::Result(inst, _) = &pos.func.dfg.value_def(value) {
if let ir::InstructionData::UnaryImm {
opcode: ir::Opcode::Iconst,
imm,
} = &pos.func.dfg[*inst]
{
let value: i64 = (*imm).into();
Some(value)
} else {
None
}
} else {
None
}
}
/// Expand the `sdiv` and `srem` instructions using `x86_sdivmodx`.
fn expand_sdivrem(
inst: ir::Inst,
@ -109,7 +128,7 @@ fn expand_sdivrem(
} => (args[0], args[1], true),
_ => panic!("Need sdiv/srem: {}", func.dfg.display_inst(inst, None)),
};
let avoid_div_traps = isa.flags().avoid_div_traps();
let old_ebb = func.layout.pp_ebb(inst);
let result = func.dfg.first_result(inst);
let ty = func.dfg.value_type(result);
@ -118,6 +137,8 @@ fn expand_sdivrem(
pos.use_srcloc(inst);
pos.func.dfg.clear_results(inst);
let avoid_div_traps = isa.flags().avoid_div_traps();
// If we can tolerate native division traps, sdiv doesn't need branching.
if !avoid_div_traps && !is_srem {
let xhi = pos.ins().sshr_imm(x, i64::from(ty.lane_bits()) - 1);
@ -126,6 +147,32 @@ fn expand_sdivrem(
return;
}
// Try to remove checks if the input value is an immediate other than 0 or -1. For these two
// immediates, we'd ideally replace conditional traps by traps, but this requires more
// manipulation of the dfg/cfg, which is out of scope here.
let (could_be_zero, could_be_minus_one) = if let Some(imm) = maybe_iconst_imm(&pos, y) {
(imm == 0, imm == -1)
} else {
(true, true)
};
// Put in an explicit division-by-zero trap if the environment requires it.
if avoid_div_traps && could_be_zero {
pos.ins().trapz(y, ir::TrapCode::IntegerDivisionByZero);
}
if !could_be_minus_one {
let xhi = pos.ins().sshr_imm(x, i64::from(ty.lane_bits()) - 1);
let reuse = if is_srem {
[None, Some(result)]
} else {
[Some(result), None]
};
pos.ins().with_results(reuse).x86_sdivmodx(x, xhi, y);
pos.remove_inst();
return;
}
// EBB handling the -1 divisor case.
let minus_one = pos.func.dfg.make_ebb();
@ -139,11 +186,6 @@ fn expand_sdivrem(
let is_m1 = pos.ins().ifcmp_imm(y, -1);
pos.ins().brif(IntCC::Equal, is_m1, minus_one, &[]);
// Put in an explicit division-by-zero trap if the environment requires it.
if avoid_div_traps {
pos.ins().trapz(y, ir::TrapCode::IntegerDivisionByZero);
}
// Now it is safe to execute the `x86_sdivmodx` instruction which will still trap on division
// by zero.
let xhi = pos.ins().sshr_imm(x, i64::from(ty.lane_bits()) - 1);
@ -206,7 +248,17 @@ fn expand_udivrem(
// Put in an explicit division-by-zero trap if the environment requires it.
if avoid_div_traps {
pos.ins().trapz(y, ir::TrapCode::IntegerDivisionByZero);
let zero_check = if let Some(imm) = maybe_iconst_imm(&pos, y) {
// Ideally, we'd just replace the conditional trap with a trap when the immediate is
// zero, but this requires more manipulation of the dfg/cfg, which is out of scope
// here.
imm == 0
} else {
true
};
if zero_check {
pos.ins().trapz(y, ir::TrapCode::IntegerDivisionByZero);
}
}
// Now it is safe to execute the `x86_udivmodx` instruction.
@ -228,8 +280,6 @@ fn expand_minmax(
cfg: &mut ControlFlowGraph,
_isa: &isa::TargetIsa,
) {
use crate::ir::condcodes::FloatCC;
let (x, y, x86_opc, bitwise_opc) = match func.dfg[inst] {
ir::InstructionData::Binary {
opcode: ir::Opcode::Fmin,
@ -322,8 +372,6 @@ fn expand_fcvt_from_uint(
cfg: &mut ControlFlowGraph,
_isa: &isa::TargetIsa,
) {
use crate::ir::condcodes::IntCC;
let x;
match func.dfg[inst] {
ir::InstructionData::Unary {
@ -395,7 +443,6 @@ fn expand_fcvt_to_sint(
cfg: &mut ControlFlowGraph,
_isa: &isa::TargetIsa,
) {
use crate::ir::condcodes::{FloatCC, IntCC};
use crate::ir::immediates::{Ieee32, Ieee64};
let x = match func.dfg[inst] {
@ -491,7 +538,6 @@ fn expand_fcvt_to_sint_sat(
cfg: &mut ControlFlowGraph,
_isa: &isa::TargetIsa,
) {
use crate::ir::condcodes::{FloatCC, IntCC};
use crate::ir::immediates::{Ieee32, Ieee64};
let x = match func.dfg[inst] {
@ -611,7 +657,6 @@ fn expand_fcvt_to_uint(
cfg: &mut ControlFlowGraph,
_isa: &isa::TargetIsa,
) {
use crate::ir::condcodes::{FloatCC, IntCC};
use crate::ir::immediates::{Ieee32, Ieee64};
let x = match func.dfg[inst] {
@ -693,7 +738,6 @@ fn expand_fcvt_to_uint_sat(
cfg: &mut ControlFlowGraph,
_isa: &isa::TargetIsa,
) {
use crate::ir::condcodes::{FloatCC, IntCC};
use crate::ir::immediates::{Ieee32, Ieee64};
let x = match func.dfg[inst] {

View File

@ -151,7 +151,17 @@ fn compute_addr(
// Convert `offset` to `addr_ty`.
if offset_ty != addr_ty {
let labels_value = offset;
offset = pos.ins().uextend(addr_ty, offset);
if let Some(values_labels) = pos.func.dfg.values_labels.as_mut() {
values_labels.insert(
offset,
ir::ValueLabelAssignments::Alias {
from: pos.func.srclocs[inst],
value: labels_value,
},
);
}
}
// Add the heap base address base

View File

@ -57,6 +57,7 @@ use std::collections::{hash_map, HashMap, HashSet};
pub use crate::context::Context;
pub use crate::legalizer::legalize_function;
pub use crate::value_label::ValueLabelsRanges;
pub use crate::verifier::verify_function;
pub use crate::write::write_function;
@ -103,6 +104,7 @@ mod simple_preopt;
mod stack_layout;
mod topo_order;
mod unreachable_code;
mod value_label;
pub use crate::result::{CodegenError, CodegenResult};

View File

@ -29,7 +29,7 @@ pub fn pretty_verifier_error<'a>(
&mut PrettyVerifierError(func_w.unwrap_or_else(|| Box::new(PlainWriter)), &mut errors),
&mut w,
func,
isa,
&isa.into(),
)
.unwrap();

View File

@ -64,6 +64,11 @@ impl Context {
self.coloring.clear();
}
/// Current values liveness state.
pub fn liveness(&self) -> &Liveness {
&self.liveness
}
/// Allocate registers in `func`.
///
/// After register allocation, all values in `func` have been assigned to a register or stack

View File

@ -314,6 +314,11 @@ impl Liveness {
}
}
/// Current live ranges.
pub fn ranges(&self) -> &LiveRangeSet {
&self.ranges
}
/// Get a context needed for working with a `LiveRange`.
pub fn context<'a>(&'a self, layout: &'a Layout) -> LiveRangeContext<'a, Layout> {
LiveRangeContext::new(layout, &self.forest)

View File

@ -379,6 +379,8 @@ mod tests {
f.to_string(),
"[shared]\n\
opt_level = \"default\"\n\
baldrdash_prologue_words = 0\n\
probestack_size_log2 = 12\n\
enable_verifier = true\n\
is_pic = false\n\
colocated_libcalls = false\n\
@ -387,11 +389,9 @@ mod tests {
enable_nan_canonicalization = false\n\
enable_simd = true\n\
enable_atomics = true\n\
baldrdash_prologue_words = 0\n\
allones_funcaddrs = false\n\
probestack_enabled = true\n\
probestack_func_adjusts_sp = false\n\
probestack_size_log2 = 12\n\
jump_tables_enabled = true\n"
);
assert_eq!(f.opt_level(), super::OptLevel::Default);

View File

@ -4,8 +4,6 @@
//! should be useful for already well-optimized code. More general purpose
//! early-stage optimizations can be found in the preopt crate.
#![allow(non_snake_case)]
use crate::cursor::{Cursor, FuncCursor};
use crate::divconst_magic_numbers::{magic_s32, magic_s64, magic_u32, magic_u64};
use crate::divconst_magic_numbers::{MS32, MS64, MU32, MU64};
@ -27,7 +25,7 @@ use crate::timing;
/// if `x` is a power of two, or the negation thereof, return the power along
/// with a boolean that indicates whether `x` is negative. Else return None.
#[inline]
fn isPowerOf2_S32(x: i32) -> Option<(bool, u32)> {
fn i32_is_power_of_two(x: i32) -> Option<(bool, u32)> {
// We have to special-case this because abs(x) isn't representable.
if x == -0x8000_0000 {
return Some((true, 31));
@ -39,9 +37,9 @@ fn isPowerOf2_S32(x: i32) -> Option<(bool, u32)> {
None
}
/// Same comments as for isPowerOf2_S64 apply.
/// Same comments as for i32_is_power_of_two apply.
#[inline]
fn isPowerOf2_S64(x: i64) -> Option<(bool, u32)> {
fn i64_is_power_of_two(x: i64) -> Option<(bool, u32)> {
// We have to special-case this because abs(x) isn't representable.
if x == -0x8000_0000_0000_0000 {
return Some((true, 63));
@ -53,10 +51,12 @@ fn isPowerOf2_S64(x: i64) -> Option<(bool, u32)> {
None
}
/// Representation of an instruction that can be replaced by a single division/remainder operation
/// between a left Value operand and a right immediate operand.
#[derive(Debug)]
enum DivRemByConstInfo {
DivU32(Value, u32), // In all cases, the arguments are:
DivU64(Value, u64), // left operand, right operand
DivU32(Value, u32),
DivU64(Value, u64),
DivS32(Value, i32),
DivS64(Value, i64),
RemU32(Value, u32),
@ -65,84 +65,88 @@ enum DivRemByConstInfo {
RemS64(Value, i64),
}
/// Possibly create a DivRemByConstInfo from the given components, by
/// figuring out which, if any, of the 8 cases apply, and also taking care to
/// sanity-check the immediate.
/// Possibly create a DivRemByConstInfo from the given components, by figuring out which, if any,
/// of the 8 cases apply, and also taking care to sanity-check the immediate.
fn package_up_divrem_info(
argL: Value,
argL_ty: Type,
argRs: i64,
isSigned: bool,
isRem: bool,
value: Value,
value_type: Type,
imm_i64: i64,
is_signed: bool,
is_rem: bool,
) -> Option<DivRemByConstInfo> {
let argRu: u64 = argRs as u64;
if !isSigned && argL_ty == I32 && argRu < 0x1_0000_0000 {
let con = if isRem {
DivRemByConstInfo::RemU32
} else {
DivRemByConstInfo::DivU32
};
return Some(con(argL, argRu as u32));
let imm_u64 = imm_i64 as u64;
match (is_signed, value_type) {
(false, I32) => {
if imm_u64 < 0x1_0000_0000 {
if is_rem {
Some(DivRemByConstInfo::RemU32(value, imm_u64 as u32))
} else {
Some(DivRemByConstInfo::DivU32(value, imm_u64 as u32))
}
} else {
None
}
}
(false, I64) => {
// unsigned 64, no range constraint.
if is_rem {
Some(DivRemByConstInfo::RemU64(value, imm_u64))
} else {
Some(DivRemByConstInfo::DivU64(value, imm_u64))
}
}
(true, I32) => {
if imm_u64 <= 0x7fff_ffff || imm_u64 >= 0xffff_ffff_8000_0000 {
if is_rem {
Some(DivRemByConstInfo::RemS32(value, imm_u64 as i32))
} else {
Some(DivRemByConstInfo::DivS32(value, imm_u64 as i32))
}
} else {
None
}
}
(true, I64) => {
// signed 64, no range constraint.
if is_rem {
Some(DivRemByConstInfo::RemS64(value, imm_u64 as i64))
} else {
Some(DivRemByConstInfo::DivS64(value, imm_u64 as i64))
}
}
_ => None,
}
if !isSigned && argL_ty == I64 {
// unsigned 64, no range constraint
let con = if isRem {
DivRemByConstInfo::RemU64
} else {
DivRemByConstInfo::DivU64
};
return Some(con(argL, argRu));
}
if isSigned && argL_ty == I32 && (argRu <= 0x7fff_ffff || argRu >= 0xffff_ffff_8000_0000) {
let con = if isRem {
DivRemByConstInfo::RemS32
} else {
DivRemByConstInfo::DivS32
};
return Some(con(argL, argRu as i32));
}
if isSigned && argL_ty == I64 {
// signed 64, no range constraint
let con = if isRem {
DivRemByConstInfo::RemS64
} else {
DivRemByConstInfo::DivS64
};
return Some(con(argL, argRu as i64));
}
None
}
/// Examine `idata` to see if it is a div or rem by a constant, and if so
/// return the operands, signedness, operation size and div-vs-rem-ness in a
/// handy bundle.
/// Examine `inst` to see if it is a div or rem by a constant, and if so return the operands,
/// signedness, operation size and div-vs-rem-ness in a handy bundle.
fn get_div_info(inst: Inst, dfg: &DataFlowGraph) -> Option<DivRemByConstInfo> {
let idata: &InstructionData = &dfg[inst];
if let InstructionData::BinaryImm { opcode, arg, imm } = *idata {
let (isSigned, isRem) = match opcode {
if let InstructionData::BinaryImm { opcode, arg, imm } = dfg[inst] {
let (is_signed, is_rem) = match opcode {
Opcode::UdivImm => (false, false),
Opcode::UremImm => (false, true),
Opcode::SdivImm => (true, false),
Opcode::SremImm => (true, true),
_other => return None,
_ => return None,
};
// Pull the operation size (type) from the left arg
let argL_ty = dfg.value_type(arg);
return package_up_divrem_info(arg, argL_ty, imm.into(), isSigned, isRem);
return package_up_divrem_info(arg, dfg.value_type(arg), imm.into(), is_signed, is_rem);
}
None
}
/// Actually do the transformation given a bundle containing the relevant
/// information. `divrem_info` describes a div or rem by a constant, that
/// `pos` currently points at, and `inst` is the associated instruction.
/// `inst` is replaced by a sequence of other operations that calculate the
/// same result. Note that there are various `divrem_info` cases where we
/// cannot do any transformation, in which case `inst` is left unchanged.
/// Actually do the transformation given a bundle containing the relevant information.
/// `divrem_info` describes a div or rem by a constant, that `pos` currently points at, and `inst`
/// is the associated instruction. `inst` is replaced by a sequence of other operations that
/// calculate the same result. Note that there are various `divrem_info` cases where we cannot do
/// any transformation, in which case `inst` is left unchanged.
fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCursor, inst: Inst) {
let isRem = match *divrem_info {
let is_rem = match *divrem_info {
DivRemByConstInfo::DivU32(_, _)
| DivRemByConstInfo::DivU64(_, _)
| DivRemByConstInfo::DivS32(_, _)
@ -162,7 +166,7 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso
// U32 div by 1: identity
// U32 rem by 1: zero
DivRemByConstInfo::DivU32(n1, 1) | DivRemByConstInfo::RemU32(n1, 1) => {
if isRem {
if is_rem {
pos.func.dfg.replace(inst).iconst(I32, 0);
} else {
pos.func.dfg.replace(inst).copy(n1);
@ -177,7 +181,7 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso
// compute k where d == 2^k
let k = d.trailing_zeros();
debug_assert!(k >= 1 && k <= 31);
if isRem {
if is_rem {
let mask = (1u64 << k) - 1;
pos.func.dfg.replace(inst).band_imm(n1, mask as i64);
} else {
@ -216,7 +220,7 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso
}
// Now qf holds the final quotient. If necessary calculate the
// remainder instead.
if isRem {
if is_rem {
let tt = pos.ins().imul_imm(qf, d as i64);
pos.func.dfg.replace(inst).isub(n1, tt);
} else {
@ -232,7 +236,7 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso
// U64 div by 1: identity
// U64 rem by 1: zero
DivRemByConstInfo::DivU64(n1, 1) | DivRemByConstInfo::RemU64(n1, 1) => {
if isRem {
if is_rem {
pos.func.dfg.replace(inst).iconst(I64, 0);
} else {
pos.func.dfg.replace(inst).copy(n1);
@ -247,7 +251,7 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso
// compute k where d == 2^k
let k = d.trailing_zeros();
debug_assert!(k >= 1 && k <= 63);
if isRem {
if is_rem {
let mask = (1u64 << k) - 1;
pos.func.dfg.replace(inst).band_imm(n1, mask as i64);
} else {
@ -286,7 +290,7 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso
}
// Now qf holds the final quotient. If necessary calculate the
// remainder instead.
if isRem {
if is_rem {
let tt = pos.ins().imul_imm(qf, d as i64);
pos.func.dfg.replace(inst).isub(n1, tt);
} else {
@ -305,7 +309,7 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso
// S32 div by 1: identity
// S32 rem by 1: zero
DivRemByConstInfo::DivS32(n1, 1) | DivRemByConstInfo::RemS32(n1, 1) => {
if isRem {
if is_rem {
pos.func.dfg.replace(inst).iconst(I32, 0);
} else {
pos.func.dfg.replace(inst).copy(n1);
@ -313,7 +317,7 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso
}
DivRemByConstInfo::DivS32(n1, d) | DivRemByConstInfo::RemS32(n1, d) => {
if let Some((isNeg, k)) = isPowerOf2_S32(d) {
if let Some((is_negative, k)) = i32_is_power_of_two(d) {
// k can be 31 only in the case that d is -2^31.
debug_assert!(k >= 1 && k <= 31);
let t1 = if k - 1 == 0 {
@ -323,7 +327,7 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso
};
let t2 = pos.ins().ushr_imm(t1, (32 - k) as i64);
let t3 = pos.ins().iadd(n1, t2);
if isRem {
if is_rem {
// S32 rem by a power-of-2
let t4 = pos.ins().band_imm(t3, i32::wrapping_neg(1 << k) as i64);
// Curiously, we don't care here what the sign of d is.
@ -331,7 +335,7 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso
} else {
// S32 div by a power-of-2
let t4 = pos.ins().sshr_imm(t3, k as i64);
if isNeg {
if is_negative {
pos.func.dfg.replace(inst).irsub_imm(t4, 0);
} else {
pos.func.dfg.replace(inst).copy(t4);
@ -360,7 +364,7 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso
let qf = pos.ins().iadd(q3, t1);
// Now qf holds the final quotient. If necessary calculate
// the remainder instead.
if isRem {
if is_rem {
let tt = pos.ins().imul_imm(qf, d as i64);
pos.func.dfg.replace(inst).isub(n1, tt);
} else {
@ -380,7 +384,7 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso
// S64 div by 1: identity
// S64 rem by 1: zero
DivRemByConstInfo::DivS64(n1, 1) | DivRemByConstInfo::RemS64(n1, 1) => {
if isRem {
if is_rem {
pos.func.dfg.replace(inst).iconst(I64, 0);
} else {
pos.func.dfg.replace(inst).copy(n1);
@ -388,7 +392,7 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso
}
DivRemByConstInfo::DivS64(n1, d) | DivRemByConstInfo::RemS64(n1, d) => {
if let Some((isNeg, k)) = isPowerOf2_S64(d) {
if let Some((is_negative, k)) = i64_is_power_of_two(d) {
// k can be 63 only in the case that d is -2^63.
debug_assert!(k >= 1 && k <= 63);
let t1 = if k - 1 == 0 {
@ -398,7 +402,7 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso
};
let t2 = pos.ins().ushr_imm(t1, (64 - k) as i64);
let t3 = pos.ins().iadd(n1, t2);
if isRem {
if is_rem {
// S64 rem by a power-of-2
let t4 = pos.ins().band_imm(t3, i64::wrapping_neg(1 << k));
// Curiously, we don't care here what the sign of d is.
@ -406,7 +410,7 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso
} else {
// S64 div by a power-of-2
let t4 = pos.ins().sshr_imm(t3, k as i64);
if isNeg {
if is_negative {
pos.func.dfg.replace(inst).irsub_imm(t4, 0);
} else {
pos.func.dfg.replace(inst).copy(t4);
@ -435,7 +439,7 @@ fn do_divrem_transformation(divrem_info: &DivRemByConstInfo, pos: &mut FuncCurso
let qf = pos.ins().iadd(q3, t1);
// Now qf holds the final quotient. If necessary calculate
// the remainder instead.
if isRem {
if is_rem {
let tt = pos.ins().imul_imm(qf, d);
pos.func.dfg.replace(inst).isub(n1, tt);
} else {
@ -770,16 +774,12 @@ pub fn do_preopt(func: &mut Function, cfg: &mut ControlFlowGraph) {
// Apply basic simplifications.
simplify(&mut pos, inst);
//-- BEGIN -- division by constants ----------------
let mb_dri = get_div_info(inst, &pos.func.dfg);
if let Some(divrem_info) = mb_dri {
// Try to transform divide-by-constant into simpler operations.
if let Some(divrem_info) = get_div_info(inst, &pos.func.dfg) {
do_divrem_transformation(&divrem_info, &mut pos, inst);
continue;
}
//-- END -- division by constants ------------------
branch_opt(&mut pos, inst);
branch_order(&mut pos, cfg, ebb, inst);
}

View File

@ -0,0 +1,268 @@
use crate::ir::{Function, SourceLoc, Value, ValueLabel, ValueLabelAssignments, ValueLoc};
use crate::isa::TargetIsa;
use crate::regalloc::{Context, RegDiversions};
use std::cmp::Ordering;
use std::collections::{BTreeMap, HashMap};
use std::iter::Iterator;
use std::ops::Bound::*;
use std::ops::Deref;
use std::vec::Vec;
/// Value location range.
#[derive(Debug, Clone, Copy)]
pub struct ValueLocRange {
pub loc: ValueLoc,
pub start: u32,
pub end: u32,
}
/// Resulting map of Value labels and their ranges/locations.
pub type ValueLabelsRanges = HashMap<ValueLabel, Vec<ValueLocRange>>;
fn build_value_labels_index<T>(func: &Function) -> BTreeMap<T, (Value, ValueLabel)>
where
T: From<SourceLoc> + Deref<Target = SourceLoc> + Ord + Copy,
{
if func.dfg.values_labels.is_none() {
return BTreeMap::new();
}
let values_labels = func.dfg.values_labels.as_ref().unwrap();
// Index values_labels by srcloc/from
let mut sorted = BTreeMap::new();
for (val, assigns) in values_labels {
match assigns {
ValueLabelAssignments::Starts(labels) => {
for label in labels {
if label.from.is_default() {
continue;
}
let srcloc = T::from(label.from);
let label = label.label;
sorted.insert(srcloc, (*val, label));
}
}
ValueLabelAssignments::Alias { from, value } => {
if from.is_default() {
continue;
}
let mut aliased_value = *value;
while let Some(ValueLabelAssignments::Alias { value, .. }) =
values_labels.get(&aliased_value)
{
// TODO check/limit recursion?
aliased_value = *value;
}
let from = T::from(*from);
if let Some(ValueLabelAssignments::Starts(labels)) =
values_labels.get(&aliased_value)
{
for label in labels {
let srcloc = if label.from.is_default() {
from
} else {
from.max(T::from(label.from))
};
let label = label.label;
sorted.insert(srcloc, (*val, label));
}
}
}
}
}
sorted
}
/// Builds ranges and location for specified value labels.
/// The labels specified at DataFlowGraph's values_labels collection.
pub fn build_value_labels_ranges<T>(
func: &Function,
regalloc: &Context,
isa: &TargetIsa,
) -> ValueLabelsRanges
where
T: From<SourceLoc> + Deref<Target = SourceLoc> + Ord + Copy,
{
let values_labels = build_value_labels_index::<T>(func);
let mut ebbs = func.layout.ebbs().collect::<Vec<_>>();
ebbs.sort_by_key(|ebb| func.offsets[*ebb]); // Ensure inst offsets always increase
let encinfo = isa.encoding_info();
let values_locations = &func.locations;
let liveness_context = regalloc.liveness().context(&func.layout);
let liveness_ranges = regalloc.liveness().ranges();
let mut ranges = HashMap::new();
let mut add_range = |label, range: (u32, u32), loc: ValueLoc| {
if range.0 >= range.1 || !loc.is_assigned() {
return;
}
if !ranges.contains_key(&label) {
ranges.insert(label, Vec::new());
}
ranges.get_mut(&label).unwrap().push(ValueLocRange {
loc,
start: range.0,
end: range.1,
});
};
let mut end_offset = 0;
let mut tracked_values: Vec<(Value, ValueLabel, u32, ValueLoc)> = Vec::new();
let mut divert = RegDiversions::new();
for ebb in ebbs {
divert.clear();
let mut last_srcloc: Option<T> = None;
for (offset, inst, size) in func.inst_offsets(ebb, &encinfo) {
divert.apply(&func.dfg[inst]);
end_offset = offset + size;
// Remove killed values.
tracked_values.retain(|(x, label, start_offset, last_loc)| {
let range = liveness_ranges.get(*x);
if range.expect("value").killed_at(inst, ebb, liveness_context) {
add_range(*label, (*start_offset, end_offset), *last_loc);
return false;
}
return true;
});
let srcloc = func.srclocs[inst];
if srcloc.is_default() {
// Don't process instructions without srcloc.
continue;
}
let srcloc = T::from(srcloc);
// Record and restart ranges if Value location was changed.
for (val, label, start_offset, last_loc) in &mut tracked_values {
let new_loc = divert.get(*val, values_locations);
if new_loc == *last_loc {
continue;
}
add_range(*label, (*start_offset, end_offset), *last_loc);
*start_offset = end_offset;
*last_loc = new_loc;
}
// New source locations range started: abandon all tracked values.
if last_srcloc.is_some() && last_srcloc.as_ref().unwrap() > &srcloc {
for (_, label, start_offset, last_loc) in &tracked_values {
add_range(*label, (*start_offset, end_offset), *last_loc);
}
tracked_values.clear();
last_srcloc = None;
}
// Get non-processed Values based on srcloc
let range = (
match last_srcloc {
Some(a) => Excluded(a),
None => Unbounded,
},
Included(srcloc),
);
let active_values = values_labels.range(range);
let active_values = active_values.filter(|(_, (v, _))| {
// Ignore dead/inactive Values.
let range = liveness_ranges.get(*v);
match range {
Some(r) => r.reaches_use(inst, ebb, liveness_context),
None => false,
}
});
// Append new Values to the tracked_values.
for (_, (val, label)) in active_values {
let loc = divert.get(*val, values_locations);
tracked_values.push((*val, *label, end_offset, loc));
}
last_srcloc = Some(srcloc);
}
// Finish all started ranges.
for (_, label, start_offset, last_loc) in &tracked_values {
add_range(*label, (*start_offset, end_offset), *last_loc);
}
}
// Optimize ranges in-place
for (_, label_ranges) in ranges.iter_mut() {
assert!(label_ranges.len() > 0);
label_ranges.sort_by(|a, b| a.start.cmp(&b.start).then_with(|| a.end.cmp(&b.end)));
// Merge ranges
let mut i = 1;
let mut j = 0;
while i < label_ranges.len() {
assert!(label_ranges[j].start <= label_ranges[i].end);
if label_ranges[j].loc != label_ranges[i].loc {
// Different location
if label_ranges[j].end >= label_ranges[i].end {
// Consumed by previous range, skipping
i += 1;
continue;
}
j += 1;
label_ranges[j] = label_ranges[i];
i += 1;
continue;
}
if label_ranges[j].end < label_ranges[i].start {
// Gap in the range location
j += 1;
label_ranges[j] = label_ranges[i];
i += 1;
continue;
}
// Merge i-th and j-th ranges
if label_ranges[j].end < label_ranges[i].end {
label_ranges[j].end = label_ranges[i].end;
}
i += 1;
}
label_ranges.truncate(j + 1);
// Cut/move start position of next range, if two neighbor ranges intersect.
for i in 0..j {
if label_ranges[i].end > label_ranges[i + 1].start {
label_ranges[i + 1].start = label_ranges[i].end;
assert!(label_ranges[i + 1].start < label_ranges[i + 1].end);
}
assert!(label_ranges[i].end <= label_ranges[i + 1].start);
}
}
ranges
}
#[derive(Eq, Clone, Copy)]
pub struct ComparableSourceLoc(SourceLoc);
impl From<SourceLoc> for ComparableSourceLoc {
fn from(s: SourceLoc) -> Self {
ComparableSourceLoc(s)
}
}
impl Deref for ComparableSourceLoc {
type Target = SourceLoc;
fn deref(&self) -> &SourceLoc {
&self.0
}
}
impl PartialOrd for ComparableSourceLoc {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ComparableSourceLoc {
fn cmp(&self, other: &Self) -> Ordering {
self.0.bits().cmp(&other.0.bits())
}
}
impl PartialEq for ComparableSourceLoc {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}

View File

@ -5,10 +5,15 @@
use crate::entity::SecondaryMap;
use crate::ir::entities::AnyEntity;
use crate::ir::{DataFlowGraph, Ebb, Function, Inst, SigRef, Type, Value, ValueDef};
use crate::ir::{
DataFlowGraph, DisplayFunctionAnnotations, Ebb, Function, Inst, SigRef, Type, Value, ValueDef,
ValueLoc,
};
use crate::isa::{RegInfo, TargetIsa};
use crate::packed_option::ReservedValue;
use crate::value_label::ValueLabelsRanges;
use core::fmt::{self, Write};
use std::collections::HashSet;
use std::string::String;
use std::vec::Vec;
@ -154,8 +159,12 @@ impl FuncWriter for PlainWriter {
/// Write `func` to `w` as equivalent text.
/// Use `isa` to emit ISA-dependent annotations.
pub fn write_function(w: &mut Write, func: &Function, isa: Option<&TargetIsa>) -> fmt::Result {
decorate_function(&mut PlainWriter, w, func, isa)
pub fn write_function(
w: &mut Write,
func: &Function,
annotations: &DisplayFunctionAnnotations,
) -> fmt::Result {
decorate_function(&mut PlainWriter, w, func, annotations)
}
/// Create a reverse-alias map from a value to all aliases having that value as a direct target
@ -177,9 +186,9 @@ pub fn decorate_function<FW: FuncWriter>(
func_w: &mut FW,
w: &mut Write,
func: &Function,
isa: Option<&TargetIsa>,
annotations: &DisplayFunctionAnnotations,
) -> fmt::Result {
let regs = isa.map(TargetIsa::register_info);
let regs = annotations.isa.map(TargetIsa::register_info);
let regs = regs.as_ref();
write!(w, "function ")?;
@ -191,7 +200,7 @@ pub fn decorate_function<FW: FuncWriter>(
if any {
writeln!(w)?;
}
decorate_ebb(func_w, w, func, &aliases, isa, ebb)?;
decorate_ebb(func_w, w, func, &aliases, annotations, ebb)?;
any = true;
}
writeln!(w, "}}")
@ -254,12 +263,53 @@ pub fn write_ebb_header(
writeln!(w, "):")
}
fn write_valueloc(w: &mut Write, loc: &ValueLoc, regs: &RegInfo) -> fmt::Result {
match loc {
ValueLoc::Reg(r) => write!(w, "{}", regs.display_regunit(*r)),
ValueLoc::Stack(ss) => write!(w, "{}", ss),
ValueLoc::Unassigned => write!(w, "?"),
}
}
fn write_value_range_markers(
w: &mut Write,
val_ranges: &ValueLabelsRanges,
regs: &RegInfo,
offset: u32,
indent: usize,
) -> fmt::Result {
let mut result = String::new();
let mut shown = HashSet::new();
for (val, rng) in val_ranges {
for i in (0..rng.len()).rev() {
if rng[i].start == offset {
write!(&mut result, " {}@", val)?;
write_valueloc(&mut result, &rng[i].loc, regs)?;
shown.insert(val);
break;
}
}
}
for (val, rng) in val_ranges {
for i in (0..rng.len()).rev() {
if rng[i].end == offset && !shown.contains(val) {
write!(&mut result, " {}\u{2620}", val)?;
break;
}
}
}
if result.len() > 0 {
writeln!(w, ";{1:0$}; {2}", indent + 24, "", result)?;
}
Ok(())
}
fn decorate_ebb<FW: FuncWriter>(
func_w: &mut FW,
w: &mut Write,
func: &Function,
aliases: &SecondaryMap<Value, Vec<Value>>,
isa: Option<&TargetIsa>,
annotations: &DisplayFunctionAnnotations,
ebb: Ebb,
) -> fmt::Result {
// Indent all instructions if any encodings are present.
@ -268,13 +318,28 @@ fn decorate_ebb<FW: FuncWriter>(
} else {
36
};
let isa = annotations.isa;
func_w.write_ebb_header(w, func, isa, ebb, indent)?;
for a in func.dfg.ebb_params(ebb).iter().cloned() {
write_value_aliases(w, aliases, a, indent)?;
}
for inst in func.layout.ebb_insts(ebb) {
func_w.write_instruction(w, func, aliases, isa, inst, indent)?;
if isa.is_some() && !func.offsets.is_empty() {
let encinfo = isa.unwrap().encoding_info();
let regs = &isa.unwrap().register_info();
for (offset, inst, size) in func.inst_offsets(ebb, &encinfo) {
func_w.write_instruction(w, func, aliases, isa, inst, indent)?;
if size > 0 {
if let Some(val_ranges) = annotations.value_ranges {
write_value_range_markers(w, val_ranges, regs, offset + size, indent)?;
}
}
}
} else {
for inst in func.layout.ebb_insts(ebb) {
func_w.write_instruction(w, func, aliases, isa, inst, indent)?;
}
}
Ok(())

View File

@ -1 +1 @@
{"files":{"Cargo.toml":"c92f07d9959d10331c6b4770a4db12f706927a18897dfed45472abcd6e58190e","LICENSE":"268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5","README.md":"dea43e8044284df50f8b8772e9b48ba8b109b45c74111ff73619775d57ad8d67","src/frontend.rs":"f3ad024e4895eddf83c8fe19c93ae37709a6bf27db2e1beef153fd742d99defa","src/lib.rs":"1cc2e7aaffa45bccea9e59fcc9d9c5d295a9f7adacd6bd55933834e20e969aef","src/ssa.rs":"88cb07071943f3e72a91c91afb58960689b4d9c56352b3bb7cd5d69288066190","src/switch.rs":"b8f337966b540254feb5f979b4a146f5ef69ae199864da6332c9d7513ff3ec8b","src/variable.rs":"f082efaa4b2d3c5eb48f6344149408074e1e15cb581f7a63f549313c7a1037be"},"package":null}
{"files":{"Cargo.toml":"c92f07d9959d10331c6b4770a4db12f706927a18897dfed45472abcd6e58190e","LICENSE":"268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5","README.md":"dea43e8044284df50f8b8772e9b48ba8b109b45c74111ff73619775d57ad8d67","src/frontend.rs":"4df0a0dedbb26c7fdc5d561b6609ceaefb396669b21a1db484638c074914a74d","src/lib.rs":"1cc2e7aaffa45bccea9e59fcc9d9c5d295a9f7adacd6bd55933834e20e969aef","src/ssa.rs":"88cb07071943f3e72a91c91afb58960689b4d9c56352b3bb7cd5d69288066190","src/switch.rs":"b8f337966b540254feb5f979b4a146f5ef69ae199864da6332c9d7513ff3ec8b","src/variable.rs":"f082efaa4b2d3c5eb48f6344149408074e1e15cb581f7a63f549313c7a1037be"},"package":null}

View File

@ -9,7 +9,7 @@ use cranelift_codegen::ir::{
types, AbiParam, DataFlowGraph, Ebb, ExtFuncData, ExternalName, FuncRef, Function, GlobalValue,
GlobalValueData, Heap, HeapData, Inst, InstBuilder, InstBuilderBase, InstructionData,
JumpTable, JumpTableData, LibCall, MemFlags, SigRef, Signature, StackSlot, StackSlotData, Type,
Value,
Value, ValueLabel, ValueLabelAssignments, ValueLabelStart,
};
use cranelift_codegen::isa::{TargetFrontendConfig, TargetIsa};
use cranelift_codegen::packed_option::PackedOption;
@ -333,6 +333,28 @@ impl<'a> FunctionBuilder<'a> {
.def_var(var, val, self.position.basic_block.unwrap());
}
/// Set label for Value
pub fn set_val_label(&mut self, val: Value, label: ValueLabel) {
if let Some(values_labels) = self.func.dfg.values_labels.as_mut() {
use std::collections::hash_map::Entry;
let start = ValueLabelStart {
from: self.srcloc,
label,
};
match values_labels.entry(val) {
Entry::Occupied(mut e) => match e.get_mut() {
ValueLabelAssignments::Starts(starts) => starts.push(start),
_ => panic!("Unexpected ValueLabelAssignments at this stage"),
},
Entry::Vacant(e) => {
e.insert(ValueLabelAssignments::Starts(vec![start]));
}
}
}
}
/// Creates a jump table in the function, to be used by `br_table` instructions.
pub fn create_jump_table(&mut self, data: JumpTableData) -> JumpTable {
self.func.create_jump_table(data)

View File

@ -1 +1 @@
{"files":{"Cargo.toml":"9827446df24b295abe90539e7ccfda7c54955f1cb44e4d49c3a4e5a66bfa5fae","LICENSE":"268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5","README.md":"87679cdb53e8cbec3b1aa45afb2124727c1c059f8bd10363d27daf318a9f9a36","src/code_translator.rs":"96fc0bf7b2c2f0de0596724bfe72d7fbcf5db1b676721fe024f5555ddbcbb7b6","src/environ/dummy.rs":"42df6db37892ea28e9a004934599d8bbcbd62db52f787486cd81a23f1a563613","src/environ/mod.rs":"617c147485038dfd797ab0ea71b4cfa9574d95d5d5b1ca362c6b7b6a462cf577","src/environ/spec.rs":"1c22dfbf956d80cbf34a6b8087dfb38b1f73a5cf010c341b673dba4286468bfe","src/func_translator.rs":"b27debdc0d17f30ecfa7a9bf4bdeea6054966507b5d398ccd4165574da4f674a","src/lib.rs":"95183fc86a20687e547d2edbd9868681005f0c3a2ca1ae1471e2ae38098f85c6","src/module_translator.rs":"ac54c24aaa3775f72ccd16d1781be648bb0e83ea83909f933d07e86ef1879213","src/sections_translator.rs":"55290a6b5d2a71719404d4d5f08a389dbf37c053264d17f3b292a57d1bdd5b62","src/state.rs":"1b1fa08736702d062c49118fba67f0a13752b4d863c1d11abd90eeb219777a23","src/translation_utils.rs":"50b45794018e1c471694f4f60707329213c9fb4153798a879953a479213b8a56","tests/wasm_testsuite.rs":"c6eac90ebdb6b58d8247c22e04454d95943c5ab0621084b624eb20c0ce2a96a3"},"package":null}
{"files":{"Cargo.toml":"9827446df24b295abe90539e7ccfda7c54955f1cb44e4d49c3a4e5a66bfa5fae","LICENSE":"268872b9816f90fd8e85db5a28d33f8150ebb8dd016653fb39ef1f94f2686bc5","README.md":"87679cdb53e8cbec3b1aa45afb2124727c1c059f8bd10363d27daf318a9f9a36","src/code_translator.rs":"56a399a242c9b59c25104e5c3faa81d289d0d441d17617a20b0061fbd9297aa3","src/environ/dummy.rs":"e2d1ac0aee88ad38a2f8b457253f70154d243d000e8a221e2a076ba10419b852","src/environ/mod.rs":"617c147485038dfd797ab0ea71b4cfa9574d95d5d5b1ca362c6b7b6a462cf577","src/environ/spec.rs":"f45b20f6f9e60d94eb13829168ce8f7078ac61282689d43f58e1fccf9815d488","src/func_translator.rs":"c9ac6fd676fdcdd119346a3ca5ee891a56f4f918b06b9b1b7ae5f4288fd92826","src/lib.rs":"0897b0270e746961db3d4dc74e5f766aced0ef23f870399aa9e685f1ec62ea83","src/module_translator.rs":"ac54c24aaa3775f72ccd16d1781be648bb0e83ea83909f933d07e86ef1879213","src/sections_translator.rs":"55290a6b5d2a71719404d4d5f08a389dbf37c053264d17f3b292a57d1bdd5b62","src/state.rs":"9e4f67900439f6aa18cfa3f16c694487374ddf42530db4504bccab0ebc360c96","src/translation_utils.rs":"72ccd5bed655f1a215873218d004b087f692b0d060af0f57773b961b75214cac","tests/wasm_testsuite.rs":"9b4e008587c61377cf38f9d0e4635418ee38e32a865db8da5dfc6e0fae047436"},"package":null}

View File

@ -30,7 +30,7 @@ use crate::translation_utils::{FuncIndex, MemoryIndex, SignatureIndex, TableInde
use core::{i32, u32};
use cranelift_codegen::ir::condcodes::{FloatCC, IntCC};
use cranelift_codegen::ir::types::*;
use cranelift_codegen::ir::{self, InstBuilder, JumpTableData, MemFlags};
use cranelift_codegen::ir::{self, InstBuilder, JumpTableData, MemFlags, ValueLabel};
use cranelift_codegen::packed_option::ReservedValue;
use cranelift_frontend::{FunctionBuilder, Variable};
use wasmparser::{MemoryImmediate, Operator};
@ -57,21 +57,28 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
* disappear in the Cranelift Code
***********************************************************************************/
Operator::GetLocal { local_index } => {
state.push1(builder.use_var(Variable::with_u32(local_index)))
let val = builder.use_var(Variable::with_u32(local_index));
state.push1(val);
let label = ValueLabel::from_u32(local_index);
builder.set_val_label(val, label);
}
Operator::SetLocal { local_index } => {
let val = state.pop1();
builder.def_var(Variable::with_u32(local_index), val);
let label = ValueLabel::from_u32(local_index);
builder.set_val_label(val, label);
}
Operator::TeeLocal { local_index } => {
let val = state.peek1();
builder.def_var(Variable::with_u32(local_index), val);
let label = ValueLabel::from_u32(local_index);
builder.set_val_label(val, label);
}
/********************************** Globals ****************************************
* `get_global` and `set_global` are handled by the environment.
***********************************************************************************/
Operator::GetGlobal { global_index } => {
let val = match state.get_global(builder.func, global_index, environ) {
let val = match state.get_global(builder.func, global_index, environ)? {
GlobalVariable::Const(val) => val,
GlobalVariable::Memory { gv, offset, ty } => {
let addr = builder.ins().global_value(environ.pointer_type(), gv);
@ -82,7 +89,7 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
state.push1(val);
}
Operator::SetGlobal { global_index } => {
match state.get_global(builder.func, global_index, environ) {
match state.get_global(builder.func, global_index, environ)? {
GlobalVariable::Const(_) => panic!("global #{} is a constant", global_index),
GlobalVariable::Memory { gv, offset, ty } => {
let addr = builder.ins().global_value(environ.pointer_type(), gv);
@ -137,7 +144,7 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
builder.ins().jump(loop_body, &[]);
state.push_loop(loop_body, next, num_return_values(ty));
builder.switch_to_block(loop_body);
environ.translate_loop_header(builder.cursor());
environ.translate_loop_header(builder.cursor())?;
}
Operator::If { ty } => {
let val = state.pop1();
@ -348,7 +355,7 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
* argument referring to an index in the external functions table of the module.
************************************************************************************/
Operator::Call { function_index } => {
let (fref, num_args) = state.get_direct_func(builder.func, function_index, environ);
let (fref, num_args) = state.get_direct_func(builder.func, function_index, environ)?;
let call = environ.translate_call(
builder.cursor(),
FuncIndex::from_u32(function_index),
@ -369,8 +376,8 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
Operator::CallIndirect { index, table_index } => {
// `index` is the index of the function's signature and `table_index` is the index of
// the table to search the function in.
let (sigref, num_args) = state.get_indirect_sig(builder.func, index, environ);
let table = state.get_table(builder.func, table_index, environ);
let (sigref, num_args) = state.get_indirect_sig(builder.func, index, environ)?;
let table = state.get_table(builder.func, table_index, environ)?;
let callee = state.pop1();
let call = environ.translate_call_indirect(
builder.cursor(),
@ -398,13 +405,13 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
// The WebAssembly MVP only supports one linear memory, but we expect the reserved
// argument to be a memory index.
let heap_index = MemoryIndex::from_u32(reserved);
let heap = state.get_heap(builder.func, reserved, environ);
let heap = state.get_heap(builder.func, reserved, environ)?;
let val = state.pop1();
state.push1(environ.translate_memory_grow(builder.cursor(), heap_index, heap, val)?)
}
Operator::MemorySize { reserved } => {
let heap_index = MemoryIndex::from_u32(reserved);
let heap = state.get_heap(builder.func, reserved, environ);
let heap = state.get_heap(builder.func, reserved, environ)?;
state.push1(environ.translate_memory_size(builder.cursor(), heap_index, heap)?);
}
/******************************* Load instructions ***********************************
@ -414,72 +421,72 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
Operator::I32Load8U {
memarg: MemoryImmediate { flags: _, offset },
} => {
translate_load(offset, ir::Opcode::Uload8, I32, builder, state, environ);
translate_load(offset, ir::Opcode::Uload8, I32, builder, state, environ)?;
}
Operator::I32Load16U {
memarg: MemoryImmediate { flags: _, offset },
} => {
translate_load(offset, ir::Opcode::Uload16, I32, builder, state, environ);
translate_load(offset, ir::Opcode::Uload16, I32, builder, state, environ)?;
}
Operator::I32Load8S {
memarg: MemoryImmediate { flags: _, offset },
} => {
translate_load(offset, ir::Opcode::Sload8, I32, builder, state, environ);
translate_load(offset, ir::Opcode::Sload8, I32, builder, state, environ)?;
}
Operator::I32Load16S {
memarg: MemoryImmediate { flags: _, offset },
} => {
translate_load(offset, ir::Opcode::Sload16, I32, builder, state, environ);
translate_load(offset, ir::Opcode::Sload16, I32, builder, state, environ)?;
}
Operator::I64Load8U {
memarg: MemoryImmediate { flags: _, offset },
} => {
translate_load(offset, ir::Opcode::Uload8, I64, builder, state, environ);
translate_load(offset, ir::Opcode::Uload8, I64, builder, state, environ)?;
}
Operator::I64Load16U {
memarg: MemoryImmediate { flags: _, offset },
} => {
translate_load(offset, ir::Opcode::Uload16, I64, builder, state, environ);
translate_load(offset, ir::Opcode::Uload16, I64, builder, state, environ)?;
}
Operator::I64Load8S {
memarg: MemoryImmediate { flags: _, offset },
} => {
translate_load(offset, ir::Opcode::Sload8, I64, builder, state, environ);
translate_load(offset, ir::Opcode::Sload8, I64, builder, state, environ)?;
}
Operator::I64Load16S {
memarg: MemoryImmediate { flags: _, offset },
} => {
translate_load(offset, ir::Opcode::Sload16, I64, builder, state, environ);
translate_load(offset, ir::Opcode::Sload16, I64, builder, state, environ)?;
}
Operator::I64Load32S {
memarg: MemoryImmediate { flags: _, offset },
} => {
translate_load(offset, ir::Opcode::Sload32, I64, builder, state, environ);
translate_load(offset, ir::Opcode::Sload32, I64, builder, state, environ)?;
}
Operator::I64Load32U {
memarg: MemoryImmediate { flags: _, offset },
} => {
translate_load(offset, ir::Opcode::Uload32, I64, builder, state, environ);
translate_load(offset, ir::Opcode::Uload32, I64, builder, state, environ)?;
}
Operator::I32Load {
memarg: MemoryImmediate { flags: _, offset },
} => {
translate_load(offset, ir::Opcode::Load, I32, builder, state, environ);
translate_load(offset, ir::Opcode::Load, I32, builder, state, environ)?;
}
Operator::F32Load {
memarg: MemoryImmediate { flags: _, offset },
} => {
translate_load(offset, ir::Opcode::Load, F32, builder, state, environ);
translate_load(offset, ir::Opcode::Load, F32, builder, state, environ)?;
}
Operator::I64Load {
memarg: MemoryImmediate { flags: _, offset },
} => {
translate_load(offset, ir::Opcode::Load, I64, builder, state, environ);
translate_load(offset, ir::Opcode::Load, I64, builder, state, environ)?;
}
Operator::F64Load {
memarg: MemoryImmediate { flags: _, offset },
} => {
translate_load(offset, ir::Opcode::Load, F64, builder, state, environ);
translate_load(offset, ir::Opcode::Load, F64, builder, state, environ)?;
}
/****************************** Store instructions ***********************************
* Wasm specifies an integer alignment flag but we drop it in Cranelift.
@ -497,7 +504,7 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
| Operator::F64Store {
memarg: MemoryImmediate { flags: _, offset },
} => {
translate_store(offset, ir::Opcode::Store, builder, state, environ);
translate_store(offset, ir::Opcode::Store, builder, state, environ)?;
}
Operator::I32Store8 {
memarg: MemoryImmediate { flags: _, offset },
@ -505,7 +512,7 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
| Operator::I64Store8 {
memarg: MemoryImmediate { flags: _, offset },
} => {
translate_store(offset, ir::Opcode::Istore8, builder, state, environ);
translate_store(offset, ir::Opcode::Istore8, builder, state, environ)?;
}
Operator::I32Store16 {
memarg: MemoryImmediate { flags: _, offset },
@ -513,12 +520,12 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
| Operator::I64Store16 {
memarg: MemoryImmediate { flags: _, offset },
} => {
translate_store(offset, ir::Opcode::Istore16, builder, state, environ);
translate_store(offset, ir::Opcode::Istore16, builder, state, environ)?;
}
Operator::I64Store32 {
memarg: MemoryImmediate { flags: _, offset },
} => {
translate_store(offset, ir::Opcode::Istore32, builder, state, environ);
translate_store(offset, ir::Opcode::Istore32, builder, state, environ)?;
}
/****************************** Nullary Operators ************************************/
Operator::I32Const { value } => state.push1(builder.ins().iconst(I32, i64::from(value))),
@ -1175,10 +1182,10 @@ fn translate_load<FE: FuncEnvironment + ?Sized>(
builder: &mut FunctionBuilder,
state: &mut TranslationState,
environ: &mut FE,
) {
) -> WasmResult<()> {
let addr32 = state.pop1();
// We don't yet support multiple linear memories.
let heap = state.get_heap(builder.func, 0, environ);
let heap = state.get_heap(builder.func, 0, environ)?;
let (base, offset) = get_heap_addr(heap, addr32, offset, environ.pointer_type(), builder);
// Note that we don't set `is_aligned` here, even if the load instruction's
// alignment immediate says it's aligned, because WebAssembly's immediate
@ -1188,6 +1195,7 @@ fn translate_load<FE: FuncEnvironment + ?Sized>(
.ins()
.Load(opcode, result_ty, flags, offset.into(), base);
state.push1(dfg.first_result(load));
Ok(())
}
/// Translate a store instruction.
@ -1197,18 +1205,19 @@ fn translate_store<FE: FuncEnvironment + ?Sized>(
builder: &mut FunctionBuilder,
state: &mut TranslationState,
environ: &mut FE,
) {
) -> WasmResult<()> {
let (addr32, val) = state.pop2();
let val_ty = builder.func.dfg.value_type(val);
// We don't yet support multiple linear memories.
let heap = state.get_heap(builder.func, 0, environ);
let heap = state.get_heap(builder.func, 0, environ)?;
let (base, offset) = get_heap_addr(heap, addr32, offset, environ.pointer_type(), builder);
// See the comments in `translate_load` about the flags.
let flags = MemFlags::new();
builder
.ins()
.Store(opcode, val_ty, flags, offset.into(), val, base);
Ok(())
}
fn translate_icmp(cc: IntCC, builder: &mut FunctionBuilder, state: &mut TranslationState) {

View File

@ -121,16 +121,20 @@ pub struct DummyEnvironment {
/// How to return from functions.
return_mode: ReturnMode,
/// Instructs to collect debug data during translation.
debug_info: bool,
}
impl DummyEnvironment {
/// Creates a new `DummyEnvironment` instance.
pub fn new(config: TargetFrontendConfig, return_mode: ReturnMode) -> Self {
pub fn new(config: TargetFrontendConfig, return_mode: ReturnMode, debug_info: bool) -> Self {
Self {
info: DummyModuleInfo::new(config),
trans: FuncTranslator::new(),
func_bytecode_sizes: Vec::new(),
return_mode,
debug_info,
}
}
@ -182,18 +186,26 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ
self.mod_info.config
}
fn make_global(&mut self, func: &mut ir::Function, index: GlobalIndex) -> GlobalVariable {
fn return_mode(&self) -> ReturnMode {
self.return_mode
}
fn make_global(
&mut self,
func: &mut ir::Function,
index: GlobalIndex,
) -> WasmResult<GlobalVariable> {
// Just create a dummy `vmctx` global.
let offset = cast::i32((index.index() * 8) + 8).unwrap().into();
let vmctx = func.create_global_value(ir::GlobalValueData::VMContext {});
GlobalVariable::Memory {
Ok(GlobalVariable::Memory {
gv: vmctx,
offset,
ty: self.mod_info.globals[index].entity.ty,
}
})
}
fn make_heap(&mut self, func: &mut ir::Function, _index: MemoryIndex) -> ir::Heap {
fn make_heap(&mut self, func: &mut ir::Function, _index: MemoryIndex) -> WasmResult<ir::Heap> {
// Create a static heap whose base address is stored at `vmctx+0`.
let addr = func.create_global_value(ir::GlobalValueData::VMContext);
let gv = func.create_global_value(ir::GlobalValueData::Load {
@ -203,7 +215,7 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ
readonly: true,
});
func.create_heap(ir::HeapData {
Ok(func.create_heap(ir::HeapData {
base: gv,
min_size: 0.into(),
offset_guard_size: 0x8000_0000.into(),
@ -211,10 +223,10 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ
bound: 0x1_0000_0000.into(),
},
index_type: I32,
})
}))
}
fn make_table(&mut self, func: &mut ir::Function, _index: TableIndex) -> ir::Table {
fn make_table(&mut self, func: &mut ir::Function, _index: TableIndex) -> WasmResult<ir::Table> {
// Create a table whose base address is stored at `vmctx+0`.
let vmctx = func.create_global_value(ir::GlobalValueData::VMContext);
let base_gv = func.create_global_value(ir::GlobalValueData::Load {
@ -230,32 +242,40 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ
readonly: true,
});
func.create_table(ir::TableData {
Ok(func.create_table(ir::TableData {
base_gv,
min_size: Uimm64::new(0),
bound_gv,
element_size: Uimm64::from(u64::from(self.pointer_bytes()) * 2),
index_type: I32,
})
}))
}
fn make_indirect_sig(&mut self, func: &mut ir::Function, index: SignatureIndex) -> ir::SigRef {
fn make_indirect_sig(
&mut self,
func: &mut ir::Function,
index: SignatureIndex,
) -> WasmResult<ir::SigRef> {
// A real implementation would probably change the calling convention and add `vmctx` and
// signature index arguments.
func.import_signature(self.vmctx_sig(index))
Ok(func.import_signature(self.vmctx_sig(index)))
}
fn make_direct_func(&mut self, func: &mut ir::Function, index: FuncIndex) -> ir::FuncRef {
fn make_direct_func(
&mut self,
func: &mut ir::Function,
index: FuncIndex,
) -> WasmResult<ir::FuncRef> {
let sigidx = self.mod_info.functions[index].entity;
// A real implementation would probably add a `vmctx` argument.
// And maybe attempt some signature de-duplication.
let signature = func.import_signature(self.vmctx_sig(sigidx));
let name = get_func_name(index);
func.import_function(ir::ExtFuncData {
Ok(func.import_function(ir::ExtFuncData {
name,
signature,
colocated: false,
})
}))
}
fn translate_call_indirect(
@ -340,10 +360,6 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ
) -> WasmResult<ir::Value> {
Ok(pos.ins().iconst(I32, -1))
}
fn return_mode(&self) -> ReturnMode {
self.return_mode
}
}
impl<'data> ModuleEnvironment<'data> for DummyEnvironment {
@ -470,6 +486,9 @@ impl<'data> ModuleEnvironment<'data> for DummyEnvironment {
let name = get_func_name(func_index);
let sig = func_environ.vmctx_sig(self.get_func_type(func_index));
let mut func = ir::Function::with_name_signature(name, sig);
if self.debug_info {
func.collect_debug_info();
}
self.trans
.translate(body_bytes, body_offset, &mut func, &mut func_environ)?;
func

View File

@ -39,7 +39,7 @@ pub enum GlobalVariable {
///
/// When a WebAssembly function can't be translated, one of these error codes will be returned
/// to describe the failure.
#[derive(Fail, Debug, PartialEq, Eq)]
#[derive(Fail, Debug)]
pub enum WasmError {
/// The input WebAssembly code is invalid.
///
@ -67,6 +67,10 @@ pub enum WasmError {
/// [limits]: https://cranelift.readthedocs.io/en/latest/ir.html#implementation-limits
#[fail(display = "Implementation limit exceeded")]
ImplLimitExceeded,
/// Any user-defined error.
#[fail(display = "User error: {}", _0)]
User(std::string::String),
}
impl From<BinaryReaderError> for WasmError {
@ -110,6 +114,13 @@ pub trait FuncEnvironment {
self.target_config().pointer_bytes()
}
/// Should the code be structured to use a single `fallthrough_return` instruction at the end
/// of the function body, rather than `return` instructions as needed? This is used by VMs
/// to append custom epilogues.
fn return_mode(&self) -> ReturnMode {
ReturnMode::NormalReturns
}
/// Set up the necessary preamble definitions in `func` to access the global variable
/// identified by `index`.
///
@ -117,19 +128,23 @@ pub trait FuncEnvironment {
///
/// Return the global variable reference that should be used to access the global and the
/// WebAssembly type of the global.
fn make_global(&mut self, func: &mut ir::Function, index: GlobalIndex) -> GlobalVariable;
fn make_global(
&mut self,
func: &mut ir::Function,
index: GlobalIndex,
) -> WasmResult<GlobalVariable>;
/// Set up the necessary preamble definitions in `func` to access the linear memory identified
/// by `index`.
///
/// The index space covers both imported and locally declared memories.
fn make_heap(&mut self, func: &mut ir::Function, index: MemoryIndex) -> ir::Heap;
fn make_heap(&mut self, func: &mut ir::Function, index: MemoryIndex) -> WasmResult<ir::Heap>;
/// Set up the necessary preamble definitions in `func` to access the table identified
/// by `index`.
///
/// The index space covers both imported and locally declared tables.
fn make_table(&mut self, func: &mut ir::Function, index: TableIndex) -> ir::Table;
fn make_table(&mut self, func: &mut ir::Function, index: TableIndex) -> WasmResult<ir::Table>;
/// Set up a signature definition in the preamble of `func` that can be used for an indirect
/// call with signature `index`.
@ -140,7 +155,11 @@ pub trait FuncEnvironment {
///
/// The signature will only be used for indirect calls, even if the module has direct function
/// calls with the same WebAssembly type.
fn make_indirect_sig(&mut self, func: &mut ir::Function, index: SignatureIndex) -> ir::SigRef;
fn make_indirect_sig(
&mut self,
func: &mut ir::Function,
index: SignatureIndex,
) -> WasmResult<ir::SigRef>;
/// Set up an external function definition in the preamble of `func` that can be used to
/// directly call the function `index`.
@ -153,7 +172,11 @@ pub trait FuncEnvironment {
///
/// The function's signature will only be used for direct calls, even if the module has
/// indirect calls with the same WebAssembly type.
fn make_direct_func(&mut self, func: &mut ir::Function, index: FuncIndex) -> ir::FuncRef;
fn make_direct_func(
&mut self,
func: &mut ir::Function,
index: FuncIndex,
) -> WasmResult<ir::FuncRef>;
/// Translate a `call_indirect` WebAssembly instruction at `pos`.
///
@ -226,15 +249,9 @@ pub trait FuncEnvironment {
///
/// This can be used to insert explicit interrupt or safepoint checking at
/// the beginnings of loops.
fn translate_loop_header(&mut self, _pos: FuncCursor) {
fn translate_loop_header(&mut self, _pos: FuncCursor) -> WasmResult<()> {
// By default, don't emit anything.
}
/// Should the code be structured to use a single `fallthrough_return` instruction at the end
/// of the function body, rather than `return` instructions as needed? This is used by VMs
/// to append custom epilogues.
fn return_mode(&self) -> ReturnMode {
ReturnMode::NormalReturns
Ok(())
}
}

View File

@ -5,10 +5,11 @@
//! WebAssembly module and the runtime environment.
use crate::code_translator::translate_operator;
use crate::environ::{FuncEnvironment, ReturnMode, WasmResult};
use crate::environ::{FuncEnvironment, ReturnMode, WasmError, WasmResult};
use crate::state::TranslationState;
use crate::translation_utils::get_vmctx_value_label;
use cranelift_codegen::entity::EntityRef;
use cranelift_codegen::ir::{self, Ebb, InstBuilder};
use cranelift_codegen::ir::{self, Ebb, InstBuilder, ValueLabel};
use cranelift_codegen::timing;
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext, Variable};
use log::info;
@ -84,6 +85,7 @@ impl FuncTranslator {
// This clears the `FunctionBuilderContext`.
let mut builder = FunctionBuilder::new(func, &mut self.func_ctx);
builder.set_srcloc(cur_srcloc(&reader));
let entry_block = builder.create_ebb();
builder.append_ebb_params_for_function_params(entry_block);
builder.switch_to_block(entry_block); // This also creates values for the arguments.
@ -127,6 +129,10 @@ fn declare_wasm_parameters(builder: &mut FunctionBuilder, entry_block: Ebb) -> u
let param_value = builder.ebb_params(entry_block)[i];
builder.def_var(local, param_value);
}
if param_type.purpose == ir::ArgumentPurpose::VMContext {
let param_value = builder.ebb_params(entry_block)[i];
builder.set_val_label(param_value, get_vmctx_value_label());
}
}
next_local
@ -147,7 +153,7 @@ fn parse_local_decls(
for _ in 0..local_count {
builder.set_srcloc(cur_srcloc(reader));
let (count, ty) = reader.read_local_decl(&mut locals_total)?;
declare_locals(builder, count, ty, &mut next_local);
declare_locals(builder, count, ty, &mut next_local)?;
}
Ok(())
@ -161,7 +167,7 @@ fn declare_locals(
count: u32,
wasm_type: wasmparser::Type,
next_local: &mut usize,
) {
) -> WasmResult<()> {
// All locals are initialized to 0.
use wasmparser::Type::*;
let zeroval = match wasm_type {
@ -169,7 +175,7 @@ fn declare_locals(
I64 => builder.ins().iconst(ir::types::I64, 0),
F32 => builder.ins().f32const(ir::immediates::Ieee32::with_bits(0)),
F64 => builder.ins().f64const(ir::immediates::Ieee64::with_bits(0)),
_ => panic!("invalid local type"),
_ => return Err(WasmError::Unsupported("unsupported local type")),
};
let ty = builder.func.dfg.value_type(zeroval);
@ -177,8 +183,10 @@ fn declare_locals(
let local = Variable::new(*next_local);
builder.declare_var(local, ty);
builder.def_var(local, zeroval);
builder.set_val_label(zeroval, ValueLabel::new(*next_local));
*next_local += 1;
}
Ok(())
}
/// Parse the function body in `reader`.
@ -264,6 +272,7 @@ mod tests {
pointer_width: PointerWidth::U64,
},
ReturnMode::NormalReturns,
false,
);
let mut ctx = Context::new();
@ -303,6 +312,7 @@ mod tests {
pointer_width: PointerWidth::U64,
},
ReturnMode::NormalReturns,
false,
);
let mut ctx = Context::new();
@ -350,6 +360,7 @@ mod tests {
pointer_width: PointerWidth::U64,
},
ReturnMode::NormalReturns,
false,
);
let mut ctx = Context::new();

View File

@ -38,9 +38,16 @@ extern crate alloc as std;
extern crate std;
#[cfg(not(feature = "std"))]
use hashmap_core::{map as hash_map, HashMap};
use hashmap_core::{
hash_map::Entry::{Occupied, Vacant},
map as hash_map, HashMap,
};
#[cfg(feature = "std")]
use std::collections::{hash_map, HashMap};
use std::collections::{
hash_map,
hash_map::Entry::{Occupied, Vacant},
HashMap,
};
mod code_translator;
mod environ;
@ -57,9 +64,9 @@ pub use crate::environ::{
pub use crate::func_translator::FuncTranslator;
pub use crate::module_translator::translate_module;
pub use crate::translation_utils::{
DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, FuncIndex, Global,
GlobalIndex, GlobalInit, Memory, MemoryIndex, SignatureIndex, Table, TableElementType,
TableIndex,
get_vmctx_value_label, DefinedFuncIndex, DefinedGlobalIndex, DefinedMemoryIndex,
DefinedTableIndex, FuncIndex, Global, GlobalIndex, GlobalInit, Memory, MemoryIndex,
SignatureIndex, Table, TableElementType, TableIndex,
};
/// Version number of this crate.

View File

@ -3,8 +3,8 @@
//! The `TranslationState` struct defined in this module is used to keep track of the WebAssembly
//! value and control stacks during the translation of a single function.
use super::HashMap;
use crate::environ::{FuncEnvironment, GlobalVariable};
use super::{HashMap, Occupied, Vacant};
use crate::environ::{FuncEnvironment, GlobalVariable, WasmResult};
use crate::translation_utils::{FuncIndex, GlobalIndex, MemoryIndex, SignatureIndex, TableIndex};
use cranelift_codegen::ir::{self, Ebb, Inst, Value};
use std::vec::Vec;
@ -285,12 +285,12 @@ impl TranslationState {
func: &mut ir::Function,
index: u32,
environ: &mut FE,
) -> GlobalVariable {
) -> WasmResult<GlobalVariable> {
let index = GlobalIndex::from_u32(index);
*self
.globals
.entry(index)
.or_insert_with(|| environ.make_global(func, index))
match self.globals.entry(index) {
Occupied(entry) => Ok(*entry.get()),
Vacant(entry) => Ok(*entry.insert(environ.make_global(func, index)?)),
}
}
/// Get the `Heap` reference that should be used to access linear memory `index`.
@ -300,12 +300,12 @@ impl TranslationState {
func: &mut ir::Function,
index: u32,
environ: &mut FE,
) -> ir::Heap {
) -> WasmResult<ir::Heap> {
let index = MemoryIndex::from_u32(index);
*self
.heaps
.entry(index)
.or_insert_with(|| environ.make_heap(func, index))
match self.heaps.entry(index) {
Occupied(entry) => Ok(*entry.get()),
Vacant(entry) => Ok(*entry.insert(environ.make_heap(func, index)?)),
}
}
/// Get the `Table` reference that should be used to access table `index`.
@ -315,12 +315,12 @@ impl TranslationState {
func: &mut ir::Function,
index: u32,
environ: &mut FE,
) -> ir::Table {
) -> WasmResult<ir::Table> {
let index = TableIndex::from_u32(index);
*self
.tables
.entry(index)
.or_insert_with(|| environ.make_table(func, index))
match self.tables.entry(index) {
Occupied(entry) => Ok(*entry.get()),
Vacant(entry) => Ok(*entry.insert(environ.make_table(func, index)?)),
}
}
/// Get the `SigRef` reference that should be used to make an indirect call with signature
@ -332,12 +332,15 @@ impl TranslationState {
func: &mut ir::Function,
index: u32,
environ: &mut FE,
) -> (ir::SigRef, usize) {
) -> WasmResult<(ir::SigRef, usize)> {
let index = SignatureIndex::from_u32(index);
*self.signatures.entry(index).or_insert_with(|| {
let sig = environ.make_indirect_sig(func, index);
(sig, normal_args(&func.dfg.signatures[sig]))
})
match self.signatures.entry(index) {
Occupied(entry) => Ok(*entry.get()),
Vacant(entry) => {
let sig = environ.make_indirect_sig(func, index)?;
Ok(*entry.insert((sig, normal_args(&func.dfg.signatures[sig]))))
}
}
}
/// Get the `FuncRef` reference that should be used to make a direct call to function
@ -349,13 +352,16 @@ impl TranslationState {
func: &mut ir::Function,
index: u32,
environ: &mut FE,
) -> (ir::FuncRef, usize) {
) -> WasmResult<(ir::FuncRef, usize)> {
let index = FuncIndex::from_u32(index);
*self.functions.entry(index).or_insert_with(|| {
let fref = environ.make_direct_func(func, index);
let sig = func.dfg.ext_funcs[fref].signature;
(fref, normal_args(&func.dfg.signatures[sig]))
})
match self.functions.entry(index) {
Occupied(entry) => Ok(*entry.get()),
Vacant(entry) => {
let fref = environ.make_direct_func(func, index)?;
let sig = func.dfg.ext_funcs[fref].signature;
Ok(*entry.insert((fref, normal_args(&func.dfg.signatures[sig]))))
}
}
}
}

View File

@ -140,3 +140,9 @@ pub fn num_return_values(ty: wasmparser::Type) -> usize {
_ => panic!("unsupported return value type"),
}
}
/// Special VMContext value label. It is tracked as 0xffff_fffe label.
pub fn get_vmctx_value_label() -> ir::ValueLabel {
const VMCTX_LABEL: u32 = 0xffff_fffe;
ir::ValueLabel::from_u32(VMCTX_LABEL)
}

View File

@ -73,7 +73,7 @@ fn handle_module(path: &Path, flags: &Flags, return_mode: ReturnMode) {
};
let triple = triple!("riscv64");
let isa = isa::lookup(triple).unwrap().finish(flags.clone());
let mut dummy_environ = DummyEnvironment::new(isa.frontend_config(), return_mode);
let mut dummy_environ = DummyEnvironment::new(isa.frontend_config(), return_mode, false);
translate_module(&data, &mut dummy_environ).unwrap();