From d2d0a0f36b060b89006614b075d7338dc30da9ef Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Wed, 17 Nov 2021 13:04:17 -0800 Subject: [PATCH] Remove Peepmatic!!! Peepmatic was an early attempt at a DSL for peephole optimizations, with the idea that maybe sometime in the future we could user it for instruction selection as well. It didn't really pan out, however: * Peepmatic wasn't quite flexible enough, and adding new operators or snippets of code implemented externally in Rust was a bit of a pain. * The performance was never competitive with the hand-written peephole optimizers. It was *very* size efficient, but that came at the cost of run-time efficiency. Everything was table-based and interpreted, rather than generating any Rust code. Ultimately, because of these reasons, we never turned Peepmatic on by default. These days, we just landed the ISLE domain-specific language, and it is better suited than Peepmatic for all the things that Peepmatic was originally designed to do. It is more flexible and easy to integrate with external Rust code. It is has better time efficiency, meeting or even beating hand-written code. I think a small part of the reason why ISLE excels in these things is because its design was informed by Peepmatic's failures. I still plan on continuing Peepmatic's mission to make Cranelift's peephole optimizer passes generated from DSL rewrite rules, but using ISLE instead of Peepmatic. Thank you Peepmatic, rest in peace! --- .github/labeler.yml | 4 - .github/subscribe-to-label.json | 2 +- .github/workflows/main.yml | 30 +- Cargo.lock | 144 -- ci/run-tests.sh | 1 - cranelift/Cargo.toml | 4 +- cranelift/codegen/Cargo.toml | 11 - cranelift/codegen/build.rs | 10 - cranelift/codegen/meta/src/gen_inst.rs | 2 +- .../codegen/src/isa/x64/inst/emit_tests.rs | 6 +- cranelift/codegen/src/lib.rs | 3 - cranelift/codegen/src/peepmatic.rs | 1317 --------------- cranelift/codegen/src/simple_preopt.rs | 42 - cranelift/filetests/Cargo.toml | 1 - .../filetests/filetests/peepmatic/branch.clif | 82 - .../peepmatic/div_by_const_indirect.clif | 56 - .../div_by_const_non_power_of_2.clif | 267 --- .../peepmatic/div_by_const_power_of_2.clif | 293 ---- ...zations_after_replacing_with_an_alias.clif | 15 - ...order_instructions_when_transplanting.clif | 23 - .../fold-extended-move-wraparound.clif | 15 - .../rem_by_const_non_power_of_2.clif | 286 ---- .../peepmatic/rem_by_const_power_of_2.clif | 292 ---- ...ing_instructions_and_cfg_predecessors.clif | 23 - .../filetests/peepmatic/simplify32.clif | 61 - .../filetests/peepmatic/simplify64.clif | 327 ---- ...plify_instruction_into_alias_of_value.clif | 21 - cranelift/filetests/src/lib.rs | 2 - cranelift/filetests/src/test_peepmatic.rs | 55 - cranelift/filetests/src/test_simple_preopt.rs | 12 +- cranelift/peepmatic/Cargo.toml | 22 - cranelift/peepmatic/LICENSE | 220 --- cranelift/peepmatic/README.md | 418 ----- .../peepmatic/crates/automata/Cargo.toml | 21 - cranelift/peepmatic/crates/automata/LICENSE | 220 --- .../peepmatic/crates/automata/src/dot.rs | 273 --- .../peepmatic/crates/automata/src/lib.rs | 1024 ------------ .../crates/automata/src/output_impls.rs | 130 -- .../crates/automata/src/serde_impls.rs | 220 --- cranelift/peepmatic/crates/fuzzing/Cargo.toml | 24 - .../peepmatic/crates/fuzzing/src/automata.rs | 201 --- .../peepmatic/crates/fuzzing/src/compile.rs | 79 - .../peepmatic/crates/fuzzing/src/interp.rs | 367 ---- cranelift/peepmatic/crates/fuzzing/src/lib.rs | 98 -- .../peepmatic/crates/fuzzing/src/parser.rs | 30 - cranelift/peepmatic/crates/macro/Cargo.toml | 19 - cranelift/peepmatic/crates/macro/LICENSE | 220 --- .../peepmatic/crates/macro/src/child_nodes.rs | 115 -- .../crates/macro/src/into_dyn_ast_ref.rs | 23 - cranelift/peepmatic/crates/macro/src/lib.rs | 150 -- cranelift/peepmatic/crates/macro/src/span.rs | 55 - cranelift/peepmatic/crates/runtime/Cargo.toml | 28 - cranelift/peepmatic/crates/runtime/LICENSE | 220 --- cranelift/peepmatic/crates/runtime/src/cc.rs | 92 - .../peepmatic/crates/runtime/src/error.rs | 44 - .../crates/runtime/src/instruction_set.rs | 144 -- .../crates/runtime/src/integer_interner.rs | 192 --- cranelift/peepmatic/crates/runtime/src/lib.rs | 33 - .../peepmatic/crates/runtime/src/linear.rs | 241 --- .../crates/runtime/src/optimizations.rs | 93 -- .../peepmatic/crates/runtime/src/optimizer.rs | 551 ------ .../peepmatic/crates/runtime/src/part.rs | 128 -- .../peepmatic/crates/runtime/src/type.rs | 262 --- .../peepmatic/crates/runtime/src/unquote.rs | 44 - cranelift/peepmatic/crates/souper/Cargo.toml | 19 - cranelift/peepmatic/crates/souper/LICENSE | 220 --- cranelift/peepmatic/crates/souper/src/lib.rs | 710 -------- .../peepmatic/crates/test-operator/Cargo.toml | 14 - .../peepmatic/crates/test-operator/LICENSE | 220 --- .../peepmatic/crates/test-operator/src/lib.rs | 211 --- cranelift/peepmatic/crates/test/Cargo.toml | 16 - cranelift/peepmatic/crates/test/src/lib.rs | 517 ------ .../peepmatic/crates/test/tests/tests.rs | 393 ----- cranelift/peepmatic/crates/traits/Cargo.toml | 11 - cranelift/peepmatic/crates/traits/src/lib.rs | 26 - .../peepmatic/crates/traits/src/operator.rs | 317 ---- .../peepmatic/crates/traits/src/typing.rs | 97 -- .../peepmatic/examples/mul-by-pow2.peepmatic | 3 - .../examples/redundant-bor.peepmatic | 13 - .../peepmatic/examples/redundant-bor.png | Bin 258513 -> 0 bytes cranelift/peepmatic/examples/simple.peepmatic | 11 - cranelift/peepmatic/src/ast.rs | 534 ------ cranelift/peepmatic/src/automatize.rs | 44 - cranelift/peepmatic/src/dot_fmt.rs | 140 -- cranelift/peepmatic/src/lib.rs | 189 --- cranelift/peepmatic/src/linear_passes.rs | 594 ------- cranelift/peepmatic/src/linearize.rs | 807 --------- cranelift/peepmatic/src/parser.rs | 981 ----------- cranelift/peepmatic/src/traversals.rs | 327 ---- cranelift/peepmatic/src/verify.rs | 1474 ----------------- cranelift/src/clif-util.rs | 16 - cranelift/src/souper_to_peepmatic.rs | 44 - fuzz/Cargo.toml | 1 - scripts/publish.rs | 9 - 94 files changed, 8 insertions(+), 17358 deletions(-) delete mode 100644 cranelift/codegen/src/peepmatic.rs delete mode 100644 cranelift/filetests/filetests/peepmatic/branch.clif delete mode 100644 cranelift/filetests/filetests/peepmatic/div_by_const_indirect.clif delete mode 100644 cranelift/filetests/filetests/peepmatic/div_by_const_non_power_of_2.clif delete mode 100644 cranelift/filetests/filetests/peepmatic/div_by_const_power_of_2.clif delete mode 100644 cranelift/filetests/filetests/peepmatic/do_not_keep_applying_optimizations_after_replacing_with_an_alias.clif delete mode 100644 cranelift/filetests/filetests/peepmatic/do_not_reorder_instructions_when_transplanting.clif delete mode 100644 cranelift/filetests/filetests/peepmatic/fold-extended-move-wraparound.clif delete mode 100644 cranelift/filetests/filetests/peepmatic/rem_by_const_non_power_of_2.clif delete mode 100644 cranelift/filetests/filetests/peepmatic/rem_by_const_power_of_2.clif delete mode 100644 cranelift/filetests/filetests/peepmatic/replace_branching_instructions_and_cfg_predecessors.clif delete mode 100644 cranelift/filetests/filetests/peepmatic/simplify32.clif delete mode 100644 cranelift/filetests/filetests/peepmatic/simplify64.clif delete mode 100644 cranelift/filetests/filetests/peepmatic/simplify_instruction_into_alias_of_value.clif delete mode 100644 cranelift/filetests/src/test_peepmatic.rs delete mode 100644 cranelift/peepmatic/Cargo.toml delete mode 100644 cranelift/peepmatic/LICENSE delete mode 100644 cranelift/peepmatic/README.md delete mode 100644 cranelift/peepmatic/crates/automata/Cargo.toml delete mode 100644 cranelift/peepmatic/crates/automata/LICENSE delete mode 100644 cranelift/peepmatic/crates/automata/src/dot.rs delete mode 100644 cranelift/peepmatic/crates/automata/src/lib.rs delete mode 100644 cranelift/peepmatic/crates/automata/src/output_impls.rs delete mode 100644 cranelift/peepmatic/crates/automata/src/serde_impls.rs delete mode 100644 cranelift/peepmatic/crates/fuzzing/Cargo.toml delete mode 100644 cranelift/peepmatic/crates/fuzzing/src/automata.rs delete mode 100644 cranelift/peepmatic/crates/fuzzing/src/compile.rs delete mode 100644 cranelift/peepmatic/crates/fuzzing/src/interp.rs delete mode 100644 cranelift/peepmatic/crates/fuzzing/src/lib.rs delete mode 100644 cranelift/peepmatic/crates/fuzzing/src/parser.rs delete mode 100644 cranelift/peepmatic/crates/macro/Cargo.toml delete mode 100644 cranelift/peepmatic/crates/macro/LICENSE delete mode 100644 cranelift/peepmatic/crates/macro/src/child_nodes.rs delete mode 100644 cranelift/peepmatic/crates/macro/src/into_dyn_ast_ref.rs delete mode 100644 cranelift/peepmatic/crates/macro/src/lib.rs delete mode 100644 cranelift/peepmatic/crates/macro/src/span.rs delete mode 100644 cranelift/peepmatic/crates/runtime/Cargo.toml delete mode 100644 cranelift/peepmatic/crates/runtime/LICENSE delete mode 100644 cranelift/peepmatic/crates/runtime/src/cc.rs delete mode 100644 cranelift/peepmatic/crates/runtime/src/error.rs delete mode 100644 cranelift/peepmatic/crates/runtime/src/instruction_set.rs delete mode 100644 cranelift/peepmatic/crates/runtime/src/integer_interner.rs delete mode 100644 cranelift/peepmatic/crates/runtime/src/lib.rs delete mode 100644 cranelift/peepmatic/crates/runtime/src/linear.rs delete mode 100644 cranelift/peepmatic/crates/runtime/src/optimizations.rs delete mode 100644 cranelift/peepmatic/crates/runtime/src/optimizer.rs delete mode 100644 cranelift/peepmatic/crates/runtime/src/part.rs delete mode 100644 cranelift/peepmatic/crates/runtime/src/type.rs delete mode 100644 cranelift/peepmatic/crates/runtime/src/unquote.rs delete mode 100644 cranelift/peepmatic/crates/souper/Cargo.toml delete mode 100644 cranelift/peepmatic/crates/souper/LICENSE delete mode 100644 cranelift/peepmatic/crates/souper/src/lib.rs delete mode 100644 cranelift/peepmatic/crates/test-operator/Cargo.toml delete mode 100644 cranelift/peepmatic/crates/test-operator/LICENSE delete mode 100644 cranelift/peepmatic/crates/test-operator/src/lib.rs delete mode 100644 cranelift/peepmatic/crates/test/Cargo.toml delete mode 100644 cranelift/peepmatic/crates/test/src/lib.rs delete mode 100644 cranelift/peepmatic/crates/test/tests/tests.rs delete mode 100644 cranelift/peepmatic/crates/traits/Cargo.toml delete mode 100644 cranelift/peepmatic/crates/traits/src/lib.rs delete mode 100644 cranelift/peepmatic/crates/traits/src/operator.rs delete mode 100644 cranelift/peepmatic/crates/traits/src/typing.rs delete mode 100644 cranelift/peepmatic/examples/mul-by-pow2.peepmatic delete mode 100644 cranelift/peepmatic/examples/redundant-bor.peepmatic delete mode 100644 cranelift/peepmatic/examples/redundant-bor.png delete mode 100644 cranelift/peepmatic/examples/simple.peepmatic delete mode 100644 cranelift/peepmatic/src/ast.rs delete mode 100644 cranelift/peepmatic/src/automatize.rs delete mode 100644 cranelift/peepmatic/src/dot_fmt.rs delete mode 100644 cranelift/peepmatic/src/lib.rs delete mode 100644 cranelift/peepmatic/src/linear_passes.rs delete mode 100644 cranelift/peepmatic/src/linearize.rs delete mode 100644 cranelift/peepmatic/src/parser.rs delete mode 100644 cranelift/peepmatic/src/traversals.rs delete mode 100644 cranelift/peepmatic/src/verify.rs mode change 100644 => 100755 cranelift/src/clif-util.rs delete mode 100644 cranelift/src/souper_to_peepmatic.rs diff --git a/.github/labeler.yml b/.github/labeler.yml index 58899c25dd..97ab0c605d 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -29,10 +29,6 @@ "cranelift:area:x64": - "cranelift/codegen/src/isa/x64/**" -"cranelift:area:peepmatic": - - "cranelift/peepmatic/**" - - "cranelift/codegen/src/peepmatic.rs" - "cranelift:docs": - "cranelift/docs/**" diff --git a/.github/subscribe-to-label.json b/.github/subscribe-to-label.json index eb7b60bb41..184ea9ae54 100644 --- a/.github/subscribe-to-label.json +++ b/.github/subscribe-to-label.json @@ -1,6 +1,6 @@ { "cfallin": ["isle"], - "fitzgen": ["cranelift:area:peepmatic", "fuzzing", "isle", "wasmtime:ref-types"], + "fitzgen": ["fuzzing", "isle", "wasmtime:ref-types"], "peterhuene": ["wasmtime:api", "wasmtime:c-api"], "kubkon": ["wasi"] } diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 530f8941fa..64bbb0daf1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -88,8 +88,7 @@ jobs: cargo doc --no-deps --workspace \ --exclude wasmtime-cli \ --exclude test-programs \ - --exclude cranelift-codegen-meta \ - --exclude 'peepmatic*' + --exclude cranelift-codegen-meta - run: cargo doc --package cranelift-codegen-meta --document-private-items # Assemble the documentation, and always upload it as an artifact for @@ -204,33 +203,6 @@ jobs: - name: Check that the ISLE DSL files are up-to-date run: git diff --exit-code - rebuild_peephole_optimizers: - name: Rebuild Peephole Optimizers - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - submodules: true - - run: rustup update stable && rustup default stable - - name: Test `peepmatic` - run: cargo test --package 'peepmatic*' - - name: Rebuild Peepmatic-based peephole optimizers - run: | - cargo test \ - --features 'enable-peepmatic rebuild-peephole-optimizers' \ - peepmatic - working-directory: ./cranelift/codegen - - name: Upload rebuilt peephole optimizers - uses: actions/upload-artifact@v2 - with: - name: peephole-optimizers - path: cranelift/codegen/src/preopt.serialized - - name: Check that built peephole optimizers are up to date - run: git diff --exit-code - - name: Test with Peepmatic-based peephole optimizers - run: cargo test --features 'enable-peepmatic' - working-directory: ./cranelift - # Perform all tests (debug mode) for `wasmtime`. This runs stable/beta/nightly # channels of Rust as well as macOS/Linux/Windows. test: diff --git a/Cargo.lock b/Cargo.lock index 6c23c29480..1e20bca4e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -545,16 +545,12 @@ dependencies = [ "isle", "log", "miette", - "peepmatic", - "peepmatic-runtime", - "peepmatic-traits", "regalloc", "serde", "sha2", "smallvec", "souper-ir", "target-lexicon", - "wast 38.0.1", ] [[package]] @@ -730,7 +726,6 @@ dependencies = [ "filecheck", "indicatif", "log", - "peepmatic-souper", "pretty_env_logger", "rayon", "structopt", @@ -1204,12 +1199,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "fst" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e398fae362f4124bbe630d99519fb2d68a03e2e3a23b441028cdcdc4f4895687" - [[package]] name = "funty" version = "1.1.0" @@ -1994,109 +1983,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" -[[package]] -name = "peepmatic" -version = "0.78.0" -dependencies = [ - "anyhow", - "peepmatic-automata", - "peepmatic-macro", - "peepmatic-runtime", - "peepmatic-test-operator", - "peepmatic-traits", - "serde", - "wast 38.0.1", - "z3", -] - -[[package]] -name = "peepmatic-automata" -version = "0.78.0" -dependencies = [ - "serde", -] - -[[package]] -name = "peepmatic-fuzzing" -version = "0.66.0" -dependencies = [ - "arbitrary", - "bincode", - "env_logger 0.8.3", - "fst", - "log", - "peepmatic", - "peepmatic-automata", - "peepmatic-runtime", - "peepmatic-test", - "peepmatic-test-operator", - "peepmatic-traits", - "rand 0.8.3", - "serde", - "wast 38.0.1", -] - -[[package]] -name = "peepmatic-macro" -version = "0.78.0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "peepmatic-runtime" -version = "0.78.0" -dependencies = [ - "bincode", - "log", - "peepmatic-automata", - "peepmatic-test-operator", - "peepmatic-traits", - "serde", - "serde_test", - "thiserror", - "wast 38.0.1", -] - -[[package]] -name = "peepmatic-souper" -version = "0.78.0" -dependencies = [ - "anyhow", - "log", - "peepmatic", - "peepmatic-test-operator", - "souper-ir", - "wast 38.0.1", -] - -[[package]] -name = "peepmatic-test" -version = "0.2.0" -dependencies = [ - "env_logger 0.8.3", - "log", - "peepmatic", - "peepmatic-runtime", - "peepmatic-test-operator", - "peepmatic-traits", -] - -[[package]] -name = "peepmatic-test-operator" -version = "0.78.0" -dependencies = [ - "peepmatic-traits", - "serde", - "wast 38.0.1", -] - -[[package]] -name = "peepmatic-traits" -version = "0.78.0" - [[package]] name = "pem-rfc7468" version = "0.2.2" @@ -2675,15 +2561,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_test" -version = "1.0.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd1055d1c20532080b9da5040ec8e27425f4d4573d8e29eb19ba4ff1e4b9da2d" -dependencies = [ - "serde", -] - [[package]] name = "sha2" version = "0.9.8" @@ -3664,7 +3541,6 @@ dependencies = [ "cranelift-reader", "cranelift-wasm", "libfuzzer-sys", - "peepmatic-fuzzing", "target-lexicon", "wasm-smith", "wasmtime", @@ -3961,26 +3837,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "z3" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa17be9852c6c2a8de2ea0875350dc5f8626f3eaa6b683148753b827f1b39ce5" -dependencies = [ - "lazy_static", - "log", - "z3-sys", -] - -[[package]] -name = "z3-sys" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afa18ba5fbd4933e41ffb440c3fd91f91fe9cdb7310cce3ddfb6648563811de0" -dependencies = [ - "cmake", -] - [[package]] name = "zeroize" version = "1.4.2" diff --git a/ci/run-tests.sh b/ci/run-tests.sh index 7bd661ffe5..f81b155f2d 100755 --- a/ci/run-tests.sh +++ b/ci/run-tests.sh @@ -4,6 +4,5 @@ cargo test \ --features "test-programs/test_programs" \ --workspace \ --exclude 'wasmtime-wasi-*' \ - --exclude 'peepmatic*' \ --exclude wasi-crypto \ $@ diff --git a/cranelift/Cargo.toml b/cranelift/Cargo.toml index 95ac24a3b0..ddc21c53ed 100644 --- a/cranelift/Cargo.toml +++ b/cranelift/Cargo.toml @@ -34,7 +34,6 @@ termcolor = "1.1.2" capstone = { version = "0.9.0", optional = true } wat = { version = "1.0.36", optional = true } target-lexicon = { version = "0.12", features = ["std"] } -peepmatic-souper = { path = "./peepmatic/crates/souper", version = "0.78.0", optional = true } pretty_env_logger = "0.4.0" rayon = { version = "1", optional = true } file-per-thread-logger = "0.1.2" @@ -45,9 +44,8 @@ anyhow = "1.0.32" structopt = "0.3.17" [features] -default = ["disas", "wasm", "cranelift-codegen/all-arch", "peepmatic-souper", "souper-harvest"] +default = ["disas", "wasm", "cranelift-codegen/all-arch", "souper-harvest"] disas = ["capstone"] -enable-peepmatic = ["cranelift-codegen/enable-peepmatic", "cranelift-filetests/enable-peepmatic"] wasm = ["wat", "cranelift-wasm"] experimental_arm32 = ["cranelift-codegen/arm32", "cranelift-filetests/experimental_arm32"] souper-harvest = ["cranelift-codegen/souper-harvest", "rayon"] diff --git a/cranelift/codegen/Cargo.toml b/cranelift/codegen/Cargo.toml index ba7e7488b6..625ca64f09 100644 --- a/cranelift/codegen/Cargo.toml +++ b/cranelift/codegen/Cargo.toml @@ -23,12 +23,8 @@ serde = { version = "1.0.94", features = ["derive"], optional = true } bincode = { version = "1.2.1", optional = true } gimli = { version = "0.25.0", default-features = false, features = ["write"], optional = true } smallvec = { version = "1.6.1" } -peepmatic = { path = "../peepmatic", optional = true, version = "=0.78.0" } -peepmatic-traits = { path = "../peepmatic/crates/traits", optional = true, version = "=0.78.0" } -peepmatic-runtime = { path = "../peepmatic/crates/runtime", optional = true, version = "=0.78.0" } regalloc = { version = "0.0.32" } souper-ir = { version = "2.1.0", optional = true } -wast = { version = "38.0.0", optional = true } # It is a goal of the cranelift-codegen crate to have minimal external dependencies. # Please don't add any unless they are essential to the task of creating binary # machine code. Integration tests that need external dependencies can be @@ -91,13 +87,6 @@ enable-serde = [ # allocation failures, or for regalloc.rs developers. regalloc-snapshot = ["bincode", "regalloc/enable-serde"] -# Recompile our optimizations that are written in the `peepmatic` DSL into a -# compact finite-state transducer automaton. -rebuild-peephole-optimizers = ["peepmatic", "peepmatic-traits", "wast"] - -# Enable the use of `peepmatic`-generated peephole optimizers. -enable-peepmatic = ["peepmatic-runtime", "peepmatic-traits", "serde"] - # Enable support for the Souper harvester. souper-harvest = ["souper-ir", "souper-ir/stringify"] diff --git a/cranelift/codegen/build.rs b/cranelift/codegen/build.rs index 651377fb55..a5cc2eaf91 100644 --- a/cranelift/codegen/build.rs +++ b/cranelift/codegen/build.rs @@ -68,16 +68,6 @@ fn main() { println!("cargo:warning=Generated files are in {}", out_dir); } - #[cfg(feature = "rebuild-peephole-optimizers")] - { - let cur_dir = env::current_dir().expect("Can't access current working directory"); - std::fs::write( - std::path::Path::new(&out_dir).join("CRANELIFT_CODEGEN_PATH"), - cur_dir.to_str().unwrap(), - ) - .unwrap() - } - // The "Meta deterministic check" CI job runs this build script N // times to ensure it produces the same output // consistently. However, it runs the script in a fresh directory, diff --git a/cranelift/codegen/meta/src/gen_inst.rs b/cranelift/codegen/meta/src/gen_inst.rs index 91da420788..54d8647ddf 100644 --- a/cranelift/codegen/meta/src/gen_inst.rs +++ b/cranelift/codegen/meta/src/gen_inst.rs @@ -410,7 +410,7 @@ fn gen_opcodes(all_inst: &AllInstructions, fmt: &mut Formatter) { fmt.line("#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]"); fmt.line( r#"#[cfg_attr( - any(feature = "enable-peepmatic", feature = "enable-serde"), + feature = "enable-serde", derive(serde::Serialize, serde::Deserialize) )]"#, ); diff --git a/cranelift/codegen/src/isa/x64/inst/emit_tests.rs b/cranelift/codegen/src/isa/x64/inst/emit_tests.rs index 2f753ace1a..acb335b80b 100644 --- a/cranelift/codegen/src/isa/x64/inst/emit_tests.rs +++ b/cranelift/codegen/src/isa/x64/inst/emit_tests.rs @@ -7,10 +7,10 @@ //! for this specific case, as of 24 Aug 2020: //! //! cd to the top of your wasmtime tree, then: +//! //! RUST_BACKTRACE=1 cargo test --features test-programs/test_programs \ -//! --features experimental_x64 --all --exclude peepmatic \ -//! --exclude peepmatic-automata --exclude peepmatic-fuzzing \ -//! --exclude peepmatic-macro --exclude wasmtime-wasi-nn -- isa::x64::inst::emit_tests::test_x64_emit +//! --features experimental_x64 --all --exclude wasmtime-wasi-nn \ +//! -- isa::x64::inst::emit_tests::test_x64_emit use super::*; use crate::isa::test_utils; diff --git a/cranelift/codegen/src/lib.rs b/cranelift/codegen/src/lib.rs index bed044cd30..a19d94407c 100644 --- a/cranelift/codegen/src/lib.rs +++ b/cranelift/codegen/src/lib.rs @@ -109,9 +109,6 @@ mod simple_preopt; mod unreachable_code; mod value_label; -#[cfg(feature = "enable-peepmatic")] -mod peepmatic; - #[cfg(feature = "souper-harvest")] mod souper_harvest; diff --git a/cranelift/codegen/src/peepmatic.rs b/cranelift/codegen/src/peepmatic.rs deleted file mode 100644 index 7281596eaf..0000000000 --- a/cranelift/codegen/src/peepmatic.rs +++ /dev/null @@ -1,1317 +0,0 @@ -//! Glue for working with `peepmatic`-generated peephole optimizers. - -use crate::cursor::{Cursor, FuncCursor}; -use crate::ir::{ - condcodes::IntCC, - dfg::DataFlowGraph, - entities::{Inst, Value}, - immediates::{Imm64, Uimm64}, - instructions::{InstructionData, Opcode}, - types, InstBuilder, -}; -use crate::isa::TargetIsa; -use peepmatic_runtime::{ - cc::ConditionCode, - instruction_set::InstructionSet, - part::{Constant, Part}, - r#type::{BitWidth, Kind, Type}, - PeepholeOptimizations, PeepholeOptimizer, -}; -use std::borrow::Cow; -use std::boxed::Box; -use std::convert::{TryFrom, TryInto}; -use std::iter; -use std::ptr; -use std::sync::atomic::{AtomicPtr, Ordering}; - -peepmatic_traits::define_parse_and_typing_rules_for_operator! { - Opcode { - band => Band { - parameters(iNN, iNN); - result(iNN); - } - band_imm => BandImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - bconst => Bconst { - immediates(b1); - result(bNN); - } - bint => Bint { - parameters(bNN); - result(iNN); - } - bor => Bor { - parameters(iNN, iNN); - result(iNN); - } - bor_imm => BorImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - brnz => Brnz { - parameters(bool_or_int); - result(void); - } - brz => Brz { - parameters(bool_or_int); - result(void); - } - bxor => Bxor { - parameters(iNN, iNN); - result(iNN); - } - bxor_imm => BxorImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - iadd => Iadd { - parameters(iNN, iNN); - result(iNN); - } - iadd_imm => IaddImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - icmp => Icmp { - immediates(cc); - parameters(iNN, iNN); - result(b1); - } - icmp_imm => IcmpImm { - immediates(cc, iNN); - parameters(iNN); - result(b1); - } - iconst => Iconst { - immediates(iNN); - result(iNN); - } - ifcmp => Ifcmp { - parameters(iNN, iNN); - result(cpu_flags); - } - ifcmp_imm => IfcmpImm { - immediates(iNN); - parameters(iNN); - result(cpu_flags); - } - imul => Imul { - parameters(iNN, iNN); - result(iNN); - } - imul_imm => ImulImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - ireduce => Ireduce { - parameters(iNN); - result(iMM); - is_reduce(true); - } - irsub_imm => IrsubImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - ishl => Ishl { - parameters(iNN, iNN); - result(iNN); - } - ishl_imm => IshlImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - isub => Isub { - parameters(iNN, iNN); - result(iNN); - } - rotl => Rotl { - parameters(iNN, iNN); - result(iNN); - } - rotl_imm => RotlImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - rotr => Rotr { - parameters(iNN, iNN); - result(iNN); - } - rotr_imm => RotrImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - sdiv => Sdiv { - parameters(iNN, iNN); - result(iNN); - } - sdiv_imm => SdivImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - select => Select { - parameters(bool_or_int, any_t, any_t); - result(any_t); - } - sextend => Sextend { - parameters(iNN); - result(iMM); - is_extend(true); - } - srem => Srem { - parameters(iNN, iNN); - result(iNN); - } - srem_imm => SremImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - sshr => Sshr { - parameters(iNN, iNN); - result(iNN); - } - sshr_imm => SshrImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - trapnz => Trapnz { - parameters(bool_or_int); - result(void); - } - trapz => Trapz { - parameters(bool_or_int); - result(void); - } - udiv => Udiv { - parameters(iNN, iNN); - result(iNN); - } - udiv_imm => UdivImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - uextend => Uextend { - parameters(iNN); - result(iMM); - is_extend(true); - } - urem => Urem { - parameters(iNN, iNN); - result(iNN); - } - urem_imm => UremImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - ushr => Ushr { - parameters(iNN, iNN); - result(iNN); - } - ushr_imm => UshrImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - } - parse_cfg(feature = "rebuild-peephole-optimizers"); -} - -/// Code required to rebuild Peepmatic-based peephole optimizers. -/// -/// This module is used to scope imports and dependencies that are only required -/// for building peephole optimizers (as opposed to just using pre-built -/// peephole optimizers). This helps ensure that our regular builds using -/// pre-built peephole optimizers stay lean. -#[cfg(feature = "rebuild-peephole-optimizers")] -mod rebuild { - use super::*; - use alloc::vec::Vec; - use std::fs; - use std::path::Path; - - /// Rebuild the `preopt.peepmatic` peephole optimizer. - /// - /// Saves and overwrites the old `preopt.serialized` build and returns a - /// copy of the result. - pub fn rebuild_preopt() -> Vec { - let codegen_path = Path::new(include_str!(concat!( - env!("OUT_DIR"), - "/CRANELIFT_CODEGEN_PATH" - ))); - let source_path = codegen_path.join("src").join("preopt.peepmatic"); - - let preopt = peepmatic::compile_file::(&source_path) - .expect("failed to compile `src/preopt.peepmatic`"); - - let serialized_path = codegen_path.join("src").join("preopt.serialized"); - preopt - .serialize_to_file(&serialized_path) - .expect("failed to serialize peephole optimizer to `src/preopt.serialized`"); - fs::read(&serialized_path).expect("failed to read `src/preopt.serialized`") - } -} - -/// Get the `preopt.peepmatic` peephole optimizer. -pub(crate) fn preopt<'a, 'b>( - isa: &'b dyn TargetIsa, -) -> PeepholeOptimizer<'static, 'a, &'b dyn TargetIsa> { - #[cfg(feature = "rebuild-peephole-optimizers")] - fn get_serialized() -> Cow<'static, [u8]> { - rebuild::rebuild_preopt().into() - } - - #[cfg(not(feature = "rebuild-peephole-optimizers"))] - fn get_serialized() -> Cow<'static, [u8]> { - static SERIALIZED: &[u8] = include_bytes!("preopt.serialized"); - SERIALIZED.into() - } - - // Once initialized, this must never be re-assigned. The initialized value - // is semantically "static data" and is intentionally leaked for the whole - // program's lifetime. - static DESERIALIZED: AtomicPtr> = AtomicPtr::new(ptr::null_mut()); - - // If `DESERIALIZED` has already been initialized, then just use it. - let ptr = DESERIALIZED.load(Ordering::SeqCst); - if let Some(peep_opts) = unsafe { ptr.as_ref() } { - return peep_opts.optimizer(isa); - } - - // Otherwise, if `DESERIALIZED` hasn't been initialized, then we need to - // deserialize the peephole optimizations and initialize it. However, - // another thread could be doing the same thing concurrently, so there is a - // race to see who initializes `DESERIALIZED` first, and we need to be - // prepared to both win or lose that race. - let peep_opts = PeepholeOptimizations::deserialize(&get_serialized()) - .expect("should always be able to deserialize `preopt.serialized`"); - let peep_opts = Box::into_raw(Box::new(peep_opts)); - - // Only update `DESERIALIZE` if it is still null, attempting to perform the - // one-time transition from null -> non-null. - if DESERIALIZED - .compare_and_swap(ptr::null_mut(), peep_opts, Ordering::SeqCst) - .is_null() - { - // We won the race to initialize `DESERIALIZED`. - debug_assert_eq!(DESERIALIZED.load(Ordering::SeqCst), peep_opts); - let peep_opts = unsafe { &*peep_opts }; - return peep_opts.optimizer(isa); - } - - // We lost the race to initialize `DESERIALIZED`. Drop our no-longer-needed - // instance of `peep_opts` and get the pointer to the instance that won the - // race. - let _ = unsafe { Box::from_raw(peep_opts) }; - let peep_opts = DESERIALIZED.load(Ordering::SeqCst); - let peep_opts = unsafe { peep_opts.as_ref().unwrap() }; - peep_opts.optimizer(isa) -} - -/// Either a `Value` or an `Inst`. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum ValueOrInst { - Value(Value), - Inst(Inst), -} - -impl ValueOrInst { - /// Get the underlying `Value` if any. - pub fn value(&self) -> Option { - match *self { - Self::Value(v) => Some(v), - Self::Inst(_) => None, - } - } - - /// Get the underlying `Inst` if any. - pub fn inst(&self) -> Option { - match *self { - Self::Inst(i) => Some(i), - Self::Value(_) => None, - } - } - - /// Unwrap the underlying `Value`, panicking if it is not a `Value. - pub fn unwrap_value(&self) -> Value { - self.value().unwrap() - } - - /// Unwrap the underlying `Inst`, panicking if it is not a `Inst. - pub fn unwrap_inst(&self) -> Inst { - self.inst().unwrap() - } - - /// Is this a `Value`? - pub fn is_value(&self) -> bool { - self.value().is_some() - } - - /// Is this an `Inst`? - pub fn is_inst(&self) -> bool { - self.inst().is_some() - } - - fn resolve_inst(&self, dfg: &DataFlowGraph) -> Option { - match *self { - ValueOrInst::Inst(i) => Some(i), - ValueOrInst::Value(v) => dfg.value_def(v).inst(), - } - } - - fn result_bit_width(&self, dfg: &DataFlowGraph) -> u8 { - match *self { - ValueOrInst::Value(v) => dfg.value_type(v).bits().try_into().unwrap(), - ValueOrInst::Inst(inst) => { - let result = dfg.first_result(inst); - dfg.value_type(result).bits().try_into().unwrap() - } - } - } - - fn to_constant(&self, pos: &mut FuncCursor) -> Option { - let inst = self.resolve_inst(&pos.func.dfg)?; - match pos.func.dfg[inst] { - InstructionData::UnaryImm { - opcode: Opcode::Iconst, - imm, - } => { - let width = self.result_bit_width(&pos.func.dfg).try_into().unwrap(); - let x: i64 = imm.into(); - Some(Constant::Int(x as u64, width)) - } - InstructionData::UnaryBool { - opcode: Opcode::Bconst, - imm, - } => { - let width = self.result_bit_width(&pos.func.dfg).try_into().unwrap(); - Some(Constant::Bool(imm, width)) - } - _ => None, - } - } -} - -impl From for ValueOrInst { - fn from(v: Value) -> ValueOrInst { - ValueOrInst::Value(v) - } -} - -impl From for ValueOrInst { - fn from(i: Inst) -> ValueOrInst { - ValueOrInst::Inst(i) - } -} - -/// Get the fixed bit width of `bit_width`, or if it is polymorphic, the bit -/// width of `root`. -fn bit_width(dfg: &DataFlowGraph, bit_width: BitWidth, root: Inst) -> u8 { - bit_width.fixed_width().unwrap_or_else(|| { - let tyvar = dfg.ctrl_typevar(root); - let ty = dfg.compute_result_type(root, 0, tyvar).unwrap(); - u8::try_from(ty.bits()).unwrap() - }) -} - -/// Convert the constant `c` into an instruction. -fn const_to_value<'a>(builder: impl InstBuilder<'a>, c: Constant, root: Inst) -> Value { - match c { - Constant::Bool(b, width) => { - let width = bit_width(builder.data_flow_graph(), width, root); - let ty = match width { - 1 => types::B1, - 8 => types::B8, - 16 => types::B16, - 32 => types::B32, - 64 => types::B64, - 128 => types::B128, - _ => unreachable!(), - }; - builder.bconst(ty, b) - } - Constant::Int(x, width) => { - let width = bit_width(builder.data_flow_graph(), width, root); - let ty = match width { - 8 => types::I8, - 16 => types::I16, - 32 => types::I32, - 64 => types::I64, - 128 => types::I128, - _ => unreachable!(), - }; - builder.iconst(ty, x as i64) - } - } -} - -fn part_to_value(pos: &mut FuncCursor, root: Inst, part: Part) -> Option { - match part { - Part::Instruction(ValueOrInst::Inst(inst)) => { - pos.func.dfg.inst_results(inst).first().copied() - } - Part::Instruction(ValueOrInst::Value(v)) => Some(v), - Part::Constant(c) => Some(const_to_value(pos.ins(), c, root)), - Part::ConditionCode(_) => None, - } -} - -impl TryFrom for Imm64 { - type Error = &'static str; - - fn try_from(c: Constant) -> Result { - match c { - Constant::Int(x, _) => Ok(Imm64::from(x as i64)), - Constant::Bool(..) => Err("cannot create Imm64 from Constant::Bool"), - } - } -} - -impl Into for Imm64 { - #[inline] - fn into(self) -> Constant { - let x: i64 = self.into(); - Constant::Int(x as _, BitWidth::SixtyFour) - } -} - -impl Into> for Imm64 { - #[inline] - fn into(self) -> Part { - let c: Constant = self.into(); - c.into() - } -} - -fn part_to_imm64(pos: &mut FuncCursor, part: Part) -> Imm64 { - return match part { - Part::Instruction(x) => match x.to_constant(pos).unwrap_or_else(|| cannot_convert()) { - Constant::Int(x, _) => (x as i64).into(), - Constant::Bool(..) => cannot_convert(), - }, - Part::Constant(Constant::Int(x, _)) => (x as i64).into(), - Part::ConditionCode(_) | Part::Constant(Constant::Bool(..)) => cannot_convert(), - }; - - #[inline(never)] - #[cold] - fn cannot_convert() -> ! { - panic!("cannot convert part into `Imm64`") - } -} - -impl Into for Uimm64 { - #[inline] - fn into(self) -> Constant { - let x: u64 = self.into(); - Constant::Int(x, BitWidth::SixtyFour) - } -} - -impl Into> for Uimm64 { - #[inline] - fn into(self) -> Part { - let c: Constant = self.into(); - c.into() - } -} - -fn peepmatic_to_intcc(cc: ConditionCode) -> IntCC { - match cc { - ConditionCode::Eq => IntCC::Equal, - ConditionCode::Ne => IntCC::NotEqual, - ConditionCode::Slt => IntCC::SignedLessThan, - ConditionCode::Sle => IntCC::SignedGreaterThanOrEqual, - ConditionCode::Sgt => IntCC::SignedGreaterThan, - ConditionCode::Sge => IntCC::SignedLessThanOrEqual, - ConditionCode::Ult => IntCC::UnsignedLessThan, - ConditionCode::Uge => IntCC::UnsignedGreaterThanOrEqual, - ConditionCode::Ugt => IntCC::UnsignedGreaterThan, - ConditionCode::Ule => IntCC::UnsignedLessThanOrEqual, - ConditionCode::Of => IntCC::Overflow, - ConditionCode::Nof => IntCC::NotOverflow, - } -} - -fn intcc_to_peepmatic(cc: IntCC) -> ConditionCode { - match cc { - IntCC::Equal => ConditionCode::Eq, - IntCC::NotEqual => ConditionCode::Ne, - IntCC::SignedLessThan => ConditionCode::Slt, - IntCC::SignedGreaterThanOrEqual => ConditionCode::Sle, - IntCC::SignedGreaterThan => ConditionCode::Sgt, - IntCC::SignedLessThanOrEqual => ConditionCode::Sge, - IntCC::UnsignedLessThan => ConditionCode::Ult, - IntCC::UnsignedGreaterThanOrEqual => ConditionCode::Uge, - IntCC::UnsignedGreaterThan => ConditionCode::Ugt, - IntCC::UnsignedLessThanOrEqual => ConditionCode::Ule, - IntCC::Overflow => ConditionCode::Of, - IntCC::NotOverflow => ConditionCode::Nof, - } -} - -fn peepmatic_ty_to_ir_ty(ty: Type, dfg: &DataFlowGraph, root: Inst) -> types::Type { - match (ty.kind, bit_width(dfg, ty.bit_width, root)) { - (Kind::Int, 8) => types::I8, - (Kind::Int, 16) => types::I16, - (Kind::Int, 32) => types::I32, - (Kind::Int, 64) => types::I64, - (Kind::Int, 128) => types::I128, - (Kind::Bool, 1) => types::B1, - (Kind::Bool, 8) => types::I8, - (Kind::Bool, 16) => types::I16, - (Kind::Bool, 32) => types::I32, - (Kind::Bool, 64) => types::I64, - (Kind::Bool, 128) => types::I128, - _ => unreachable!(), - } -} - -// NB: the unsafe contract we must uphold here is that our implementation of -// `instruction_result_bit_width` must always return a valid, non-zero bit -// width. -unsafe impl<'a, 'b> InstructionSet<'b> for &'a dyn TargetIsa { - type Context = FuncCursor<'b>; - - type Operator = Opcode; - - type Instruction = ValueOrInst; - - fn replace_instruction( - &self, - pos: &mut FuncCursor<'b>, - old: ValueOrInst, - new: Part, - ) -> ValueOrInst { - log::trace!("replace {:?} with {:?}", old, new); - let old_inst = old.resolve_inst(&pos.func.dfg).unwrap(); - - // Try to convert `new` to an instruction, because we prefer replacing - // an old instruction with a new one wholesale. However, if the - // replacement cannot be converted to an instruction (e.g. the - // right-hand side is a block/function parameter value) then we change - // the old instruction's result to an alias of the new value. - let new_inst = match new { - Part::Instruction(ValueOrInst::Inst(inst)) => Some(inst), - Part::Instruction(ValueOrInst::Value(_)) => { - // Do not try and follow the value definition. If we transplant - // this value's instruction, and there are other uses of this - // value, then we could mess up ordering between instructions. - None - } - Part::Constant(c) => { - let v = const_to_value(pos.ins(), c, old_inst); - let inst = pos.func.dfg.value_def(v).unwrap_inst(); - Some(inst) - } - Part::ConditionCode(_) => None, - }; - - match new_inst { - Some(new_inst) => { - pos.func.transplant_inst(old_inst, new_inst); - debug_assert_eq!(pos.current_inst(), Some(old_inst)); - old_inst.into() - } - None => { - let new_value = part_to_value(pos, old_inst, new).unwrap(); - - let old_results = pos.func.dfg.detach_results(old_inst); - let old_results = old_results.as_slice(&pos.func.dfg.value_lists); - assert_eq!(old_results.len(), 1); - let old_value = old_results[0]; - - pos.func.dfg.change_to_alias(old_value, new_value); - pos.func.dfg.replace(old_inst).nop(); - - new_value.into() - } - } - } - - fn operator( - &self, - pos: &mut FuncCursor<'b>, - value_or_inst: ValueOrInst, - operands: &mut E, - ) -> Option - where - E: Extend>, - { - let inst = value_or_inst.resolve_inst(&pos.func.dfg)?; - Some(match pos.func.dfg[inst] { - InstructionData::Binary { - opcode: opcode @ Opcode::Band, - args, - } - | InstructionData::Binary { - opcode: opcode @ Opcode::Bor, - args, - } - | InstructionData::Binary { - opcode: opcode @ Opcode::Bxor, - args, - } - | InstructionData::Binary { - opcode: opcode @ Opcode::Iadd, - args, - } - | InstructionData::Binary { - opcode: opcode @ Opcode::Ifcmp, - args, - } - | InstructionData::Binary { - opcode: opcode @ Opcode::Imul, - args, - } - | InstructionData::Binary { - opcode: opcode @ Opcode::Ishl, - args, - } - | InstructionData::Binary { - opcode: opcode @ Opcode::Isub, - args, - } - | InstructionData::Binary { - opcode: opcode @ Opcode::Rotl, - args, - } - | InstructionData::Binary { - opcode: opcode @ Opcode::Rotr, - args, - } - | InstructionData::Binary { - opcode: opcode @ Opcode::Sdiv, - args, - } - | InstructionData::Binary { - opcode: opcode @ Opcode::Srem, - args, - } - | InstructionData::Binary { - opcode: opcode @ Opcode::Sshr, - args, - } - | InstructionData::Binary { - opcode: opcode @ Opcode::Udiv, - args, - } - | InstructionData::Binary { - opcode: opcode @ Opcode::Urem, - args, - } - | InstructionData::Binary { - opcode: opcode @ Opcode::Ushr, - args, - } => { - operands.extend(args.iter().map(|v| Part::Instruction((*v).into()))); - opcode - } - - InstructionData::BinaryImm64 { - opcode: opcode @ Opcode::BandImm, - imm, - arg, - } - | InstructionData::BinaryImm64 { - opcode: opcode @ Opcode::BorImm, - imm, - arg, - } - | InstructionData::BinaryImm64 { - opcode: opcode @ Opcode::BxorImm, - imm, - arg, - } - | InstructionData::BinaryImm64 { - opcode: opcode @ Opcode::IaddImm, - imm, - arg, - } - | InstructionData::BinaryImm64 { - opcode: opcode @ Opcode::IfcmpImm, - imm, - arg, - } - | InstructionData::BinaryImm64 { - opcode: opcode @ Opcode::ImulImm, - imm, - arg, - } - | InstructionData::BinaryImm64 { - opcode: opcode @ Opcode::IrsubImm, - imm, - arg, - } - | InstructionData::BinaryImm64 { - opcode: opcode @ Opcode::IshlImm, - imm, - arg, - } - | InstructionData::BinaryImm64 { - opcode: opcode @ Opcode::RotlImm, - imm, - arg, - } - | InstructionData::BinaryImm64 { - opcode: opcode @ Opcode::RotrImm, - imm, - arg, - } - | InstructionData::BinaryImm64 { - opcode: opcode @ Opcode::SdivImm, - imm, - arg, - } - | InstructionData::BinaryImm64 { - opcode: opcode @ Opcode::SremImm, - imm, - arg, - } - | InstructionData::BinaryImm64 { - opcode: opcode @ Opcode::SshrImm, - imm, - arg, - } - | InstructionData::BinaryImm64 { - opcode: opcode @ Opcode::UdivImm, - imm, - arg, - } - | InstructionData::BinaryImm64 { - opcode: opcode @ Opcode::UremImm, - imm, - arg, - } - | InstructionData::BinaryImm64 { - opcode: opcode @ Opcode::UshrImm, - imm, - arg, - } => { - operands.extend( - iter::once(imm.into()).chain(iter::once(Part::Instruction(arg.into()))), - ); - opcode - } - - InstructionData::Branch { - opcode: opcode @ Opcode::Brnz, - ref args, - destination: _, - } - | InstructionData::Branch { - opcode: opcode @ Opcode::Brz, - ref args, - destination: _, - } => { - operands.extend( - args.as_slice(&pos.func.dfg.value_lists) - .iter() - .map(|v| Part::Instruction((*v).into())) - // NB: Peepmatic only knows about the condition, not any - // of the arguments to the block, which are special - // cased elsewhere, if/when we actually replace the - // instruction. - .take(1), - ); - opcode - } - - InstructionData::CondTrap { - opcode: opcode @ Opcode::Trapnz, - arg, - code: _, - } - | InstructionData::CondTrap { - opcode: opcode @ Opcode::Trapz, - arg, - code: _, - } => { - operands.extend(iter::once(Part::Instruction(arg.into()))); - opcode - } - - InstructionData::IntCompare { - opcode: opcode @ Opcode::Icmp, - cond, - args, - } => { - operands.extend( - iter::once(intcc_to_peepmatic(cond).into()) - .chain(args.iter().map(|v| Part::Instruction((*v).into()))), - ); - opcode - } - - InstructionData::IntCompareImm { - opcode: opcode @ Opcode::IcmpImm, - cond, - imm, - arg, - } => { - operands.extend( - iter::once(intcc_to_peepmatic(cond).into()) - .chain(iter::once(Part::Constant(imm.into()))) - .chain(iter::once(Part::Instruction(arg.into()))), - ); - opcode - } - - InstructionData::Ternary { - opcode: opcode @ Opcode::Select, - ref args, - } => { - operands.extend(args.iter().map(|v| Part::Instruction((*v).into()))); - opcode - } - - InstructionData::Unary { - opcode: opcode @ Opcode::Bint, - arg, - } - | InstructionData::Unary { - opcode: opcode @ Opcode::Ireduce, - arg, - } - | InstructionData::Unary { - opcode: opcode @ Opcode::Sextend, - arg, - } - | InstructionData::Unary { - opcode: opcode @ Opcode::Uextend, - arg, - } => { - operands.extend(iter::once(Part::Instruction(arg.into()))); - opcode - } - - InstructionData::UnaryBool { opcode, imm } => { - operands.extend(iter::once(Part::Constant(Constant::Bool( - imm, - BitWidth::Polymorphic, - )))); - opcode - } - - InstructionData::UnaryImm { - opcode: opcode @ Opcode::Iconst, - imm, - } => { - operands.extend(iter::once(imm.into())); - opcode - } - ref otherwise => { - log::trace!("Not supported by Peepmatic: {:?}", otherwise); - return None; - } - }) - } - - fn make_inst_1( - &self, - pos: &mut FuncCursor<'b>, - root: ValueOrInst, - operator: Opcode, - r#type: Type, - a: Part, - ) -> ValueOrInst { - log::trace!("make_inst_1: {:?}({:?})", operator, a); - - let root = root.resolve_inst(&pos.func.dfg).unwrap(); - match operator { - Opcode::Bconst => { - let c = a.unwrap_constant(); - let val = const_to_value(pos.ins(), c, root); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::Bint => { - let a = part_to_value(pos, root, a).unwrap(); - let ty = peepmatic_ty_to_ir_ty(r#type, &pos.func.dfg, root); - let val = pos.ins().bint(ty, a); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::Bnot => { - let a = part_to_value(pos, root, a).unwrap(); - let val = pos.ins().bnot(a); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::Brnz => { - let a = part_to_value(pos, root, a).unwrap(); - - // NB: branching instructions must be the root of an - // optimization's right-hand side, so we get the destination - // block and arguments from the left-hand side's root. Peepmatic - // doesn't currently represent labels or varargs. - let block = pos.func.dfg[root].branch_destination().unwrap(); - let args = pos.func.dfg.inst_args(root)[1..].to_vec(); - - pos.ins().brnz(a, block, &args).into() - } - Opcode::Brz => { - let a = part_to_value(pos, root, a).unwrap(); - - // See the comment in the `Opcode::Brnz` match argm. - let block = pos.func.dfg[root].branch_destination().unwrap(); - let args = pos.func.dfg.inst_args(root)[1..].to_vec(); - - pos.ins().brz(a, block, &args).into() - } - Opcode::Iconst => { - let a = a.unwrap_constant(); - let val = const_to_value(pos.ins(), a, root); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::Ireduce => { - let a = part_to_value(pos, root, a).unwrap(); - let ty = peepmatic_ty_to_ir_ty(r#type, &pos.func.dfg, root); - let val = pos.ins().ireduce(ty, a); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::Sextend => { - let a = part_to_value(pos, root, a).unwrap(); - let ty = peepmatic_ty_to_ir_ty(r#type, &pos.func.dfg, root); - let val = pos.ins().sextend(ty, a); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::Trapnz => { - let a = part_to_value(pos, root, a).unwrap(); - - // NB: similar to branching instructions (see comment in the - // `Opcode::Brnz` match arm) trapping instructions must be the - // root of an optimization's right-hand side, and we get the - // trap code from the root of the left-hand side. Peepmatic - // doesn't currently represent trap codes. - let code = pos.func.dfg[root].trap_code().unwrap(); - - pos.ins().trapnz(a, code).into() - } - Opcode::Trapz => { - let a = part_to_value(pos, root, a).unwrap(); - // See comment in the `Opcode::Trapnz` match arm. - let code = pos.func.dfg[root].trap_code().unwrap(); - pos.ins().trapz(a, code).into() - } - Opcode::Uextend => { - let a = part_to_value(pos, root, a).unwrap(); - let ty = peepmatic_ty_to_ir_ty(r#type, &pos.func.dfg, root); - let val = pos.ins().uextend(ty, a); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - _ => unreachable!(), - } - } - - fn make_inst_2( - &self, - pos: &mut FuncCursor<'b>, - root: ValueOrInst, - operator: Opcode, - _: Type, - a: Part, - b: Part, - ) -> ValueOrInst { - log::trace!("make_inst_2: {:?}({:?}, {:?})", operator, a, b); - - let root = root.resolve_inst(&pos.func.dfg).unwrap(); - match operator { - Opcode::Band => { - let a = part_to_value(pos, root, a).unwrap(); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().band(a, b); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::BandImm => { - let a = part_to_imm64(pos, a); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().band_imm(b, a); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::Bor => { - let a = part_to_value(pos, root, a).unwrap(); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().bor(a, b); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::BorImm => { - let a = part_to_imm64(pos, a); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().bor_imm(b, a); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::Bxor => { - let a = part_to_value(pos, root, a).unwrap(); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().bxor(a, b); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::BxorImm => { - let a = part_to_imm64(pos, a); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().bxor_imm(b, a); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::Iadd => { - let a = part_to_value(pos, root, a).unwrap(); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().iadd(a, b); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::IaddImm => { - let a = part_to_imm64(pos, a); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().iadd_imm(b, a); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::Ifcmp => { - let a = part_to_value(pos, root, a).unwrap(); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().ifcmp(a, b); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::IfcmpImm => { - let a = part_to_imm64(pos, a); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().ifcmp_imm(b, a); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::Imul => { - let a = part_to_value(pos, root, a).unwrap(); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().imul(a, b); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::ImulImm => { - let a = part_to_imm64(pos, a); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().imul_imm(b, a); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::IrsubImm => { - let a = part_to_imm64(pos, a); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().irsub_imm(b, a); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::Ishl => { - let a = part_to_value(pos, root, a).unwrap(); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().ishl(a, b); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::IshlImm => { - let a = part_to_imm64(pos, a); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().ishl_imm(b, a); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::Isub => { - let a = part_to_value(pos, root, a).unwrap(); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().isub(a, b); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::Rotl => { - let a = part_to_value(pos, root, a).unwrap(); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().rotl(a, b); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::RotlImm => { - let a = part_to_imm64(pos, a); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().rotl_imm(b, a); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::Rotr => { - let a = part_to_value(pos, root, a).unwrap(); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().rotr(a, b); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::RotrImm => { - let a = part_to_imm64(pos, a); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().rotr_imm(b, a); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::Sdiv => { - let a = part_to_value(pos, root, a).unwrap(); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().sdiv(a, b); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::SdivImm => { - let a = part_to_imm64(pos, a); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().sdiv_imm(b, a); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::Srem => { - let a = part_to_value(pos, root, a).unwrap(); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().srem(a, b); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::SremImm => { - let a = part_to_imm64(pos, a); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().srem_imm(b, a); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::Sshr => { - let a = part_to_value(pos, root, a).unwrap(); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().sshr(a, b); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::SshrImm => { - let a = part_to_imm64(pos, a); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().sshr_imm(b, a); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::Udiv => { - let a = part_to_value(pos, root, a).unwrap(); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().udiv(a, b); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::UdivImm => { - let a = part_to_imm64(pos, a); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().udiv_imm(b, a); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::Urem => { - let a = part_to_value(pos, root, a).unwrap(); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().urem(a, b); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::UremImm => { - let a = part_to_imm64(pos, a); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().urem_imm(b, a); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::Ushr => { - let a = part_to_value(pos, root, a).unwrap(); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().ushr(a, b); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::UshrImm => { - let a = part_to_imm64(pos, a); - let b = part_to_value(pos, root, b).unwrap(); - let val = pos.ins().ushr_imm(b, a); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - _ => unreachable!(), - } - } - - fn make_inst_3( - &self, - pos: &mut FuncCursor<'b>, - root: ValueOrInst, - operator: Opcode, - _: Type, - a: Part, - b: Part, - c: Part, - ) -> ValueOrInst { - log::trace!("make_inst_3: {:?}({:?}, {:?}, {:?})", operator, a, b, c); - - let root = root.resolve_inst(&pos.func.dfg).unwrap(); - match operator { - Opcode::Icmp => { - let cond = a.unwrap_condition_code(); - let cond = peepmatic_to_intcc(cond); - let b = part_to_value(pos, root, b).unwrap(); - let c = part_to_value(pos, root, c).unwrap(); - let val = pos.ins().icmp(cond, b, c); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::IcmpImm => { - let cond = a.unwrap_condition_code(); - let cond = peepmatic_to_intcc(cond); - let imm = part_to_imm64(pos, b); - let c = part_to_value(pos, root, c).unwrap(); - let val = pos.ins().icmp_imm(cond, c, imm); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - Opcode::Select => { - let a = part_to_value(pos, root, a).unwrap(); - let b = part_to_value(pos, root, b).unwrap(); - let c = part_to_value(pos, root, c).unwrap(); - let val = pos.ins().select(a, b, c); - pos.func.dfg.value_def(val).unwrap_inst().into() - } - _ => unreachable!(), - } - } - - fn instruction_to_constant( - &self, - pos: &mut FuncCursor<'b>, - value_or_inst: ValueOrInst, - ) -> Option { - value_or_inst.to_constant(pos) - } - - fn instruction_result_bit_width( - &self, - pos: &mut FuncCursor<'b>, - value_or_inst: ValueOrInst, - ) -> u8 { - value_or_inst.result_bit_width(&pos.func.dfg) - } - - fn native_word_size_in_bits(&self, _pos: &mut FuncCursor<'b>) -> u8 { - self.pointer_bits() - } -} - -#[cfg(test)] -#[cfg(any(feature = "x64", feature = "x86", feature = "arm64"))] -mod tests { - use super::*; - use crate::isa::{lookup, TargetIsa}; - use crate::settings::{builder, Flags}; - use std::str::FromStr; - use target_lexicon::triple; - - fn isa() -> Box { - // We need a triple to instantiate and run the peephole optimizer, but we - // don't care which one when we're just trying to trigger a rebuild of the - // peephole optimizer (it doesn't affect the serialized bytes at all). - let triple = if cfg!(any(feature = "x64", feature = "x86")) { - triple!("x86_64") - } else if cfg!(feature = "arm64") { - triple!("aarch64") - } else { - panic!("unknown arch") - }; - lookup(triple).unwrap().finish(Flags::new(builder())) - } - - #[test] - fn get_peepmatic_preopt() { - let isa = isa(); - let _ = preopt(&*isa); - } -} diff --git a/cranelift/codegen/src/simple_preopt.rs b/cranelift/codegen/src/simple_preopt.rs index 0beda58c1a..276ff13c65 100644 --- a/cranelift/codegen/src/simple_preopt.rs +++ b/cranelift/codegen/src/simple_preopt.rs @@ -608,48 +608,6 @@ fn branch_order(pos: &mut FuncCursor, cfg: &mut ControlFlowGraph, block: Block, cfg.recompute_block(pos.func, block); } -#[cfg(feature = "enable-peepmatic")] -mod simplify { - use super::*; - use crate::peepmatic::ValueOrInst; - - pub type PeepholeOptimizer<'a, 'b> = - peepmatic_runtime::optimizer::PeepholeOptimizer<'static, 'a, &'b dyn TargetIsa>; - - pub fn peephole_optimizer<'a, 'b>(isa: &'b dyn TargetIsa) -> PeepholeOptimizer<'a, 'b> { - crate::peepmatic::preopt(isa) - } - - pub fn apply_all<'a, 'b>( - optimizer: &mut PeepholeOptimizer<'a, 'b>, - pos: &mut FuncCursor<'a>, - inst: Inst, - _native_word_width: u32, - ) { - // After we apply one optimization, that might make another - // optimization applicable. Keep running the peephole optimizer - // until either: - // - // * No optimization applied, and therefore it doesn't make sense to - // try again, because no optimization will apply again. - // - // * Or when we replaced an instruction with an alias to an existing - // value, because we already ran the peephole optimizer over the - // aliased value's instruction in an early part of the traversal - // over the function. - while let Some(ValueOrInst::Inst(new_inst)) = - optimizer.apply_one(pos, ValueOrInst::Inst(inst)) - { - // We transplanted a new instruction into the current - // instruction, so the "new" instruction is actually the same - // one, just with different data. - debug_assert_eq!(new_inst, inst); - } - debug_assert_eq!(pos.current_inst(), Some(inst)); - } -} - -#[cfg(not(feature = "enable-peepmatic"))] mod simplify { use super::*; use crate::ir::{ diff --git a/cranelift/filetests/Cargo.toml b/cranelift/filetests/Cargo.toml index a19618b8d8..cf657fe448 100644 --- a/cranelift/filetests/Cargo.toml +++ b/cranelift/filetests/Cargo.toml @@ -27,5 +27,4 @@ thiserror = "1.0.15" anyhow = "1.0.32" [features] -enable-peepmatic = [] experimental_arm32 = [] diff --git a/cranelift/filetests/filetests/peepmatic/branch.clif b/cranelift/filetests/filetests/peepmatic/branch.clif deleted file mode 100644 index 3e12cbcc6d..0000000000 --- a/cranelift/filetests/filetests/peepmatic/branch.clif +++ /dev/null @@ -1,82 +0,0 @@ -test peepmatic -target aarch64 -target x86_64 - -function %icmp_to_brz_fold(i32) -> i32 { -block0(v0: i32): - v1 = icmp_imm eq v0, 0 - brnz v1, block1 - jump block2 -block1: - v3 = iconst.i32 1 - return v3 -block2: - v4 = iconst.i32 2 - return v4 -} -; sameln: function %icmp_to_brz_fold -; nextln: block0(v0: i32): -; nextln: v1 = icmp_imm eq v0, 0 -; nextln: brnz v0, block2 -; nextln: jump block1 -; nextln: -; nextln: block1: -; nextln: v3 = iconst.i32 1 -; nextln: return v3 -; nextln: -; nextln: block2: -; nextln: v4 = iconst.i32 2 -; nextln: return v4 -; nextln: } - -function %icmp_to_brz_inverted_fold(i32) -> i32 { -block0(v0: i32): - v1 = icmp_imm ne v0, 0 - brz v1, block1 - jump block2 -block1: - v3 = iconst.i32 1 - return v3 -block2: - v4 = iconst.i32 2 - return v4 -} -; sameln: function %icmp_to_brz_inve -; nextln: block0(v0: i32): -; nextln: v1 = icmp_imm ne v0, 0 -; nextln: brnz v0, block2 -; nextln: jump block1 -; nextln: -; nextln: block1: -; nextln: v3 = iconst.i32 1 -; nextln: return v3 -; nextln: -; nextln: block2: -; nextln: v4 = iconst.i32 2 -; nextln: return v4 -; nextln: } - -function %br_icmp_inversion(i32, i32) -> i32 { -block0(v0: i32, v1: i32): - br_icmp ugt v0, v1, block1 - jump block2 -block1: - v2 = iconst.i32 1 - return v2 -block2: - v3 = iconst.i32 2 - return v3 -} -; sameln: function %br_icmp_inversio -; nextln: block0(v0: i32, v1: i32): -; nextln: br_icmp ule v0, v1, block2 -; nextln: jump block1 -; nextln: -; nextln: block1: -; nextln: v2 = iconst.i32 1 -; nextln: return v2 -; nextln: -; nextln: block2: -; nextln: v3 = iconst.i32 2 -; nextln: return v3 -; nextln: } diff --git a/cranelift/filetests/filetests/peepmatic/div_by_const_indirect.clif b/cranelift/filetests/filetests/peepmatic/div_by_const_indirect.clif deleted file mode 100644 index 12c5f29d2a..0000000000 --- a/cranelift/filetests/filetests/peepmatic/div_by_const_indirect.clif +++ /dev/null @@ -1,56 +0,0 @@ -test peepmatic -target aarch64 -target x86_64 baseline - -; Cases where the denominator is created by an iconst - -function %indir_udiv32(i32) -> i32 { -block0(v0: i32): - v1 = iconst.i32 7 - v2 = udiv v0, v1 - ; check: v4 = iconst.i32 0x2492_4925 - ; nextln: v5 = umulhi v0, v4 - ; nextln: v6 = isub v0, v5 - ; nextln: v7 = ushr_imm v6, 1 - ; nextln: v8 = iadd v7, v5 - ; nextln: v9 = ushr_imm v8, 2 - ; nextln: v2 -> v9 - return v2 -} - -function %indir_sdiv32(i32) -> i32 { -block0(v0: i32): - v1 = iconst.i32 -17 - v2 = sdiv v0, v1 - ; check: v4 = iconst.i32 0xffff_ffff_8787_8787 - ; nextln: v5 = smulhi v0, v4 - ; nextln: v6 = sshr_imm v5, 3 - ; nextln: v7 = ushr_imm v6, 31 - ; nextln: v8 = iadd v6, v7 - ; nextln: v2 -> v8 - return v2 -} - -function %indir_udiv64(i64) -> i64 { -block0(v0: i64): - v1 = iconst.i64 1337 - v2 = udiv v0, v1 - ; check: v4 = iconst.i64 0xc411_9d95_2866_a139 - ; nextln: v5 = umulhi v0, v4 - ; nextln: v6 = ushr_imm v5, 10 - ; nextln: v2 -> v6 - return v2 -} - -function %indir_sdiv64(i64) -> i64 { -block0(v0: i64): - v1 = iconst.i64 -90210 - v2 = sdiv v0, v1 - ; check: v4 = iconst.i64 0xd181_4ee8_939c_b8bb - ; nextln: v5 = smulhi v0, v4 - ; nextln: v6 = sshr_imm v5, 14 - ; nextln: v7 = ushr_imm v6, 63 - ; nextln: v8 = iadd v6, v7 - ; nextln: v2 -> v8 - return v2 -} diff --git a/cranelift/filetests/filetests/peepmatic/div_by_const_non_power_of_2.clif b/cranelift/filetests/filetests/peepmatic/div_by_const_non_power_of_2.clif deleted file mode 100644 index 3731f7fdc7..0000000000 --- a/cranelift/filetests/filetests/peepmatic/div_by_const_non_power_of_2.clif +++ /dev/null @@ -1,267 +0,0 @@ -test peepmatic -target aarch64 -target i686 baseline - -; -------- U32 -------- - -; complex case (mul, sub, shift, add, shift) -function %t_udiv32_p7(i32) -> i32 { -block0(v0: i32): - v1 = udiv_imm v0, 7 - ; check: iconst.i32 0x2492_4925 - ; check: umulhi v0, v2 - ; check: isub v0, v3 - ; check: ushr_imm v4, 1 - ; check: iadd v5, v3 - ; check: v7 = ushr_imm v6, 2 - ; check: v1 -> v7 - return v1 -} - -; simple case (mul, shift) -function %t_udiv32_p125(i32) -> i32 { -block0(v0: i32): - v1 = udiv_imm v0, 125 - ; check: iconst.i32 0x1062_4dd3 - ; check: umulhi v0, v2 - ; check: v4 = ushr_imm v3, 3 - ; check: v1 -> v4 - return v1 -} - -; simple case w/ shift by zero (mul) -function %t_udiv32_p641(i32) -> i32 { -block0(v0: i32): - v1 = udiv_imm v0, 641 - ; check: iconst.i32 0x0066_3d81 - ; check: v3 = umulhi v0, v2 - ; check: v1 -> v3 - return v1 -} - - -; -------- S32 -------- - -; simple case w/ shift by zero (mul, add-sign-bit) -function %t_sdiv32_n6(i32) -> i32 { -block0(v0: i32): - v1 = sdiv_imm v0, -6 - ; check: iconst.i32 0xffff_ffff_d555_5555 - ; check: smulhi v0, v2 - ; check: ushr_imm v3, 31 - ; check: v5 = iadd v3, v4 - ; check: v1 -> v5 - return v1 -} - -; simple case (mul, shift, add-sign-bit) -function %t_sdiv32_n5(i32) -> i32 { -block0(v0: i32): - v1 = sdiv_imm v0, -5 - ; check: iconst.i32 0xffff_ffff_9999_9999 - ; check: smulhi v0, v2 - ; check: sshr_imm v3, 1 - ; check: ushr_imm v4, 31 - ; check: v6 = iadd v4, v5 - ; check: v1 -> v6 - return v1 -} - -; case d < 0 && M > 0 (mul, sub, shift, add-sign-bit) -function %t_sdiv32_n3(i32) -> i32 { -block0(v0: i32): - v1 = sdiv_imm v0, -3 - ; check: iconst.i32 0x5555_5555 - ; check: smulhi v0, v2 - ; check: isub v3, v0 - ; check: sshr_imm v4, 1 - ; check: ushr_imm v5, 31 - ; check: v7 = iadd v5, v6 - ; check: v1 -> v7 - return v1 -} - -; simple case w/ shift by zero (mul, add-sign-bit) -function %t_sdiv32_p6(i32) -> i32 { -block0(v0: i32): - v1 = sdiv_imm v0, 6 - ; check: iconst.i32 0x2aaa_aaab - ; check: smulhi v0, v2 - ; check: ushr_imm v3, 31 - ; check: v5 = iadd v3, v4 - ; check: v1 -> v5 - return v1 -} - -; case d > 0 && M < 0 (mull, add, shift, add-sign-bit) -function %t_sdiv32_p7(i32) -> i32 { -block0(v0: i32): - v1 = sdiv_imm v0, 7 - ; check: iconst.i32 0xffff_ffff_9249_2493 - ; check: smulhi v0, v2 - ; check: iadd v3, v0 - ; check: sshr_imm v4, 2 - ; check: ushr_imm v5, 31 - ; check: v7 = iadd v5, v6 - ; check: v1 -> v7 - return v1 -} - -; simple case (mul, shift, add-sign-bit) -function %t_sdiv32_p625(i32) -> i32 { -block0(v0: i32): - v1 = sdiv_imm v0, 625 - ; check: iconst.i32 0x68db_8bad - ; check: smulhi v0, v2 - ; check: sshr_imm v3, 8 - ; check: ushr_imm v4, 31 - ; check: v6 = iadd v4, v5 - ; check: v1 -> v6 - return v1 -} - - -; -------- U64 -------- - -; complex case (mul, sub, shift, add, shift) -function %t_udiv64_p7(i64) -> i64 { -block0(v0: i64): - v1 = udiv_imm v0, 7 - ; check: iconst.i64 0x2492_4924_9249_2493 - ; check: umulhi v0, v2 - ; check: isub v0, v3 - ; check: ushr_imm v4, 1 - ; check: iadd v5, v3 - ; check: v7 = ushr_imm v6, 2 - ; check: v1 -> v7 - return v1 -} - -; simple case (mul, shift) -function %t_udiv64_p9(i64) -> i64 { -block0(v0: i64): - v1 = udiv_imm v0, 9 - ; check: iconst.i64 0xe38e_38e3_8e38_e38f - ; check: umulhi v0, v2 - ; check: v4 = ushr_imm v3, 3 - ; check: v1 -> v4 - return v1 -} - -; complex case (mul, sub, shift, add, shift) -function %t_udiv64_p125(i64) -> i64 { -block0(v0: i64): - v1 = udiv_imm v0, 125 - ; check: iconst.i64 0x0624_dd2f_1a9f_be77 - ; check: umulhi v0, v2 - ; check: isub v0, v3 - ; check: ushr_imm v4, 1 - ; check: iadd v5, v3 - ; check: v7 = ushr_imm v6, 6 - ; check: v1 -> v7 - return v1 -} - -; simple case w/ shift by zero (mul) -function %t_udiv64_p274177(i64) -> i64 { -block0(v0: i64): - v1 = udiv_imm v0, 274177 - ; check: iconst.i64 0x3d30_f19c_d101 - ; check: v3 = umulhi v0, v2 - ; check: v1 -> v3 - return v1 -} - - -; -------- S64 -------- - -; simple case (mul, shift, add-sign-bit) -function %t_sdiv64_n625(i64) -> i64 { -block0(v0: i64): - v1 = sdiv_imm v0, -625 - ; check: iconst.i64 0xcb92_3a29_c779_a6b5 - ; check: smulhi v0, v2 - ; check: sshr_imm v3, 7 - ; check: ushr_imm v4, 63 - ; check: v6 = iadd v4, v5 - ; check: v1 -> v6 - return v1 -} - -; simple case w/ zero shift (mul, add-sign-bit) -function %t_sdiv64_n6(i64) -> i64 { -block0(v0: i64): - v1 = sdiv_imm v0, -6 - ; check: iconst.i64 0xd555_5555_5555_5555 - ; check: smulhi v0, v2 - ; check: ushr_imm v3, 63 - ; check: v5 = iadd v3, v4 - ; check: v1 -> v5 - return v1 -} - -; simple case w/ zero shift (mul, add-sign-bit) -function %t_sdiv64_n5(i64) -> i64 { -block0(v0: i64): - v1 = sdiv_imm v0, -5 - ; check: iconst.i64 0x9999_9999_9999_9999 - ; check: smulhi v0, v2 - ; check: sshr_imm v3, 1 - ; check: ushr_imm v4, 63 - ; check: v6 = iadd v4, v5 - ; check: v1 -> v6 - return v1 -} - -; case d < 0 && M > 0 (mul, sub, shift, add-sign-bit) -function %t_sdiv64_n3(i64) -> i64 { -block0(v0: i64): - v1 = sdiv_imm v0, -3 - ; check: iconst.i64 0x5555_5555_5555_5555 - ; check: smulhi v0, v2 - ; check: isub v3, v0 - ; check: sshr_imm v4, 1 - ; check: ushr_imm v5, 63 - ; check: v7 = iadd v5, v6 - ; check: v1 -> v7 - return v1 -} - -; simple case w/ zero shift (mul, add-sign-bit) -function %t_sdiv64_p6(i64) -> i64 { -block0(v0: i64): - v1 = sdiv_imm v0, 6 - ; check: iconst.i64 0x2aaa_aaaa_aaaa_aaab - ; check: smulhi v0, v2 - ; check: ushr_imm v3, 63 - ; check: v5 = iadd v3, v4 - ; check: v1 -> v5 - return v1 -} - -; case d > 0 && M < 0 (mul, add, shift, add-sign-bit) -function %t_sdiv64_p15(i64) -> i64 { -block0(v0: i64): - v1 = sdiv_imm v0, 15 - ; check: iconst.i64 0x8888_8888_8888_8889 - ; check: smulhi v0, v2 - ; check: iadd v3, v0 - ; check: sshr_imm v4, 3 - ; check: ushr_imm v5, 63 - ; check: v7 = iadd v5, v6 - ; check: v1 -> v7 - return v1 -} - -; simple case (mul, shift, add-sign-bit) -function %t_sdiv64_p625(i64) -> i64 { -block0(v0: i64): - v1 = sdiv_imm v0, 625 - ; check: iconst.i64 0x346d_c5d6_3886_594b - ; check: smulhi v0, v2 - ; check: sshr_imm v3, 7 - ; check: ushr_imm v4, 63 - ; check: v6 = iadd v4, v5 - ; check: v1 -> v6 - return v1 -} diff --git a/cranelift/filetests/filetests/peepmatic/div_by_const_power_of_2.clif b/cranelift/filetests/filetests/peepmatic/div_by_const_power_of_2.clif deleted file mode 100644 index c91c15468c..0000000000 --- a/cranelift/filetests/filetests/peepmatic/div_by_const_power_of_2.clif +++ /dev/null @@ -1,293 +0,0 @@ -test peepmatic -target aarch64 -target i686 baseline - -; -------- U32 -------- - -; ignored -function %t_udiv32_p0(i32) -> i32 { -block0(v0: i32): - v1 = udiv_imm v0, 0 - ; check: udiv_imm v0, 0 - return v1 -} - -; converted to a nop -function %t_udiv32_p1(i32) -> i32 { -block0(v0: i32): - v1 = udiv_imm v0, 1 - ; check: nop - return v1 -} - -; shift -function %t_udiv32_p2(i32) -> i32 { -block0(v0: i32): - v1 = udiv_imm v0, 2 - ; check: ushr_imm v0, 1 - return v1 -} - -; shift -function %t_udiv32_p2p31(i32) -> i32 { -block0(v0: i32): - v1 = udiv_imm v0, 0x8000_0000 - ; check: ushr_imm v0, 31 - return v1 -} - - -; -------- U64 -------- - -; ignored -function %t_udiv64_p0(i64) -> i64 { -block0(v0: i64): - v1 = udiv_imm v0, 0 - ; check: udiv_imm v0, 0 - return v1 -} - -; converted to a nop -function %t_udiv64_p1(i64) -> i64 { -block0(v0: i64): - v1 = udiv_imm v0, 1 - ; check: nop - return v1 -} - -; shift -function %t_udiv64_p2(i64) -> i64 { -block0(v0: i64): - v1 = udiv_imm v0, 2 - ; check: ushr_imm v0, 1 - return v1 -} - -; shift -function %t_udiv64_p2p63(i64) -> i64 { -block0(v0: i64): - v1 = udiv_imm v0, 0x8000_0000_0000_0000 - ; check: ushr_imm v0, 63 - return v1 -} - - -; -------- S32 -------- - -; ignored -function %t_sdiv32_p0(i32) -> i32 { -block0(v0: i32): - v1 = sdiv_imm v0, 0 - ; check: sdiv_imm v0, 0 - return v1 -} - -; converted to a nop -function %t_sdiv32_p1(i32) -> i32 { -block0(v0: i32): - v1 = sdiv_imm v0, 1 - ; check: nop - return v1 -} - -; ignored -function %t_sdiv32_n1(i32) -> i32 { -block0(v0: i32): - v1 = sdiv_imm v0, -1 - ; check: sdiv_imm v0, -1 - return v1 -} - -; shift -function %t_sdiv32_p2(i32) -> i32 { -block0(v0: i32): - v1 = sdiv_imm v0, 2 - ; check: ushr_imm v0, 31 - ; check: iadd v0, v2 - ; check: sshr_imm v3, 1 - ; check: v1 -> v4 - return v1 -} - -; shift -function %t_sdiv32_n2(i32) -> i32 { -block0(v0: i32): - v1 = sdiv_imm v0, -2 - ; check: ushr_imm v0, 31 - ; check: iadd v0, v2 - ; check: sshr_imm v3, 1 - ; check: irsub_imm v4, 0 - return v1 -} - -; shift -function %t_sdiv32_p4(i32) -> i32 { -block0(v0: i32): - v1 = sdiv_imm v0, 4 - ; check: v2 = sshr_imm v0, 1 - ; check: ushr_imm v2, 30 - ; check: iadd v0, v3 - ; check: v5 = sshr_imm v4, 2 - ; check: v1 -> v5 - - return v1 -} - -; shift -function %t_sdiv32_n4(i32) -> i32 { -block0(v0: i32): - v1 = sdiv_imm v0, -4 - ; check: sshr_imm v0, 1 - ; check: ushr_imm v2, 30 - ; check: iadd v0, v3 - ; check: sshr_imm v4, 2 - ; check: irsub_imm v5, 0 - return v1 -} - -; shift -function %t_sdiv32_p2p30(i32) -> i32 { -block0(v0: i32): - v1 = sdiv_imm v0, 0x4000_0000 - ; check: sshr_imm v0, 29 - ; check: ushr_imm v2, 2 - ; check: iadd v0, v3 - ; check: v5 = sshr_imm v4, 30 - ; check: v1 -> v5 - return v1 -} - -; shift -function %t_sdiv32_n2p30(i32) -> i32 { -block0(v0: i32): - v1 = sdiv_imm v0, -0x4000_0000 - ; check: sshr_imm v0, 29 - ; check: ushr_imm v2, 2 - ; check: iadd v0, v3 - ; check: sshr_imm v4, 30 - ; check: irsub_imm v5, 0 - return v1 -} - -; there's no positive version of this, since -(-0x8000_0000) isn't -; representable. -function %t_sdiv32_n2p31(i32) -> i32 { -block0(v0: i32): - v1 = sdiv_imm v0, -0x8000_0000 - ; check: sshr_imm v0, 30 - ; check: ushr_imm v2, 1 - ; check: iadd v0, v3 - ; check: sshr_imm v4, 31 - ; check: irsub_imm v5, 0 - return v1 -} - - -; -------- S64 -------- - -; ignored -function %t_sdiv64_p0(i64) -> i64 { -block0(v0: i64): - v1 = sdiv_imm v0, 0 - ; check: sdiv_imm v0, 0 - return v1 -} - -; converted to a nop -function %t_sdiv64_p1(i64) -> i64 { -block0(v0: i64): - v1 = sdiv_imm v0, 1 - ; check: nop - return v1 -} - -; ignored -function %t_sdiv64_n1(i64) -> i64 { -block0(v0: i64): - v1 = sdiv_imm v0, -1 - ; check: sdiv_imm v0, -1 - return v1 -} - -; shift -function %t_sdiv64_p2(i64) -> i64 { -block0(v0: i64): - v1 = sdiv_imm v0, 2 - ; check: ushr_imm v0, 63 - ; check: iadd v0, v2 - ; check: v4 = sshr_imm v3, 1 - ; check: v1 -> v4 - return v1 -} - -; shift -function %t_sdiv64_n2(i64) -> i64 { -block0(v0: i64): - v1 = sdiv_imm v0, -2 - ; check: ushr_imm v0, 63 - ; check: iadd v0, v2 - ; check: sshr_imm v3, 1 - ; check: irsub_imm v4, 0 - return v1 -} - -; shift -function %t_sdiv64_p4(i64) -> i64 { -block0(v0: i64): - v1 = sdiv_imm v0, 4 - ; check: sshr_imm v0, 1 - ; check: ushr_imm v2, 62 - ; check: iadd v0, v3 - ; check: v5 = sshr_imm v4, 2 - ; check: v1 -> v5 - return v1 -} - -; shift -function %t_sdiv64_n4(i64) -> i64 { -block0(v0: i64): - v1 = sdiv_imm v0, -4 - ; check: sshr_imm v0, 1 - ; check: ushr_imm v2, 62 - ; check: iadd v0, v3 - ; check: sshr_imm v4, 2 - ; check: irsub_imm v5, 0 - return v1 -} - -; shift -function %t_sdiv64_p2p62(i64) -> i64 { -block0(v0: i64): - v1 = sdiv_imm v0, 0x4000_0000_0000_0000 - ; check: sshr_imm v0, 61 - ; check: ushr_imm v2, 2 - ; check: iadd v0, v3 - ; check: v5 = sshr_imm v4, 62 - ; check: v1 -> v5 - return v1 -} - -; shift -function %t_sdiv64_n2p62(i64) -> i64 { -block0(v0: i64): - v1 = sdiv_imm v0, -0x4000_0000_0000_0000 - ; check: sshr_imm v0, 61 - ; check: ushr_imm v2, 2 - ; check: iadd v0, v3 - ; check: sshr_imm v4, 62 - ; check: irsub_imm v5, 0 - return v1 -} - -; there's no positive version of this, since -(-0x8000_0000_0000_0000) isn't -; representable. -function %t_sdiv64_n2p63(i64) -> i64 { -block0(v0: i64): - v1 = sdiv_imm v0, -0x8000_0000_0000_0000 - ; check: sshr_imm v0, 62 - ; check: ushr_imm v2, 1 - ; check: iadd v0, v3 - ; check: sshr_imm v4, 63 - ; check: irsub_imm v5, 0 - return v1 -} diff --git a/cranelift/filetests/filetests/peepmatic/do_not_keep_applying_optimizations_after_replacing_with_an_alias.clif b/cranelift/filetests/filetests/peepmatic/do_not_keep_applying_optimizations_after_replacing_with_an_alias.clif deleted file mode 100644 index f3fc4c72a9..0000000000 --- a/cranelift/filetests/filetests/peepmatic/do_not_keep_applying_optimizations_after_replacing_with_an_alias.clif +++ /dev/null @@ -1,15 +0,0 @@ -test peepmatic -target aarch64 -target x86_64 - -;; This file used to trigger assertions where we would keep trying to -;; unnecessarily apply optimizations after replacing an instruction with an -;; alias of another value that we had already optimized. - -function %foo() { -block0: - v0 = iconst.i32 3 - v1 = srem_imm v0, 2 - v2 = sdiv_imm v1, 1 - trap unreachable -} diff --git a/cranelift/filetests/filetests/peepmatic/do_not_reorder_instructions_when_transplanting.clif b/cranelift/filetests/filetests/peepmatic/do_not_reorder_instructions_when_transplanting.clif deleted file mode 100644 index 37f7652831..0000000000 --- a/cranelift/filetests/filetests/peepmatic/do_not_reorder_instructions_when_transplanting.clif +++ /dev/null @@ -1,23 +0,0 @@ -test peepmatic -target aarch64 -target x86_64 - -;; Test that although v5 can be replaced with v1, we don't transplant `load.i32 -;; v0` on top of `iadd v3, v4`, because that would move the load past other uses -;; of its result. - -function %foo(i64) -> i32 { -block0(v0: i64): - v1 = load.i32 v0 - v2 = iconst.i32 16 - v3 = iadd_imm v1, -16 - v4 = iconst.i32 16 - v5 = iadd v3, v4 - ; check: v1 = load.i32 v0 - ; nextln: v5 -> v1 - ; nextln: v2 = iconst.i32 16 - ; nextln: v3 = iadd_imm v1, -16 - ; nextln: v4 = iconst.i32 16 - ; nextln: nop - return v5 -} diff --git a/cranelift/filetests/filetests/peepmatic/fold-extended-move-wraparound.clif b/cranelift/filetests/filetests/peepmatic/fold-extended-move-wraparound.clif deleted file mode 100644 index f6db2cfbad..0000000000 --- a/cranelift/filetests/filetests/peepmatic/fold-extended-move-wraparound.clif +++ /dev/null @@ -1,15 +0,0 @@ -test peepmatic -target aarch64 -target x86_64 - -function %wraparound(i64 vmctx) -> f32 system_v { - gv0 = vmctx - gv1 = iadd_imm.i64 gv0, 48 - -block35(v0: i64): - v88 = iconst.i64 0 - v89 = iconst.i64 0x8000_0000_0000_0000 - v90 = ishl_imm v88, 0x8000_0000_0000_0000 - v91 = sshr v90, v89; check: sshr_imm v90, 0x8000_0000_0000_0000 - trap user0 -} diff --git a/cranelift/filetests/filetests/peepmatic/rem_by_const_non_power_of_2.clif b/cranelift/filetests/filetests/peepmatic/rem_by_const_non_power_of_2.clif deleted file mode 100644 index dd13e73c0f..0000000000 --- a/cranelift/filetests/filetests/peepmatic/rem_by_const_non_power_of_2.clif +++ /dev/null @@ -1,286 +0,0 @@ -test peepmatic -target aarch64 -target i686 baseline - -; -------- U32 -------- - -; complex case (mul, sub, shift, add, shift) -function %t_urem32_p7(i32) -> i32 { -block0(v0: i32): - v1 = urem_imm v0, 7 - ; check: iconst.i32 0x2492_4925 - ; check: umulhi v0, v2 - ; check: isub v0, v3 - ; check: ushr_imm v4, 1 - ; check: iadd v5, v3 - ; check: ushr_imm v6, 2 - ; check: imul_imm v7, 7 - ; check: isub v0, v8 - return v1 -} - -; simple case (mul, shift) -function %t_urem32_p125(i32) -> i32 { -block0(v0: i32): - v1 = urem_imm v0, 125 - ; check: iconst.i32 0x1062_4dd3 - ; check: umulhi v0, v2 - ; check: ushr_imm v3, 3 - ; check: imul_imm v4, 125 - ; check: isub v0, v5 - return v1 -} - -; simple case w/ shift by zero (mul) -function %t_urem32_p641(i32) -> i32 { -block0(v0: i32): - v1 = urem_imm v0, 641 - ; check: iconst.i32 0x0066_3d81 - ; check: umulhi v0, v2 - ; check: imul_imm v3, 641 - ; check: isub v0, v4 - return v1 -} - - -; -------- S32 -------- - -; simple case w/ shift by zero (mul, add-sign-bit) -function %t_srem32_n6(i32) -> i32 { -block0(v0: i32): - v1 = srem_imm v0, -6 - ; check: iconst.i32 0xffff_ffff_d555_5555 - ; check: smulhi v0, v2 - ; check: ushr_imm v3, 31 - ; check: iadd v3, v4 - ; check: imul_imm v5, -6 - ; check: isub v0, v6 - return v1 -} - -; simple case (mul, shift, add-sign-bit) -function %t_srem32_n5(i32) -> i32 { -block0(v0: i32): - v1 = srem_imm v0, -5 - ; check: iconst.i32 0xffff_ffff_9999_9999 - ; check: smulhi v0, v2 - ; check: sshr_imm v3, 1 - ; check: ushr_imm v4, 31 - ; check: iadd v4, v5 - ; check: imul_imm v6, -5 - ; check: isub v0, v7 - return v1 -} - -; case d < 0 && M > 0 (mul, sub, shift, add-sign-bit) -function %t_srem32_n3(i32) -> i32 { -block0(v0: i32): - v1 = srem_imm v0, -3 - ; check: iconst.i32 0x5555_5555 - ; check: smulhi v0, v2 - ; check: isub v3, v0 - ; check: sshr_imm v4, 1 - ; check: ushr_imm v5, 31 - ; check: iadd v5, v6 - ; check: imul_imm v7, -3 - ; check: isub v0, v8 - return v1 -} - -; simple case w/ shift by zero (mul, add-sign-bit) -function %t_srem32_p6(i32) -> i32 { -block0(v0: i32): - v1 = srem_imm v0, 6 - ; check: iconst.i32 0x2aaa_aaab - ; check: smulhi v0, v2 - ; check: ushr_imm v3, 31 - ; check: iadd v3, v4 - ; check: imul_imm v5, 6 - ; check: isub v0, v6 - return v1 -} - -; case d > 0 && M < 0 (mull, add, shift, add-sign-bit) -function %t_srem32_p7(i32) -> i32 { -block0(v0: i32): - v1 = srem_imm v0, 7 - ; check: iconst.i32 0xffff_ffff_9249_2493 - ; check: smulhi v0, v2 - ; check: iadd v3, v0 - ; check: sshr_imm v4, 2 - ; check: ushr_imm v5, 31 - ; check: iadd v5, v6 - ; check: imul_imm v7, 7 - ; check: isub v0, v8 - return v1 -} - -; simple case (mul, shift, add-sign-bit) -function %t_srem32_p625(i32) -> i32 { -block0(v0: i32): - v1 = srem_imm v0, 625 - ; check: iconst.i32 0x68db_8bad - ; check: smulhi v0, v2 - ; check: sshr_imm v3, 8 - ; check: ushr_imm v4, 31 - ; check: iadd v4, v5 - ; check: imul_imm v6, 625 - ; check: isub v0, v7 - return v1 -} - - -; -------- U64 -------- - -; complex case (mul, sub, shift, add, shift) -function %t_urem64_p7(i64) -> i64 { -block0(v0: i64): - v1 = urem_imm v0, 7 - ; check: umulhi v0, v2 - ; check: isub v0, v3 - ; check: ushr_imm v4, 1 - ; check: iadd v5, v3 - ; check: ushr_imm v6, 2 - ; check: imul_imm v7, 7 - ; check: isub v0, v8 - return v1 -} - -; simple case (mul, shift) -function %t_urem64_p9(i64) -> i64 { -block0(v0: i64): - v1 = urem_imm v0, 9 - ; check: iconst.i64 0xe38e_38e3_8e38_e38f - ; check: umulhi v0, v2 - ; check: ushr_imm v3, 3 - ; check: imul_imm v4, 9 - ; check: isub v0, v5 - return v1 -} - -; complex case (mul, sub, shift, add, shift) -function %t_urem64_p125(i64) -> i64 { -block0(v0: i64): - v1 = urem_imm v0, 125 - ; check: iconst.i64 0x0624_dd2f_1a9f_be77 - ; check: umulhi v0, v2 - ; check: isub v0, v3 - ; check: ushr_imm v4, 1 - ; check: iadd v5, v3 - ; check: ushr_imm v6, 6 - ; check: imul_imm v7, 125 - ; check: isub v0, v8 - return v1 -} - -; simple case w/ shift by zero (mul) -function %t_urem64_p274177(i64) -> i64 { -block0(v0: i64): - v1 = urem_imm v0, 274177 - ; check: iconst.i64 0x3d30_f19c_d101 - ; check: umulhi v0, v2 - ; check: imul_imm v3, 0x0004_2f01 - ; check: isub v0, v4 - return v1 -} - - -; -------- S64 -------- - -; simple case (mul, shift, add-sign-bit) -function %t_srem64_n625(i64) -> i64 { -block0(v0: i64): - v1 = srem_imm v0, -625 - ; check: iconst.i64 0xcb92_3a29_c779_a6b5 - ; check: smulhi v0, v2 - ; check: sshr_imm v3, 7 - ; check: ushr_imm v4, 63 - ; check: iadd v4, v5 - ; check: imul_imm v6, -625 - ; check: isub v0, v7 - return v1 -} - -; simple case w/ zero shift (mul, add-sign-bit) -function %t_srem64_n6(i64) -> i64 { -block0(v0: i64): - v1 = srem_imm v0, -6 - ; check: iconst.i64 0xd555_5555_5555_5555 - ; check: smulhi v0, v2 - ; check: ushr_imm v3, 63 - ; check: iadd v3, v4 - ; check: imul_imm v5, -6 - ; check: isub v0, v6 - return v1 -} - -; simple case w/ zero shift (mul, add-sign-bit) -function %t_srem64_n5(i64) -> i64 { -block0(v0: i64): - v1 = srem_imm v0, -5 - ; check: iconst.i64 0x9999_9999_9999_9999 - ; check: smulhi v0, v2 - ; check: sshr_imm v3, 1 - ; check: ushr_imm v4, 63 - ; check: iadd v4, v5 - ; check: imul_imm v6, -5 - ; check: isub v0, v7 - return v1 -} - -; case d < 0 && M > 0 (mul, sub, shift, add-sign-bit) -function %t_srem64_n3(i64) -> i64 { -block0(v0: i64): - v1 = srem_imm v0, -3 - ; check: iconst.i64 0x5555_5555_5555_5555 - ; check: smulhi v0, v2 - ; check: isub v3, v0 - ; check: sshr_imm v4, 1 - ; check: ushr_imm v5, 63 - ; check: iadd v5, v6 - ; check: imul_imm v7, -3 - ; check: isub v0, v8 - return v1 -} - -; simple case w/ zero shift (mul, add-sign-bit) -function %t_srem64_p6(i64) -> i64 { -block0(v0: i64): - v1 = srem_imm v0, 6 - ; check: iconst.i64 0x2aaa_aaaa_aaaa_aaab - ; check: smulhi v0, v2 - ; check: ushr_imm v3, 63 - ; check: iadd v3, v4 - ; check: imul_imm v5, 6 - ; check: isub v0, v6 - return v1 -} - -; case d > 0 && M < 0 (mul, add, shift, add-sign-bit) -function %t_srem64_p15(i64) -> i64 { -block0(v0: i64): - v1 = srem_imm v0, 15 - ; check: iconst.i64 0x8888_8888_8888_8889 - ; check: smulhi v0, v2 - ; check: iadd v3, v0 - ; check: sshr_imm v4, 3 - ; check: ushr_imm v5, 63 - ; check: iadd v5, v6 - ; check: imul_imm v7, 15 - ; check: isub v0, v8 - return v1 -} - -; simple case (mul, shift, add-sign-bit) -function %t_srem64_p625(i64) -> i64 { -block0(v0: i64): - v1 = srem_imm v0, 625 - ; check: iconst.i64 0x346d_c5d6_3886_594b - ; check: smulhi v0, v2 - ; check: sshr_imm v3, 7 - ; check: ushr_imm v4, 63 - ; check: iadd v4, v5 - ; check: imul_imm v6, 625 - ; check: isub v0, v7 - return v1 -} diff --git a/cranelift/filetests/filetests/peepmatic/rem_by_const_power_of_2.clif b/cranelift/filetests/filetests/peepmatic/rem_by_const_power_of_2.clif deleted file mode 100644 index 23225a4ec9..0000000000 --- a/cranelift/filetests/filetests/peepmatic/rem_by_const_power_of_2.clif +++ /dev/null @@ -1,292 +0,0 @@ -test peepmatic -target aarch64 -target i686 baseline - -; -------- U32 -------- - -; ignored -function %t_urem32_p0(i32) -> i32 { -block0(v0: i32): - v1 = urem_imm v0, 0 - ; check: urem_imm v0, 0 - return v1 -} - -; converted to constant zero -function %t_urem32_p1(i32) -> i32 { -block0(v0: i32): - v1 = urem_imm v0, 1 - ; check: iconst.i32 0 - return v1 -} - -; shift -function %t_urem32_p2(i32) -> i32 { -block0(v0: i32): - v1 = urem_imm v0, 2 - ; check: band_imm v0, 1 - return v1 -} - -; shift -function %t_urem32_p2p31(i32) -> i32 { -block0(v0: i32): - v1 = urem_imm v0, 0x8000_0000 - ; check: band_imm v0, 0x7fff_ffff - return v1 -} - - -; -------- U64 -------- - -; ignored -function %t_urem64_p0(i64) -> i64 { -block0(v0: i64): - v1 = urem_imm v0, 0 - ; check: urem_imm v0, 0 - return v1 -} - -; converted to constant zero -function %t_urem64_p1(i64) -> i64 { -block0(v0: i64): - v1 = urem_imm v0, 1 - ; check: iconst.i64 0 - return v1 -} - -; shift -function %t_urem64_p2(i64) -> i64 { -block0(v0: i64): - v1 = urem_imm v0, 2 - ; check: band_imm v0, 1 - return v1 -} - -; shift -function %t_urem64_p2p63(i64) -> i64 { -block0(v0: i64): - v1 = urem_imm v0, 0x8000_0000_0000_0000 - ; check: band_imm v0, 0x7fff_ffff_ffff_ffff - return v1 -} - - -; -------- S32 -------- - -; ignored -function %t_srem32_n1(i32) -> i32 { -block0(v0: i32): - v1 = srem_imm v0, -1 - ; check: srem_imm v0, -1 - return v1 -} - -; ignored -function %t_srem32_p0(i32) -> i32 { -block0(v0: i32): - v1 = srem_imm v0, 0 - ; check: srem_imm v0, 0 - return v1 -} - -; converted to constant zero -function %t_srem32_p1(i32) -> i32 { -block0(v0: i32): - v1 = srem_imm v0, 1 - ; check: iconst.i32 0 - return v1 -} - -; shift -function %t_srem32_p2(i32) -> i32 { -block0(v0: i32): - v1 = srem_imm v0, 2 - ; check: ushr_imm v0, 31 - ; check: iadd v0, v2 - ; check: band_imm v3, -2 - ; check: isub v0, v4 - return v1 -} - -; shift -function %t_srem32_n2(i32) -> i32 { -block0(v0: i32): - v1 = srem_imm v0, -2 - ; check: ushr_imm v0, 31 - ; check: iadd v0, v2 - ; check: band_imm v3, -2 - ; check: isub v0, v4 - return v1 -} - -; shift -function %t_srem32_p4(i32) -> i32 { -block0(v0: i32): - v1 = srem_imm v0, 4 - ; check: sshr_imm v0, 1 - ; check: ushr_imm v2, 30 - ; check: iadd v0, v3 - ; check: band_imm v4, -4 - ; check: isub v0, v5 - return v1 -} - -; shift -function %t_srem32_n4(i32) -> i32 { -block0(v0: i32): - v1 = srem_imm v0, -4 - ; check: sshr_imm v0, 1 - ; check: ushr_imm v2, 30 - ; check: iadd v0, v3 - ; check: band_imm v4, -4 - ; check: isub v0, v5 - return v1 -} - -; shift -function %t_srem32_p2p30(i32) -> i32 { -block0(v0: i32): - v1 = srem_imm v0, 0x4000_0000 - ; check: sshr_imm v0, 29 - ; check: ushr_imm v2, 2 - ; check: iadd v0, v3 - ; check: band_imm v4, 0xffff_ffff_c000_0000 - ; check: isub v0, v5 - return v1 -} - -; shift -function %t_srem32_n2p30(i32) -> i32 { -block0(v0: i32): - v1 = srem_imm v0, -0x4000_0000 - ; check: sshr_imm v0, 29 - ; check: ushr_imm v2, 2 - ; check: iadd v0, v3 - ; check: band_imm v4, 0xffff_ffff_c000_0000 - ; check: isub v0, v5 - return v1 -} - -; there's no positive version of this, since -(-0x8000_0000) isn't -; representable. -function %t_srem32_n2p31(i32) -> i32 { -block0(v0: i32): - v1 = srem_imm v0, -0x8000_0000 - ; check: sshr_imm v0, 30 - ; check: ushr_imm v2, 1 - ; check: iadd v0, v3 - ; check: band_imm v4, 0xffff_ffff_8000_0000 - ; check: isub v0, v5 - return v1 -} - - -; -------- S64 -------- - -; ignored -function %t_srem64_n1(i64) -> i64 { -block0(v0: i64): - v1 = srem_imm v0, -1 - ; check: srem_imm v0, -1 - return v1 -} - -; ignored -function %t_srem64_p0(i64) -> i64 { -block0(v0: i64): - v1 = srem_imm v0, 0 - ; check: srem_imm v0, 0 - return v1 -} - -; converted to constant zero -function %t_srem64_p1(i64) -> i64 { -block0(v0: i64): - v1 = srem_imm v0, 1 - ; check: iconst.i64 0 - return v1 -} - -; shift -function %t_srem64_p2(i64) -> i64 { -block0(v0: i64): - v1 = srem_imm v0, 2 - ; check: ushr_imm v0, 63 - ; check: iadd v0, v2 - ; check: band_imm v3, -2 - ; check: isub v0, v4 - return v1 -} - -; shift -function %t_srem64_n2(i64) -> i64 { -block0(v0: i64): - v1 = srem_imm v0, -2 - ; check: ushr_imm v0, 63 - ; check: iadd v0, v2 - ; check: band_imm v3, -2 - ; check: isub v0, v4 - return v1 -} - -; shift -function %t_srem64_p4(i64) -> i64 { -block0(v0: i64): - v1 = srem_imm v0, 4 - ; check: sshr_imm v0, 1 - ; check: ushr_imm v2, 62 - ; check: iadd v0, v3 - ; check: band_imm v4, -4 - ; check: isub v0, v5 - return v1 -} - -; shift -function %t_srem64_n4(i64) -> i64 { -block0(v0: i64): - v1 = srem_imm v0, -4 - ; check: sshr_imm v0, 1 - ; check: ushr_imm v2, 62 - ; check: iadd v0, v3 - ; check: band_imm v4, -4 - ; check: isub v0, v5 - return v1 -} - -; shift -function %t_srem64_p2p62(i64) -> i64 { -block0(v0: i64): - v1 = srem_imm v0, 0x4000_0000_0000_0000 - ; check: sshr_imm v0, 61 - ; check: ushr_imm v2, 2 - ; check: iadd v0, v3 - ; check: band_imm v4, 0xc000_0000_0000_0000 - ; check: isub v0, v5 - return v1 -} - -; shift -function %t_srem64_n2p62(i64) -> i64 { -block0(v0: i64): - v1 = srem_imm v0, -0x4000_0000_0000_0000 - ; check: sshr_imm v0, 61 - ; check: ushr_imm v2, 2 - ; check: iadd v0, v3 - ; check: band_imm v4, 0xc000_0000_0000_0000 - ; check: isub v0, v5 - return v1 -} - -; there's no positive version of this, since -(-0x8000_0000_0000_0000) isn't -; representable. -function %t_srem64_n2p63(i64) -> i64 { -block0(v0: i64): - v1 = srem_imm v0, -0x8000_0000_0000_0000 - ; check: sshr_imm v0, 62 - ; check: ushr_imm v2, 1 - ; check: iadd v0, v3 - ; check: band_imm v4, 0x8000_0000_0000_0000 - ; check: isub v0, v5 - return v1 -} diff --git a/cranelift/filetests/filetests/peepmatic/replace_branching_instructions_and_cfg_predecessors.clif b/cranelift/filetests/filetests/peepmatic/replace_branching_instructions_and_cfg_predecessors.clif deleted file mode 100644 index 643f08a4a7..0000000000 --- a/cranelift/filetests/filetests/peepmatic/replace_branching_instructions_and_cfg_predecessors.clif +++ /dev/null @@ -1,23 +0,0 @@ -test peepmatic -target aarch64 -target x86_64 - -function u0:2(i64 , i64) { - gv1 = load.i64 notrap aligned gv0 - heap0 = static gv1 - block0(v0: i64, v1: i64): - v16 = iconst.i32 6 - v17 = heap_addr.i64 heap0, v16, 1 - v18 = load.i32 v17 - v19 = iconst.i32 4 - v20 = icmp ne v18, v19 - v21 = bint.i32 v20 - brnz v21, block2 - jump block4 - block4: - jump block1 - block2: - jump block1 - block1: - return -} diff --git a/cranelift/filetests/filetests/peepmatic/simplify32.clif b/cranelift/filetests/filetests/peepmatic/simplify32.clif deleted file mode 100644 index 95ba29f30a..0000000000 --- a/cranelift/filetests/filetests/peepmatic/simplify32.clif +++ /dev/null @@ -1,61 +0,0 @@ -test peepmatic -target aarch64 -target i686 - -;; 32-bits platforms. - -function %iadd_imm(i32) -> i32 { -block0(v0: i32): - v1 = iconst.i32 2 - v2 = iadd v0, v1 - return v2 -} -; sameln: function %iadd_imm -; nextln: block0(v0: i32): -; nextln: v1 = iconst.i32 2 -; nextln: v2 = iadd_imm v0, 2 -; nextln: return v2 -; nextln: } - -function %isub_imm(i32) -> i32 { -block0(v0: i32): - v1 = iconst.i32 2 - v2 = isub v0, v1 - return v2 -} -; sameln: function %isub_imm -; nextln: block0(v0: i32): -; nextln: v1 = iconst.i32 2 -; nextln: v2 = iadd_imm v0, -2 -; nextln: return v2 -; nextln: } - -function %icmp_imm(i32) -> i32 { -block0(v0: i32): - v1 = iconst.i32 2 - v2 = icmp slt v0, v1 - v3 = bint.i32 v2 - return v3 -} -; sameln: function %icmp_imm -; nextln: block0(v0: i32): -; nextln: v1 = iconst.i32 2 -; nextln: v2 = icmp_imm slt v0, 2 -; nextln: v3 = bint.i32 v2 -; nextln: return v3 -; nextln: } - -;; Don't simplify operations that would get illegal because of lack of native -;; support. -function %iadd_imm(i64) -> i64 { -block0(v0: i64): - v1 = iconst.i64 2 - v2 = iadd v0, v1 - return v2 -} -; sameln: function %iadd_imm -; nextln: block0(v0: i64): -; nextln: v1 = iconst.i64 2 -; nextln: v2 = iadd v0, v1 -; nextln: return v2 -; nextln: } diff --git a/cranelift/filetests/filetests/peepmatic/simplify64.clif b/cranelift/filetests/filetests/peepmatic/simplify64.clif deleted file mode 100644 index 4fb5b81e18..0000000000 --- a/cranelift/filetests/filetests/peepmatic/simplify64.clif +++ /dev/null @@ -1,327 +0,0 @@ -test peepmatic -target aarch64 -target x86_64 - -;; 64-bits platforms. - -function %iadd_imm(i32) -> i32 { -block0(v0: i32): - v1 = iconst.i32 2 - v2 = iadd v0, v1 - return v2 -} -; sameln: function %iadd_imm -; nextln: block0(v0: i32): -; nextln: v1 = iconst.i32 2 -; nextln: v2 = iadd_imm v0, 2 -; nextln: return v2 -; nextln: } - -function %isub_imm(i32) -> i32 { -block0(v0: i32): - v1 = iconst.i32 2 - v2 = isub v0, v1 - return v2 -} -; sameln: function %isub_imm -; nextln: block0(v0: i32): -; nextln: v1 = iconst.i32 2 -; nextln: v2 = iadd_imm v0, -2 -; nextln: return v2 -; nextln: } - -function %icmp_imm(i32) -> i32 { -block0(v0: i32): - v1 = iconst.i32 2 - v2 = icmp slt v0, v1 - v3 = bint.i32 v2 - return v3 -} -; sameln: function %icmp_imm -; nextln: block0(v0: i32): -; nextln: v1 = iconst.i32 2 -; nextln: v2 = icmp_imm slt v0, 2 -; nextln: v3 = bint.i32 v2 -; nextln: return v3 -; nextln: } - -function %ifcmp_imm(i32) -> i32 { -block0(v0: i32): - v1 = iconst.i32 2 - v2 = ifcmp v0, v1 - brif eq v2, block1 - jump block2 - -block1: - v3 = iconst.i32 1 - return v3 - -block2: - v4 = iconst.i32 2 - return v4 -} -; sameln: function %ifcmp_imm -; nextln: block0(v0: i32): -; nextln: v1 = iconst.i32 2 -; nextln: v2 = ifcmp_imm v0, 2 -; nextln: brif eq v2, block1 -; nextln: jump block2 -; nextln: -; nextln: block1: -; nextln: v3 = iconst.i32 1 -; nextln: return v3 -; nextln: -; nextln: block2: -; nextln: v4 = iconst.i32 2 -; nextln: return v4 -; nextln: } - -function %brz_bint(i32) { -block0(v0: i32): - v3 = icmp_imm slt v0, 0 - v1 = bint.i32 v3 - v2 = select v1, v1, v1 - trapz v1, user0 - brz v1, block1 - jump block2 - -block1: - return - -block2: - return -} -; sameln: function %brz_bint -; nextln: (v0: i32): -; nextln: v3 = icmp_imm slt v0, 0 -; nextln: v1 = bint.i32 v3 -; nextln: v2 = select v3, v1, v1 -; nextln: trapz v3, user0 -; nextln: brnz v3, block2 -; nextln: jump block1 - -function %irsub_imm(i32) -> i32 { -block0(v0: i32): - v1 = iconst.i32 2 - v2 = isub v1, v0 - return v2 -} -; sameln: function %irsub_imm -; nextln: block0(v0: i32): -; nextln: v1 = iconst.i32 2 -; nextln: v2 = irsub_imm v0, 2 -; nextln: return v2 -; nextln: } - -;; Sign-extensions. - -;; 8 -> 16 -function %uextend_8_16() -> i16 { -block0: - v0 = iconst.i16 37 - v1 = ishl_imm v0, 8 - v2 = ushr_imm v1, 8 - return v2 -} -; sameln: function %uextend_8_16 -; nextln: block0: -; nextln: v0 = iconst.i16 37 -; nextln: v1 = ishl_imm v0, 8 -; nextln: v3 = ireduce.i8 v0 -; nextln: v2 = uextend.i16 v3 -; nextln: return v2 -; nextln: } - -function %sextend_8_16() -> i16 { -block0: - v0 = iconst.i16 37 - v1 = ishl_imm v0, 8 - v2 = sshr_imm v1, 8 - return v2 -} -; sameln: function %sextend_8_16 -; nextln: block0: -; nextln: v0 = iconst.i16 37 -; nextln: v1 = ishl_imm v0, 8 -; nextln: v3 = ireduce.i8 v0 -; nextln: v2 = sextend.i16 v3 -; nextln: return v2 -; nextln: } - -;; 8 -> 32 -function %uextend_8_32() -> i32 { -block0: - v0 = iconst.i32 37 - v1 = ishl_imm v0, 24 - v2 = ushr_imm v1, 24 - return v2 -} -; sameln: function %uextend_8_32 -; nextln: block0: -; nextln: v0 = iconst.i32 37 -; nextln: v1 = ishl_imm v0, 24 -; nextln: v3 = ireduce.i8 v0 -; nextln: v2 = uextend.i32 v3 -; nextln: return v2 -; nextln: } - -function %sextend_8_32() -> i32 { -block0: - v0 = iconst.i32 37 - v1 = ishl_imm v0, 24 - v2 = sshr_imm v1, 24 - return v2 -} -; sameln: function %sextend_8_32 -; nextln: block0: -; nextln: v0 = iconst.i32 37 -; nextln: v1 = ishl_imm v0, 24 -; nextln: v3 = ireduce.i8 v0 -; nextln: v2 = sextend.i32 v3 -; nextln: return v2 -; nextln: } - -;; 16 -> 32 -function %uextend_16_32() -> i32 { -block0: - v0 = iconst.i32 37 - v1 = ishl_imm v0, 16 - v2 = ushr_imm v1, 16 - return v2 -} -; sameln: function %uextend_16_32 -; nextln: block0: -; nextln: v0 = iconst.i32 37 -; nextln: v1 = ishl_imm v0, 16 -; nextln: v3 = ireduce.i16 v0 -; nextln: v2 = uextend.i32 v3 -; nextln: return v2 -; nextln: } - -function %sextend_16_32() -> i32 { -block0: - v0 = iconst.i32 37 - v1 = ishl_imm v0, 16 - v2 = sshr_imm v1, 16 - return v2 -} -; sameln: function %sextend_16_32 -; nextln: block0: -; nextln: v0 = iconst.i32 37 -; nextln: v1 = ishl_imm v0, 16 -; nextln: v3 = ireduce.i16 v0 -; nextln: v2 = sextend.i32 v3 -; nextln: return v2 -; nextln: } - -;; 8 -> 64 -function %uextend_8_64() -> i64 { -block0: - v0 = iconst.i64 37 - v1 = ishl_imm v0, 56 - v2 = ushr_imm v1, 56 - return v2 -} -; sameln: function %uextend_8_64 -; nextln: block0: -; nextln: v0 = iconst.i64 37 -; nextln: v1 = ishl_imm v0, 56 -; nextln: v3 = ireduce.i8 v0 -; nextln: v2 = uextend.i64 v3 -; nextln: return v2 -; nextln: } - -function %sextend_8_64() -> i64 { -block0: - v0 = iconst.i64 37 - v1 = ishl_imm v0, 56 - v2 = sshr_imm v1, 56 - return v2 -} -; sameln: function %sextend_8_64 -; nextln: block0: -; nextln: v0 = iconst.i64 37 -; nextln: v1 = ishl_imm v0, 56 -; nextln: v3 = ireduce.i8 v0 -; nextln: v2 = sextend.i64 v3 -; nextln: return v2 -; nextln: } - -;; 16 -> 64 -function %uextend_16_64() -> i64 { -block0: - v0 = iconst.i64 37 - v1 = ishl_imm v0, 48 - v2 = ushr_imm v1, 48 - return v2 -} -; sameln: function %uextend_16_64 -; nextln: block0: -; nextln: v0 = iconst.i64 37 -; nextln: v1 = ishl_imm v0, 48 -; nextln: v3 = ireduce.i16 v0 -; nextln: v2 = uextend.i64 v3 -; nextln: return v2 -; nextln: } - -function %sextend_16_64() -> i64 { -block0: - v0 = iconst.i64 37 - v1 = ishl_imm v0, 48 - v2 = sshr_imm v1, 48 - return v2 -} -; sameln: function %sextend_16_64 -; nextln: block0: -; nextln: v0 = iconst.i64 37 -; nextln: v1 = ishl_imm v0, 48 -; nextln: v3 = ireduce.i16 v0 -; nextln: v2 = sextend.i64 v3 -; nextln: return v2 -; nextln: } - -;; 32 -> 64 -function %uextend_32_64() -> i64 { -block0: - v0 = iconst.i64 37 - v1 = ishl_imm v0, 32 - v2 = ushr_imm v1, 32 - return v2 -} -; sameln: function %uextend_32_64 -; nextln: block0: -; nextln: v0 = iconst.i64 37 -; nextln: v1 = ishl_imm v0, 32 -; nextln: v3 = ireduce.i32 v0 -; nextln: v2 = uextend.i64 v3 -; nextln: return v2 -; nextln: } - -function %sextend_32_64() -> i64 { -block0: - v0 = iconst.i64 37 - v1 = ishl_imm v0, 32 - v2 = sshr_imm v1, 32 - return v2 -} -; sameln: function %sextend_32_64 -; nextln: block0: -; nextln: v0 = iconst.i64 37 -; nextln: v1 = ishl_imm v0, 32 -; nextln: v3 = ireduce.i32 v0 -; nextln: v2 = sextend.i64 v3 -; nextln: return v2 -; nextln: } - -function %add_imm_fold(i32) -> i32 { -block0(v0: i32): - v1 = iadd_imm v0, 42 - v2 = iadd_imm v1, -42 - return v2 -} -; sameln: function %add_imm_fold(i32) -; nextln: block0(v0: i32): -; nextln: v2 -> v0 -; nextln: v1 = iadd_imm v0, 42 -; nextln: nop -; nextln: return v2 diff --git a/cranelift/filetests/filetests/peepmatic/simplify_instruction_into_alias_of_value.clif b/cranelift/filetests/filetests/peepmatic/simplify_instruction_into_alias_of_value.clif deleted file mode 100644 index a7c059f6c0..0000000000 --- a/cranelift/filetests/filetests/peepmatic/simplify_instruction_into_alias_of_value.clif +++ /dev/null @@ -1,21 +0,0 @@ -test peepmatic -target aarch64 -target x86_64 - -;; The `isub` is a no-op, but we can't replace the whole `isub` instruction with -;; its `v2` operand's instruction because `v2` is one of many results. Instead, -;; we need to make an alias `v3 -> v2`. - -function %replace_inst_with_alias() -> i32 { - sig0 = (i32, i32) -> i32, i32 - fn0 = u0:0 sig0 - -block0: - v0 = iconst.i32 0 - v1, v2 = call fn0(v0, v0) - v3 = isub v2, v0 - ; check: v0 = iconst.i32 0 - ; nextln: v1, v2 = call fn0(v0, v0) - ; nextln: v3 -> v2 - return v3 -} diff --git a/cranelift/filetests/src/lib.rs b/cranelift/filetests/src/lib.rs index e5ea69e27f..133d610621 100644 --- a/cranelift/filetests/src/lib.rs +++ b/cranelift/filetests/src/lib.rs @@ -44,7 +44,6 @@ mod test_domtree; mod test_interpret; mod test_legalizer; mod test_licm; -mod test_peepmatic; mod test_preopt; mod test_print_cfg; mod test_run; @@ -119,7 +118,6 @@ fn new_subtest(parsed: &TestCommand) -> anyhow::Result "interpret" => test_interpret::subtest(parsed), "legalizer" => test_legalizer::subtest(parsed), "licm" => test_licm::subtest(parsed), - "peepmatic" => test_peepmatic::subtest(parsed), "preopt" => test_preopt::subtest(parsed), "print-cfg" => test_print_cfg::subtest(parsed), "run" => test_run::subtest(parsed), diff --git a/cranelift/filetests/src/test_peepmatic.rs b/cranelift/filetests/src/test_peepmatic.rs deleted file mode 100644 index 6efe42e00e..0000000000 --- a/cranelift/filetests/src/test_peepmatic.rs +++ /dev/null @@ -1,55 +0,0 @@ -//! Test command for `peepmatic`-generated peephole optimizers. - -use crate::subtest::{run_filecheck, Context, SubTest}; -use cranelift_codegen; -use cranelift_codegen::ir::Function; -use cranelift_reader::TestCommand; -use std::borrow::Cow; - -struct TestPreopt; - -pub fn subtest(parsed: &TestCommand) -> anyhow::Result> { - assert_eq!(parsed.command, "peepmatic"); - if parsed.options.is_empty() { - Ok(Box::new(TestPreopt)) - } else { - anyhow::bail!("No options allowed on {}", parsed); - } -} - -impl SubTest for TestPreopt { - fn name(&self) -> &'static str { - "peepmatic" - } - - fn is_mutating(&self) -> bool { - true - } - - fn needs_isa(&self) -> bool { - true - } - - fn run(&self, func: Cow, context: &Context) -> anyhow::Result<()> { - let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned()); - let isa = context.isa.expect("preopt needs an ISA"); - - comp_ctx.compute_cfg(); - comp_ctx - .preopt(isa) - .map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, Into::into(e)))?; - let text = &comp_ctx.func.display().to_string(); - log::debug!("After peepmatic-based simple_preopt:\n{}", text); - - // Only actually run the filecheck if peepmatic is enabled, because it - // can generate slightly different code (alias a result vs replace an - // instruction) than the non-peepmatic versions of peephole - // optimizations. Note that the non-`peepmatic` results can be tested - // with the `test simple_preopt` subtest. - if cfg!(feature = "enable-peepmatic") { - run_filecheck(&text, context) - } else { - Ok(()) - } - } -} diff --git a/cranelift/filetests/src/test_simple_preopt.rs b/cranelift/filetests/src/test_simple_preopt.rs index 01fef79b82..9a591ef023 100644 --- a/cranelift/filetests/src/test_simple_preopt.rs +++ b/cranelift/filetests/src/test_simple_preopt.rs @@ -41,16 +41,6 @@ impl SubTest for TestSimplePreopt { .map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, e))?; let text = &comp_ctx.func.display().to_string(); log::debug!("After simple_preopt:\n{}", text); - - // Only actually run the filecheck if peepmatic is *not* enabled, - // because it can generate slightly different code (alias a result vs - // replace an instruction) than the non-peepmatic versions of peephole - // optimizations. Note that the `peepmatic`-based results can be tested - // with the `test peepmatic` subtest. - if cfg!(feature = "enable-peepmatic") { - Ok(()) - } else { - run_filecheck(&text, context) - } + run_filecheck(&text, context) } } diff --git a/cranelift/peepmatic/Cargo.toml b/cranelift/peepmatic/Cargo.toml deleted file mode 100644 index 74b4aee21b..0000000000 --- a/cranelift/peepmatic/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "peepmatic" -version = "0.78.0" -authors = ["Nick Fitzgerald "] -edition = "2018" -license = "Apache-2.0 WITH LLVM-exception" -description = "DSL and compiler for generating peephole optimizers" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -anyhow = "1.0.27" -peepmatic-automata = { version = "=0.78.0", path = "crates/automata", features = ["dot"] } -peepmatic-macro = { version = "=0.78.0", path = "crates/macro" } -peepmatic-runtime = { version = "=0.78.0", path = "crates/runtime", features = ["construct"] } -peepmatic-traits = { version = "=0.78.0", path = "crates/traits" } -serde = { version = "1.0.105", features = ["derive"] } -wast = "38.0.0" -z3 = { version = "0.7.1", features = ["static-link-z3"] } - -[dev-dependencies] -peepmatic-test-operator = { path = "crates/test-operator" } diff --git a/cranelift/peepmatic/LICENSE b/cranelift/peepmatic/LICENSE deleted file mode 100644 index f9d81955f4..0000000000 --- a/cranelift/peepmatic/LICENSE +++ /dev/null @@ -1,220 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - ---- LLVM Exceptions to the Apache 2.0 License ---- - -As an exception, if, as a result of your compiling your source code, portions -of this Software are embedded into an Object form of such source code, you -may redistribute such embedded portions in such Object form without complying -with the conditions of Sections 4(a), 4(b) and 4(d) of the License. - -In addition, if you combine or link compiled forms of this Software with -software that is licensed under the GPLv2 ("Combined Software") and if a -court of competent jurisdiction determines that the patent provision (Section -3), the indemnity provision (Section 9) or other Section of the License -conflicts with the conditions of the GPLv2, you may retroactively and -prospectively choose to deem waived or otherwise exclude such Section(s) of -the License, but only in their entirety and only with respect to the Combined -Software. - diff --git a/cranelift/peepmatic/README.md b/cranelift/peepmatic/README.md deleted file mode 100644 index 4938d35c77..0000000000 --- a/cranelift/peepmatic/README.md +++ /dev/null @@ -1,418 +0,0 @@ -
-

peepmatic

- -

- - peepmatic is a DSL and compiler for peephole optimizers for - Cranelift. - -

- - - -
- - - - - -- [About](#about) -- [Example](#example) -- [A DSL for Optimizations](#a-dsl-for-optimizations) - - [Variables](#variables) - - [Constants](#constants) - - [Nested Patterns](#nested-patterns) - - [Preconditions and Unquoting](#preconditions-and-unquoting) - - [Bit Widths](#bit-widths) -- [Implementation](#implementation) - - [Parsing](#parsing) - - [Type Checking](#type-checking) - - [Linearization](#linearization) - - [Automatization](#automatization) -- [References](#references) -- [Acknowledgments](#acknowledgments) - - - -## About - -Peepmatic is a DSL for peephole optimizations and compiler for generating -peephole optimizers from them. The user writes a set of optimizations in the -DSL, and then `peepmatic` compiles the set of optimizations into an efficient -peephole optimizer: - -``` -DSL ----peepmatic----> Peephole Optimizer -``` - -The generated peephole optimizer has all of its optimizations' left-hand sides -collapsed into a compact automata that makes matching candidate instruction -sequences fast. - -The DSL's optimizations may be written by hand or discovered mechanically with a -superoptimizer like [Souper][]. Eventually, `peepmatic` should have a verifier -that ensures that the DSL's optimizations are sound, similar to what [Alive][] -does for LLVM optimizations. - -[Cranelift]: https://github.com/bytecodealliance/wasmtime/tree/main/cranelift#readme -[Souper]: https://github.com/google/souper -[Alive]: https://github.com/AliveToolkit/alive2 - -## Example - -This snippet of our DSL describes optimizations for removing redundant -bitwise-or instructions that are no-ops: - -```lisp -(=> (bor $x (bor $x $y)) - (bor $x $y)) - -(=> (bor $y (bor $x $y)) - (bor $x $y)) - -(=> (bor (bor $x $y) $x) - (bor $x $y)) - -(=> (bor (bor $x $y) $y) - (bor $x $y)) -``` - -When compiled into a peephole optimizer automaton, they look like this: - -![](examples/redundant-bor.png) - -## A DSL for Optimizations - -A single peephole optimization has two parts: - -1. A **left-hand side** that describes candidate instruction sequences that the - optimization applies to. -2. A **right-hand side** that contains the new instruction sequence that - replaces old instruction sequences that the left-hand side matched. - -A left-hand side may bind sub-expressions to variables and the right-hand side -may contain those bound variables to reuse the sub-expressions. The operations -inside the left-hand and right-hand sides are a subset of clif operations. - -Let's take a look at an example: - -```lisp -(=> (imul $x 2) - (ishl $x 1)) -``` - -As you can see, the DSL uses S-expressions. (S-expressions are easy to parse and -we also have a bunch of nice parsing infrastructure for S-expressions already -for our [`wat`][wat] and [`wast`][wast] crates.) - -[wat]: https://crates.io/crates/wat -[wast]: https://crates.io/crates/wast - -The left-hand side of this optimization is `(imul $x 2)`. It matches integer -multiplication operations where a value is multiplied by the constant two. The -value multiplied by two is bound to the variable `$x`. - -The right-hand side of this optimization is `(ishl $x 1)`. It reuses the `$x` -variable that was bound in the left-hand side. - -This optimization replaces expressions of the form `x * 2` with `x << 1`. This -is sound because multiplication by two is the same as shifting left by one for -binary integers, and it is desirable because a shift-left instruction executes -in fewer cycles than a multiplication. - -The general form of an optimization is: - -```lisp -(=> ) -``` - -### Variables - -Variables begin with a dollar sign and are followed by lowercase letters, -numbers, hyphens, and underscores: `$x`, `$y`, `$my-var`, `$operand2`. - -Left-hand side patterns may contain variables that match any kind of -sub-expression and give it a name so that it may be reused in the right-hand -side. - -```lisp -;; Replace `x + 0` with simply `x`. -(=> (iadd $x 0) - $x) -``` - -Within a pattern, every occurrence of a variable with the same name must match -the same value. That is `(iadd $x $x)` matches `(iadd 1 1)` but does not match -`(iadd 1 2)`. This lets us write optimizations such as this: - -```lisp -;; Xor'ing a value with itself is always zero. -(=> (bxor $x $x) - (iconst 0)) -``` - -### Constants - -We've already seen specific integer literals and wildcard variables in patterns, -but we can also match any constant. These are written similar to variables, but -use uppercase letters rather than lowercase: `$C`, `$MY-CONST`, and `$OPERAND2`. - -For example, we can use constant patterns to combine an `iconst` and `iadd` into -a single `iadd_imm` instruction: - -```lisp -(=> (iadd (iconst $C) $x) - (iadd_imm $C $x)) -``` - -### Nested Patterns - -Patterns can also match nested operations with their own nesting: - -```lisp -(=> (bor $x (bor $x $y)) - (bor $x $y)) -``` - -### Preconditions and Unquoting - -Let's reconsider our first example optimization: - -```lisp -(=> (imul $x 2) - (ishl $x 1)) -``` - -This optimization is a little too specific. Here is another version of this -optimization that we'd like to support: - -```lisp -(=> (imul $x 4) - (ishl $x 2)) -``` - -We don't want to have to write out all instances of this general class of -optimizations! That would be a lot of repetition and could also bloat the size -of our generated peephole optimizer's matching automata. - -Instead, we can generalize this optimization by matching any multiplication by a -power of two constant `C` and replacing it with a shift left of `log2(C)`. - -First, rather than match `2` directly, we want to match any constant variable `C`: - -```lisp -(imul $x $C) -``` - -Note that variables begin with lowercase letters, while constants begin with -uppercase letters. Both the constant pattern `$C` and variable pattern `$x` will -match `5`, but only the variable pattern `$x` will match a whole sub-expression -like `(iadd 1 2)`. The constant pattern `$C` only matches constant values. - -Next, we augment our left-hand side's pattern with a **precondition** that the -constant `$C` must be a power of two. Preconditions are introduced by wrapping -a pattern in the `when` form: - -```lisp -;; Our new left-hand side, augmenting a pattern with a precondition! -(when - ;; The pattern matching multiplication by a constant value. - (imul $x $C) - - ;; The precondition that $C must be a power of two. - (is-power-of-two $C)) - ``` - -In the right-hand side, we use **unquoting** to perform compile-time evaluation -of `log2($C)`. Unquoting is done with the `$(...)` form: - -```lisp -;; Our new right-hand side, using unqouting to do compile-time evaluation of -;; constants that were matched and bound in the left-hand side! -(ishl $x $(log2 $C)) -``` - -Finally, here is the general optimization putting our new left-hand and -right-hand sides together: - -```lisp -(=> (when (imul $x $C) - (is-power-of-two $C)) - (ishl $x $(log2 $C))) -``` - -### Bit Widths - -Similar to how Cranelift's instructions are bit-width polymorphic, `peepmatic` -optimizations are also bit-width polymorphic. Unless otherwise specified, a -pattern will match expressions manipulating `i32`s just the same as expressions -manipulating `i64`s, etc... An optimization that doesn't constrain its pattern's -bit widths must be valid for all bit widths: - -* 1 -* 8 -* 16 -* 32 -* 64 -* 128 - -To constrain an optimization to only match `i32`s, for example, you can use the -`bit-width` precondition: - -```lisp -(=> (when (iadd $x $y) - (bit-width $x 32) - (bit-width $y 32)) - ...) -``` - -Alternatively, you can ascribe a type to an operation by putting the type inside -curly brackets after the operator, like this: - -```lisp -(=> (when (sextend{i64} (ireduce{i32} $x)) - (bit-width $x 64)) - (sshr (ishl $x 32) 32)) -``` - -## Implementation - -Peepmatic has roughly four phases: - -1. Parsing -2. Type Checking -3. Linearization -4. Automatization - -(I say "roughly" because there are a couple micro-passes that happen after -linearization and before automatization. But those are the four main phases.) - -### Parsing - -Parsing transforms the DSL source text into an abstract syntax tree (AST). - -We use [the `wast` crate][wast]. It gives us nicely formatted errors with source -context, as well as some other generally nice-to-have parsing infrastructure. - -Relevant source files: - -* `src/parser.rs` -* `src/ast.rs` - -[wast]: https://crates.io/crates/wast - -### Type Checking - -Type checking operates on the AST. It checks that types and bit widths in the -optimizations are all valid. For example, it ensures that the type and bit width -of an optimization's left-hand side is the same as its right-hand side, because -it doesn't make sense to replace an integer expression with a boolean -expression. - -After type checking is complete, certain AST nodes are assigned a type and bit -width, that are later used in linearization and when matching and applying -optimizations. - -We walk the AST and gather type constraints. Every constraint is associated with -a span in the source file. We hand these constraints off to Z3. In the case that -there are type errors (i.e. Z3 returns `unsat`), we get the constraints that are -in conflict with each other via `z3::Solver::get_unsat_core` and report the type -errors to the user, with the source context, thanks to the constraints' -associated spans. - -Using Z3 not only makes implementing type checking easier than it otherwise -would be, but makes it that much easier to extend type checking with searching -for counterexample inputs in the future. That is, inputs for which the RHS is -not equivalent to the LHS, implying that the optimization is unsound. - -Relevant source files: - -* `src/verify.rs` - -### Linearization - -Linearization takes the AST of optimizations and converts each optimization into -a linear form. The goal is to make automaton construction easier in the -automatization step, as well as simplifying the language to make matching and -applying optimizations easier. - -Each optimization's left-hand side is converted into a sequence of - -* match operation, -* path to the instruction/value/immediate to which the operation is applied, and -* expected result of the operation. - -All match operations must have the expected result for the optimization to be -applicable to an instruction sequence. - -Each optimization's right-hand side is converted into a sequence of build -actions. These are commands that describe how to construct the right-hand side, -given that the left-hand side has been matched. - -Relevant source files: - -* `src/linearize.rs` -* `src/linear_passes.rs` -* `crates/runtime/src/linear.rs` - -### Automatization - -Automatization takes a set of linear optimizations and combines them into a -transducer automaton. This automaton is the final, compiled peephole -optimizations. The goal is to de-duplicate as much as we can from all the linear -optimizations, producing as compact and cache-friendly a representation as we -can. - -Plain automata can tell you whether it matches an input string. It can be -thought of as a compact representation of a set of strings. A transducer is a -type of automaton that doesn't just match input strings, but can map them to -output values. It can be thought of as a compact representation of a dictionary -or map. By using transducers, we de-duplicate not only the prefix and suffix of -the match operations, but also the right-hand side build actions. - -Each state in the emitted transducers is associated with a match operation and -path. The transitions out of that state are over the result of the match -operation. Each transition optionally accumulates some RHS build actions. By the -time we reach a final state, the RHS build actions are complete and can be -interpreted to apply the matched optimization. - -The relevant source files for constructing the transducer automaton are: - -* `src/automatize.rs` -* `crates/automata/src/lib.rs` - -The relevant source files for the runtime that interprets the transducers and -applies optimizations are: - -* `crates/runtime/src/optimizations.rs` -* `crates/runtime/src/optimizer.rs` - -## References - -I found these resources helpful when designing `peepmatic`: - -* [Extending tree pattern matching for application to peephole - optimizations](https://pure.tue.nl/ws/portalfiles/portal/125543109/Thesis_JanekvOirschot.pdf) - by van Oirschot - -* [Interpreted Pattern Match Execution for - MLIR](https://drive.google.com/drive/folders/1hb_sXbdMbIz95X-aaa6Vf5wSYRwsJuve) - by Jeff Niu - -* [Direct Construction of Minimal Acyclic Subsequential - Transducers](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.24.3698&rep=rep1&type=pdf) - by Mihov and Maurel - -* [Index 1,600,000,000 Keys with Automata and - Rust](https://blog.burntsushi.net/transducers/) and [the `fst` - crate](https://crates.io/crates/fst) by Andrew Gallant - -## Acknowledgments - -Thanks to [Jubi Taneja], [Dan Gohman], [John Regehr], and [Nuno Lopes] for their -input in design discussions and for sharing helpful resources! - -[Jubi Taneja]: https://www.cs.utah.edu/~jubi/ -[Dan Gohman]: https://github.com/sunfishcode -[John Regehr]: https://www.cs.utah.edu/~regehr/ -[Nuno Lopes]: http://web.ist.utl.pt/nuno.lopes/ diff --git a/cranelift/peepmatic/crates/automata/Cargo.toml b/cranelift/peepmatic/crates/automata/Cargo.toml deleted file mode 100644 index ffd0341377..0000000000 --- a/cranelift/peepmatic/crates/automata/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "peepmatic-automata" -version = "0.78.0" -authors = ["Nick Fitzgerald "] -edition = "2018" -license = "Apache-2.0 WITH LLVM-exception" -description = "Finite-state transducer automata" - -# FIXME(rust-lang/cargo#9300): uncomment once that lands -# [package.metadata.docs.rs] -# all-features = true - -[dependencies] -serde = { version = "1.0.106", optional = true } - -[features] -# Enable support for generating GraphViz Dot files that can be used to visually -# render an automaton. -# -# https://en.wikipedia.org/wiki/DOT_%28graph_description_language%29 -dot = [] diff --git a/cranelift/peepmatic/crates/automata/LICENSE b/cranelift/peepmatic/crates/automata/LICENSE deleted file mode 100644 index f9d81955f4..0000000000 --- a/cranelift/peepmatic/crates/automata/LICENSE +++ /dev/null @@ -1,220 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - ---- LLVM Exceptions to the Apache 2.0 License ---- - -As an exception, if, as a result of your compiling your source code, portions -of this Software are embedded into an Object form of such source code, you -may redistribute such embedded portions in such Object form without complying -with the conditions of Sections 4(a), 4(b) and 4(d) of the License. - -In addition, if you combine or link compiled forms of this Software with -software that is licensed under the GPLv2 ("Combined Software") and if a -court of competent jurisdiction determines that the patent provision (Section -3), the indemnity provision (Section 9) or other Section of the License -conflicts with the conditions of the GPLv2, you may retroactively and -prospectively choose to deem waived or otherwise exclude such Section(s) of -the License, but only in their entirety and only with respect to the Combined -Software. - diff --git a/cranelift/peepmatic/crates/automata/src/dot.rs b/cranelift/peepmatic/crates/automata/src/dot.rs deleted file mode 100644 index bc102d1f81..0000000000 --- a/cranelift/peepmatic/crates/automata/src/dot.rs +++ /dev/null @@ -1,273 +0,0 @@ -//! Helpers for generating [GraphViz -//! Dot](https://graphviz.gitlab.io/_pages/pdf/dotguide.pdf) files to visually -//! render automata. -//! -//! **This module only exists when the `"dot"` cargo feature is enabled.** - -use crate::{Automaton, Output, State}; -use std::fmt::{Debug, Display}; -use std::fs; -use std::hash::Hash; -use std::io::{self, Write}; -use std::path::Path; - -/// Format the user-provided bits of an `Automaton` for Graphviz Dot output. -/// -/// There are two provided implementations of `DotFmt`: -/// -/// * [`DebugDotFmt`][crate::dot::DebugDotFmt] -- format each type parameter -/// with its `std::fmt::Debug` implementation. -/// -/// * [`DisplayDotFmt`][crate::dot::DisplayDotFmt] -- format each type parameter -/// with its `std::fmt::Display` implementation. -/// -/// You can also implement this trait yourself if your type parameters don't -/// implement `Debug` or `Display`, or if you want to format them in some other -/// way. -pub trait DotFmt { - /// Format a transition edge: `from ---input---> to`. - /// - /// This will be inside an [HTML - /// label](https://www.graphviz.org/doc/info/shapes.html#html), so you may - /// use balanced HTML tags. - fn fmt_transition( - &self, - w: &mut impl Write, - from: Option<&TState>, - input: &TAlphabet, - to: Option<&TState>, - ) -> io::Result<()>; - - /// Format the custom data associated with a state. - /// - /// This will be inside an [HTML - /// label](https://www.graphviz.org/doc/info/shapes.html#html), so you may - /// use balanced HTML tags. - fn fmt_state(&self, w: &mut impl Write, state: &TState) -> io::Result<()>; - - /// Format a transition's output or the final output of a final state. - /// - /// This will be inside an [HTML - /// label](https://www.graphviz.org/doc/info/shapes.html#html), so you may - /// use balanced HTML tags. - fn fmt_output(&self, w: &mut impl Write, output: &TOutput) -> io::Result<()>; -} - -impl Automaton -where - TAlphabet: Clone + Eq + Hash + Ord, - TState: Clone + Eq + Hash, - TOutput: Output, -{ - /// Write this `Automaton` out as a [GraphViz - /// Dot](https://graphviz.gitlab.io/_pages/pdf/dotguide.pdf) file at the - /// given path. - /// - /// The `formatter` parameter controls how `TAlphabet`, `TState`, and - /// `TOutput` are rendered. See the [`DotFmt`][crate::dot::DotFmt] trait for - /// details. - /// - /// **This method only exists when the `"dot"` cargo feature is enabled.** - pub fn write_dot_file( - &self, - formatter: &impl DotFmt, - path: impl AsRef, - ) -> io::Result<()> { - let mut file = fs::File::create(path)?; - self.write_dot(formatter, &mut file)?; - Ok(()) - } - - /// Write this `Automaton` out to the given write-able as a [GraphViz - /// Dot](https://graphviz.gitlab.io/_pages/pdf/dotguide.pdf) file. - /// - /// The `formatter` parameter controls how `TAlphabet`, `TState`, and - /// `TOutput` are rendered. See the [`DotFmt`][crate::dot::DotFmt] trait for - /// details. - /// - /// **This method only exists when the `"dot"` cargo feature is enabled.** - pub fn write_dot( - &self, - formatter: &impl DotFmt, - w: &mut impl Write, - ) -> io::Result<()> { - writeln!(w, "digraph {{")?; - writeln!(w, " rankdir = \"LR\";")?; - writeln!(w, " nodesep = 2;")?; - - // Fake state for the incoming arrow to the start state. - writeln!(w, " \"\" [shape = none];")?; - - // Each state, its associated custom data, and its final output. - for (i, state_data) in self.state_data.iter().enumerate() { - write!( - w, - r#" state_{i} [shape = {shape}, label = <")?; - if let Some(final_output) = self.final_states.get(&State(i as u32)) { - write!(w, r#"")?; - } - writeln!(w, "
{i}
"#, - i = i, - shape = if self.final_states.contains_key(&State(i as u32)) { - "doublecircle" - } else { - "circle" - } - )?; - if let Some(state_data) = state_data { - formatter.fmt_state(w, state_data)?; - } else { - write!(w, "(no state data)")?; - } - write!(w, "
"#)?; - formatter.fmt_output(w, final_output)?; - write!(w, "
>];")?; - } - - // Fake transition to the start state. - writeln!(w, r#" "" -> state_{};"#, self.start_state.0)?; - - // Transitions between states and their outputs. - for (from, transitions) in self.transitions.iter().enumerate() { - for (input, (to, output)) in transitions { - write!( - w, - r#" state_{from} -> state_{to} [label = <
Input:"#, - from = from, - to = to.0, - )?; - formatter.fmt_transition( - w, - self.state_data[from].as_ref(), - input, - self.state_data[to.0 as usize].as_ref(), - )?; - write!( - w, - r#"
Output:"#, - )?; - formatter.fmt_output(w, output)?; - writeln!(w, "
>];")?; - } - } - - writeln!(w, "}}")?; - Ok(()) - } -} - -/// Format an `Automaton`'s `TAlphabet`, `TState`, and `TOutput` with their -/// `std::fmt::Debug` implementations. -#[derive(Debug)] -pub struct DebugDotFmt; - -impl DotFmt for DebugDotFmt -where - TAlphabet: Debug, - TState: Debug, - TOutput: Debug, -{ - fn fmt_transition( - &self, - w: &mut impl Write, - _from: Option<&TState>, - input: &TAlphabet, - _to: Option<&TState>, - ) -> io::Result<()> { - write!(w, r#"{:?}"#, input) - } - - fn fmt_state(&self, w: &mut impl Write, state: &TState) -> io::Result<()> { - write!(w, r#"{:?}"#, state) - } - - fn fmt_output(&self, w: &mut impl Write, output: &TOutput) -> io::Result<()> { - write!(w, r#"{:?}"#, output) - } -} - -/// Format an `Automaton`'s `TAlphabet`, `TState`, and `TOutput` with their -/// `std::fmt::Display` implementations. -#[derive(Debug)] -pub struct DisplayDotFmt; - -impl DotFmt for DisplayDotFmt -where - TAlphabet: Display, - TState: Display, - TOutput: Display, -{ - fn fmt_transition( - &self, - w: &mut impl Write, - _from: Option<&TState>, - input: &TAlphabet, - _to: Option<&TState>, - ) -> io::Result<()> { - write!(w, "{}", input) - } - - fn fmt_state(&self, w: &mut impl Write, state: &TState) -> io::Result<()> { - write!(w, "{}", state) - } - - fn fmt_output(&self, w: &mut impl Write, output: &TOutput) -> io::Result<()> { - write!(w, "{}", output) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::Builder; - - #[test] - fn test_write_dot() { - let mut builder = Builder::::new(); - - // Insert "mon" -> 1 - let mut insertion = builder.insert(); - insertion.next('m', 1).next('o', 0).next('n', 0); - insertion.finish(); - - // Insert "sat" -> 6 - let mut insertion = builder.insert(); - insertion.next('s', 6).next('a', 0).next('t', 0); - insertion.finish(); - - // Insert "sun" -> 0 - let mut insertion = builder.insert(); - insertion.next('s', 0).next('u', 0).next('n', 0); - insertion.finish(); - - let automata = builder.finish(); - - let expected = r#" -digraph { - rankdir = "LR"; - nodesep = 2; - "" [shape = none]; - state_0 [shape = doublecircle, label = <
0
(no state data)
0
>]; - state_1 [shape = circle, label = <
1
(no state data)
>]; - state_2 [shape = circle, label = <
2
(no state data)
>]; - state_3 [shape = circle, label = <
3
(no state data)
>]; - state_4 [shape = circle, label = <
4
(no state data)
>]; - state_5 [shape = circle, label = <
5
(no state data)
>]; - "" -> state_5; - state_1 -> state_0 [label = <
Input:'n'
Output:0
>]; - state_2 -> state_1 [label = <
Input:'o'
Output:0
>]; - state_3 -> state_0 [label = <
Input:'t'
Output:0
>]; - state_4 -> state_3 [label = <
Input:'a'
Output:6
>]; - state_4 -> state_1 [label = <
Input:'u'
Output:0
>]; - state_5 -> state_2 [label = <
Input:'m'
Output:1
>]; - state_5 -> state_4 [label = <
Input:'s'
Output:0
>]; -} -"#; - - let mut buf = vec![]; - automata.write_dot(&DebugDotFmt, &mut buf).unwrap(); - let actual = String::from_utf8(buf).unwrap(); - eprintln!("{}", actual); - assert_eq!(expected.trim(), actual.trim()); - } -} diff --git a/cranelift/peepmatic/crates/automata/src/lib.rs b/cranelift/peepmatic/crates/automata/src/lib.rs deleted file mode 100644 index 6ed09a66e3..0000000000 --- a/cranelift/peepmatic/crates/automata/src/lib.rs +++ /dev/null @@ -1,1024 +0,0 @@ -//! Finite-state transducer automata. -//! -//! A transducer is a type of automata that has not only an input that it -//! accepts or rejects, but also an output. While regular automata check whether -//! an input string is in the set that the automata accepts, a transducer maps -//! the input strings to values. A regular automata is sort of a compressed, -//! immutable set, and a transducer is sort of a compressed, immutable key-value -//! dictionary. A [trie] compresses a set of strings or map from a string to a -//! value by sharing prefixes of the input string. Automata and transducers can -//! compress even better: they can share both prefixes and suffixes. [*Index -//! 1,600,000,000 Keys with Automata and Rust* by Andrew Gallant (aka -//! burntsushi)][burntsushi-blog-post] is a top-notch introduction. -//! -//! If you're looking for a general-purpose transducers crate in Rust you're -//! probably looking for [the `fst` crate][fst-crate]. While this implementation -//! is fully generic and has no dependencies, its feature set is specific to -//! `peepmatic`'s needs: -//! -//! * We need to associate extra data with each state: the match operation to -//! evaluate next. -//! -//! * We can't provide the full input string up front, so this crate must -//! support incremental lookups. This is because the peephole optimizer is -//! computing the input string incrementally and dynamically: it looks at the -//! current state's match operation, evaluates it, and then uses the result as -//! the next character of the input string. -//! -//! * We also support incremental insertion and output when building the -//! transducer. This is necessary because we don't want to emit output values -//! that bind a match on an optimization's left-hand side's pattern (for -//! example) until after we've succeeded in matching it, which might not -//! happen until we've reached the n^th state. -//! -//! * We need to support generic output values. The `fst` crate only supports -//! `u64` outputs, while we need to build up an optimization's right-hand side -//! instructions. -//! -//! This implementation is based on [*Direct Construction of Minimal Acyclic -//! Subsequential Transducers* by Mihov and Maurel][paper]. That means that keys -//! must be inserted in lexicographic order during construction. -//! -//! [trie]: https://en.wikipedia.org/wiki/Trie -//! [burntsushi-blog-post]: https://blog.burntsushi.net/transducers/#ordered-maps -//! [fst-crate]: https://crates.io/crates/fst -//! [paper]: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.24.3698&rep=rep1&type=pdf - -#![deny(missing_debug_implementations)] -#![deny(missing_docs)] - -mod output_impls; - -#[cfg(feature = "serde")] -mod serde_impls; - -#[cfg(feature = "dot")] -pub mod dot; - -use std::collections::{BTreeMap, HashMap, HashSet}; -use std::convert::TryInto; -use std::hash::Hash; -use std::iter; -use std::mem; - -/// An output type for a transducer automata. -/// -/// Not every type can be the output of a transducer. For correctness (not -/// memory safety) each type that implements this trait must satisfy the -/// following laws: -/// -/// 1. `concat(empty(), x) == x` -- concatenating something with the empty -/// instance produces that same something. -/// -/// 2. `prefix(a, b) == prefix(b, a)` -- taking the prefix of two instances is -/// commutative. -/// -/// 3. `prefix(empty(), x) == empty()` -- the prefix of any value and the empty -/// instance is the empty instance. -/// -/// 4. `difference(concat(a, b), a) == b` -- concatenating a prefix value and -/// then removing it is the identity function. -/// -/// ## Example -/// -/// Here is an example implementation for unsigned integers: -/// -/// ``` -/// use peepmatic_automata::Output; -/// -/// #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -/// struct MyInt(u64); -/// -/// impl Output for MyInt { -/// // The empty value is zero. -/// fn empty() -> Self { -/// MyInt(0) -/// } -/// -/// // The prefix of two values is their min. -/// fn prefix(a: &MyInt, b: &MyInt) -> Self { -/// std::cmp::min(*a, *b) -/// } -/// -/// // The difference is subtraction. -/// fn difference(a: &MyInt, b: &MyInt) -> Self { -/// MyInt(a.0 - b.0) -/// } -/// -/// // Concatenation is addition. -/// fn concat(a: &MyInt, b: &MyInt) -> Self { -/// MyInt(a.0 + b.0) -/// } -/// } -/// -/// // Law 1 -/// assert_eq!( -/// MyInt::concat(&MyInt::empty(), &MyInt(5)), -/// MyInt(5), -/// ); -/// -/// // Law 2 -/// assert_eq!( -/// MyInt::prefix(&MyInt(3), &MyInt(5)), -/// MyInt::prefix(&MyInt(5), &MyInt(3)) -/// ); -/// -/// // Law 3 -/// assert_eq!( -/// MyInt::prefix(&MyInt::empty(), &MyInt(5)), -/// MyInt::empty() -/// ); -/// -/// // Law 4 -/// assert_eq!( -/// MyInt::difference(&MyInt::concat(&MyInt(2), &MyInt(3)), &MyInt(2)), -/// MyInt(3), -/// ); -/// ``` -pub trait Output: Sized + Eq + Hash + Clone { - /// Construct the empty instance. - fn empty() -> Self; - - /// Is this the empty instance? - /// - /// The default implementation constructs the empty instance and then checks - /// if `self` is equal to it. Override this default if you can provide a - /// better implementation. - fn is_empty(&self) -> bool { - *self == Self::empty() - } - - /// Get the shared prefix of two instances. - /// - /// This must be commutative. - fn prefix(a: &Self, b: &Self) -> Self; - - /// When `b` is a prefix of `a`, get the remaining suffix of `a` that is not - /// shared with `b`. - fn difference(a: &Self, b: &Self) -> Self; - - /// Concatenate `a` and `b`. - fn concat(a: &Self, b: &Self) -> Self; -} - -/// A builder for a transducer automata. -/// -/// ## Type Parameters -/// -/// Generic over the following parameters: -/// -/// * `TAlphabet` -- the alphabet of the input strings. If your input keys are -/// `String`s, this would be `char`. If your input keys are arbitrary byte -/// strings, this would be `u8`. -/// -/// * `TState` -- extra, custom data associated with each state. This isn't used -/// by the automata itself, but you can use it to annotate states with extra -/// information for your own purposes. -/// -/// * `TOutput` -- the output type. See [the `Output` trait][crate::Output] for -/// the requirements that any output type must fulfill. -/// -/// ## Insertions -/// -/// Insertions *must* happen in lexicographic order. Failure to do this, or -/// inserting duplicates, will trigger panics. -/// -/// ## Example -/// -/// ``` -/// use peepmatic_automata::Builder; -/// -/// let mut builder = Builder::::new(); -/// -/// // Insert "mon" -> 1 -/// let mut insertion = builder.insert(); -/// insertion -/// .next(b'm', 1) -/// .next(b'o', 0) -/// .next(b'n', 0); -/// insertion.finish(); -/// -/// // Insert "sat" -> 6 -/// let mut insertion = builder.insert(); -/// insertion -/// .next(b's', 6) -/// .next(b'a', 0) -/// .next(b't', 0); -/// insertion.finish(); -/// -/// // Insert "sun" -> 0 -/// let mut insertion = builder.insert(); -/// insertion -/// .next(b's', 0) -/// .next(b'u', 0) -/// .next(b'n', 0); -/// insertion.finish(); -/// -/// let automata = builder.finish(); -/// -/// assert_eq!(automata.get(b"sun"), Some(0)); -/// assert_eq!(automata.get(b"mon"), Some(1)); -/// assert_eq!(automata.get(b"sat"), Some(6)); -/// -/// assert!(automata.get(b"tues").is_none()); -/// ``` -#[derive(Debug, Clone)] -pub struct Builder -where - TAlphabet: Clone + Eq + Hash + Ord, - TState: Clone + Eq + Hash, - TOutput: Output, -{ - inner: Option>, -} - -impl Builder -where - TAlphabet: Clone + Eq + Hash + Ord, - TState: Clone + Eq + Hash, - TOutput: Output, -{ - /// Make a new builder to start constructing a new transducer automata. - pub fn new() -> Self { - let mut inner = BuilderInner { - frozen: vec![], - wip: BTreeMap::new(), - wip_state_id_counter: 0, - unfinished: vec![], - already_frozen: HashMap::new(), - last_insertion_finished: true, - }; - - // Create the start state. - let id = inner.new_wip_state(); - inner.unfinished.push(id); - - Builder { inner: Some(inner) } - } - - fn inner(&mut self) -> &mut BuilderInner { - self.inner - .as_mut() - .expect("cannot use `Builder` anymore after calling `finish` on it") - } - - /// Start building a new key/value insertion. - /// - /// Insertions are built up incrementally, and a full entry is created from - /// a series of `TAlphabet` and `TOutput` pairs passed to - /// [`InsertionBuilder::next`][crate::InsertionBuilder::next]. - /// - /// ## Panics - /// - /// Panics if [`finish`][crate::InsertionBuilder::finish] was not called on - /// the last `InsertionBuilder` returned from this method. - pub fn insert(&mut self) -> InsertionBuilder { - let inner = self.inner(); - assert!( - inner.last_insertion_finished, - "did not call `finish` on the last `InsertionBuilder`" - ); - inner.last_insertion_finished = false; - InsertionBuilder { - inner: inner, - index: 0, - output: TOutput::empty(), - } - } - - /// Finish building this transducer and return the constructed `Automaton`. - /// - /// ## Panics - /// - /// Panics if this builder is empty, and has never had anything inserted - /// into it. - /// - /// Panics if the last insertion's - /// [`InsertionBuilder`][crate::InsertionBuilder] did not call its - /// [finish][crate::InsertionBuilder::finish] method. - pub fn finish(&mut self) -> Automaton { - let mut inner = self - .inner - .take() - .expect("cannot use `Builder` anymore after calling `finish` on it"); - assert!(inner.last_insertion_finished); - - let wip_start = inner.unfinished[0]; - - // Freeze everything! We're done! - let wip_to_frozen = inner.freeze_from(0); - assert!(inner.wip.is_empty()); - assert!(inner.unfinished.is_empty()); - - // Now transpose our states and transitions into our packed, - // struct-of-arrays representation that we use inside `Automaton`. - let FrozenStateId(s) = wip_to_frozen[&wip_start]; - let start_state = State(s); - let mut state_data = vec![None; inner.frozen.len()]; - let mut transitions = (0..inner.frozen.len()) - .map(|_| BTreeMap::new()) - .collect::>(); - let mut final_states = BTreeMap::new(); - - assert!((inner.frozen.len() as u64) < (std::u32::MAX as u64)); - for (i, state) in inner.frozen.into_iter().enumerate() { - assert!(state_data[i].is_none()); - assert!(transitions[i].is_empty()); - - state_data[i] = state.state_data; - - for (input, (FrozenStateId(to_state), output)) in state.transitions { - assert!((to_state as usize) < transitions.len()); - transitions[i].insert(input, (State(to_state), output)); - } - - if state.is_final { - final_states.insert(State(i as u32), state.final_output); - } else { - assert!(state.final_output.is_empty()); - } - } - - let automata = Automaton { - state_data, - transitions, - final_states, - start_state, - }; - - #[cfg(debug_assertions)] - { - if let Err(msg) = automata.check_representation() { - panic!("Automaton::check_representation failed: {}", msg); - } - } - - automata - } -} - -/// A state in an automaton. -/// -/// Only use a `State` with the automaton that it came from! Mixing and matching -/// states between automata will result in bogus results and/or panics! -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)] -pub struct State(u32); - -#[derive(Clone, Debug)] -struct BuilderInner -where - TAlphabet: Clone + Eq + Hash + Ord, - TState: Clone + Eq + Hash, - TOutput: Output, -{ - // The `i`th entry maps `FrozenStateId(i)` to its state. - frozen: Vec>, - - // Our mutable, work-in-progress states. - wip: BTreeMap>, - - // A counter for WIP state ids. - wip_state_id_counter: u32, - - // A stack of our work-in-progress states. - unfinished: Vec, - - // A map from `WipState`s that we've already frozen to their canonical, - // de-duplicated frozen state. This is used for hash-consing frozen states - // so that we share suffixes in the automata. - already_frozen: HashMap, FrozenStateId>, - - // The the last `InsertionBuilder` have its `finish` method invoked? - last_insertion_finished: bool, -} - -impl BuilderInner -where - TAlphabet: Clone + Eq + Hash + Ord, - TState: Clone + Eq + Hash, - TOutput: Output, -{ - fn new_wip_state(&mut self) -> WipStateId { - let id = WipStateId(self.wip_state_id_counter); - self.wip_state_id_counter += 1; - let old = self.wip.insert( - id, - WipState { - state_data: None, - transitions: BTreeMap::new(), - is_final: false, - final_output: TOutput::empty(), - }, - ); - debug_assert!(old.is_none()); - id - } - - fn freeze_from(&mut self, index: usize) -> BTreeMap { - assert!(index <= self.unfinished.len()); - - let mut wip_to_frozen = BTreeMap::new(); - - if index == self.unfinished.len() { - // Nothing to freeze. - return wip_to_frozen; - } - - // Freeze `self.inner.unfinished[self.index + 1..]` from the end - // back. We're essentially hash-consing each state. - for _ in (index..self.unfinished.len()).rev() { - let wip_id = self.unfinished.pop().unwrap(); - let mut wip = self.wip.remove(&wip_id).unwrap(); - - // Update transitions to any state we just froze in an earlier - // iteration of this loop. - wip.update_transitions(&wip_to_frozen); - - // Get or create the canonical frozen state for this WIP state. - // - // Note: we're not using the entry API here because this way we can - // avoid cloning `wip`, which would be more costly than the double - // lookup we're doing instead. - let frozen_id = if let Some(id) = self.already_frozen.get(&wip) { - *id - } else { - let id = FrozenStateId(self.frozen.len().try_into().unwrap()); - self.frozen.push(FrozenState { - state_data: wip.state_data.clone(), - transitions: wip - .transitions - .clone() - .into_iter() - .map(|(input, (id, output))| { - let id = match id { - WipOrFrozenStateId::Frozen(id) => id, - WipOrFrozenStateId::Wip(_) => panic!( - "when we are freezing a WIP state, it should never have \ - any transitions to another WIP state" - ), - }; - (input, (id, output)) - }) - .collect(), - is_final: wip.is_final, - final_output: wip.final_output.clone(), - }); - self.already_frozen.insert(wip, id); - id - }; - - // Record the id for this newly frozen state, so that other states - // which referenced it when it wasn't frozen can reference it as a - // frozen state. - wip_to_frozen.insert(wip_id, frozen_id); - } - - // Update references to newly frozen states from the rest of the - // unfinished stack that we didn't freeze. - for wip_id in &self.unfinished { - self.wip - .get_mut(wip_id) - .unwrap() - .update_transitions(&wip_to_frozen); - } - - wip_to_frozen - } -} - -/// A builder for a new entry in a transducer automata. -#[derive(Debug)] -pub struct InsertionBuilder<'a, TAlphabet, TState, TOutput> -where - TAlphabet: Clone + Eq + Hash + Ord, - TState: Clone + Eq + Hash, - TOutput: Output, -{ - inner: &'a mut BuilderInner, - - // The index within `inner.unfinished` where we will transition out of next. - index: usize, - - // Any leftover output from the last transition that we need to roll over - // into the next transition. - output: TOutput, -} - -impl<'a, TAlphabet, TState, TOutput> InsertionBuilder<'a, TAlphabet, TState, TOutput> -where - TAlphabet: Clone + Eq + Hash + Ord, - TState: Clone + Eq + Hash, - TOutput: Output, -{ - /// Insert the next character of input for this entry, and the associated - /// output that should be emitted along with it. - /// - /// In general, you want to add all of your output on the very first `next` - /// call, and use [`Output::empty()`][crate::Output::empty] for all the - /// rest. This enables the most tail-sharing of suffixes, which leads to the - /// most compact automatas. - /// - /// However, there are times when you *cannot* emit output yet, as it - /// depends on having moved throught he automata further. For example, with - /// `peepmatic` we cannot bind something from an optimization's left-hand - /// side's pattern until after we know it exists, which only happens after - /// we've moved some distance through the automata. - pub fn next(&mut self, input: TAlphabet, output: TOutput) -> &mut Self { - assert!(self.index < self.inner.unfinished.len()); - - if output.is_empty() { - // Leave `self.output` as it is. - } else if self.output.is_empty() { - self.output = output; - } else { - self.output = TOutput::concat(&self.output, &output); - } - - let wip_id = self.inner.unfinished[self.index]; - let wip = self.inner.wip.get_mut(&wip_id).unwrap(); - - match wip.transitions.get_mut(&input) { - Some((WipOrFrozenStateId::Frozen(_), _)) => { - panic!("out of order insertion: wip->frozen edge in shared prefix") - } - - // We're still in a shared prefix with the last insertion. That - // means that the state we are transitioning to must be the next - // state in `unfinished`. All we have to do is make sure the - // transition's output is the common prefix of the this insertion - // and the last, and push any excess suffix output out to other - // transition edges. - Some((WipOrFrozenStateId::Wip(next_id), out)) => { - let next_id = *next_id; - assert_eq!(next_id, self.inner.unfinished[self.index + 1]); - - // Find the common prefix of `out` and `self.output`. - let prefix = TOutput::prefix(&self.output, out); - - // Carry over this key's suffix for the next input's transition. - self.output = TOutput::difference(&self.output, &prefix); - - let rest = TOutput::difference(out, &prefix); - *out = prefix; - - let next_wip = self.inner.wip.get_mut(&next_id).unwrap(); - - // Push the leftover suffix of `out` along its other - // transitions. As a small optimization, only iterate over the - // edges if there is a non-empty value to push out along them. - if !rest.is_empty() { - if next_wip.is_final { - next_wip.final_output = TOutput::concat(&rest, &next_wip.final_output); - } - for (_input, (_state, output)) in &mut next_wip.transitions { - *output = TOutput::concat(&rest, output); - } - } - } - - // We've diverged from the shared prefix with the last - // insertion. Freeze the last insertion's unshared suffix and create - // a new WIP state for us to transition into. - None => { - self.inner.freeze_from(self.index + 1); - - let output = mem::replace(&mut self.output, TOutput::empty()); - - let new_id = self.inner.new_wip_state(); - self.inner.unfinished.push(new_id); - self.inner - .wip - .get_mut(&wip_id) - .unwrap() - .transitions - .insert(input, (WipOrFrozenStateId::Wip(new_id), output)); - } - } - - self.index += 1; - assert!(self.index < self.inner.unfinished.len()); - - self - } - - /// Finish this insertion. - /// - /// Failure to call this method before this `InsertionBuilder` is dropped - /// means that the insertion is *not* committed in the builder, and future - /// calls to [`InsertionBuilder::next`][crate::InsertionBuilder::next] will - /// panic! - pub fn finish(self) { - assert!(!self.inner.unfinished.is_empty()); - assert_eq!( - self.index, - self.inner.unfinished.len() - 1, - "out of order insertion" - ); - - let wip_id = *self.inner.unfinished.last().unwrap(); - let wip = self.inner.wip.get_mut(&wip_id).unwrap(); - wip.is_final = true; - wip.final_output = self.output; - - self.inner.last_insertion_finished = true; - } - - /// Set the optional, custom data for the current state. - /// - /// If you assign different state data to two otherwise-identical states - /// within the same shared *prefix* during insertion, it is implementation - /// defined which state and custom state data is kept. - /// - /// For *suffixes*, assigning different state data to two - /// otehrwise-identical states will result in the duplication of those - /// states: they won't get de-duplicated. - pub fn set_state_data(&mut self, data: TState) -> &mut Self { - assert!(self.index < self.inner.unfinished.len()); - let id = self.inner.unfinished[self.index]; - self.inner.wip.get_mut(&id).unwrap().state_data = Some(data); - self - } - - /// Get the current state's optional, custom data, if any. - /// - /// For shared prefixes, this may return state data that was assigned to an - /// equivalent state that was added earlier in the build process. - pub fn get_state_data(&self) -> Option<&TState> { - let id = self.inner.unfinished[self.index]; - self.inner.wip.get(&id).unwrap().state_data.as_ref() - } -} - -/// The id of an immutable, frozen state. -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -struct FrozenStateId(u32); - -/// The id of a mutable, work-in-progress state. -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -struct WipStateId(u32); - -/// The id of either a frozen or a WIP state. -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -enum WipOrFrozenStateId { - Wip(WipStateId), - Frozen(FrozenStateId), -} - -/// A frozen, immutable state inside a `Builder`. -/// -/// These states are from earlier in the lexicographic sorting on input keys, -/// and have already been processed. -#[derive(Clone, Debug, Hash)] -struct FrozenState -where - TAlphabet: Clone + Eq + Hash + Ord, - TState: Clone + Eq + Hash, - TOutput: Output, -{ - state_data: Option, - transitions: BTreeMap, - is_final: bool, - final_output: TOutput, -} - -/// A mutable, work-in-progress state inside a `Builder`. -/// -/// These states only exist for the last-inserted and currently-being-inserted -/// input keys. As soon as we find the end of their shared prefix, the last -/// key's unshared suffix is frozen, and then only the currently-being-inserted -/// input key has associated WIP states. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -struct WipState -where - TAlphabet: Clone + Eq + Hash + Ord, - TState: Clone + Eq + Hash, - TOutput: Output, -{ - state_data: Option, - transitions: BTreeMap, - is_final: bool, - final_output: TOutput, -} - -impl WipState -where - TAlphabet: Clone + Eq + Hash + Ord, - TState: Clone + Eq + Hash, - TOutput: Output, -{ - /// Given that we froze some old, WIP state, update any transitions out of - /// this WIP state so they point to the new, frozen state. - fn update_transitions(&mut self, wip_to_frozen: &BTreeMap) { - for (to, _) in self.transitions.values_mut() { - if let WipOrFrozenStateId::Wip(w) = *to { - if let Some(f) = wip_to_frozen.get(&w) { - *to = WipOrFrozenStateId::Frozen(*f); - } - } - } - } -} - -/// A finite-state transducer automata. -/// -/// These are constructed via [`Builder`][crate::Builder]. -/// -/// An `Automaton` is immutable: new entries cannot be inserted and existing -/// entries cannot be removed. -/// -/// To query an `Automaton`, there are two APIs: -/// -/// 1. [`get`][crate::Automaton::get] -- a high-level method to get the associated -/// output value of a full input sequence. -/// -/// 2. [`query`][crate::Automaton::query] -- a low-level method to -/// incrementally query the automata. It does not require that you have the -/// full input sequence on hand all at once, only the next character. It also -/// allows you to process the output as it it built up, rather than only at -/// giving you the final, complete output value. -#[derive(Debug, Clone)] -pub struct Automaton -where - TAlphabet: Clone + Eq + Hash + Ord, - TState: Clone + Eq + Hash, - TOutput: Output, -{ - // The `i`th entry is `State(i)`'s associated custom data. - state_data: Vec>, - - // The `i`th entry contains `State(i)`'s transitions. - transitions: Vec>, - - // Keeps track of which states are final, and if so, what their final output - // is. - final_states: BTreeMap, - - // The starting state. - start_state: State, -} - -impl Automaton -where - TAlphabet: Clone + Eq + Hash + Ord, - TState: Clone + Eq + Hash, - TOutput: Output, -{ - /// Get the output value associated with the given input sequence. - /// - /// Returns `None` if the input sequence is not a member of this - /// `Automaton`'s keys. Otherwise, returns `Some(output)`. - pub fn get<'a>(&self, input: impl IntoIterator) -> Option - where - TAlphabet: 'a, - { - let mut query = self.query(); - let mut output = TOutput::empty(); - - for inp in input { - let this_out = query.next(inp)?; - output = TOutput::concat(&output, &this_out); - } - - let final_output = query.finish()?; - Some(TOutput::concat(&output, final_output)) - } - - /// Create a low-level query. - /// - /// This allows you to incrementally query this `Automaton`, without - /// providing the full input sequence ahead of time, and also incrementally - /// build up the output. - /// - /// See [`Query`][crate::Query] for details. - pub fn query(&self) -> Query { - Query { - automata: self, - current_state: self.start_state, - } - } - - /// Check that the internal representaton is OK. - /// - /// Checks that we don't have any transitions to unknown states, that there - /// aren't any cycles, that ever path through the automata eventually ends - /// in a final state, etc. - /// - /// This property is `debug_assert!`ed in `Builder::finish`, and checked - /// when deserializing an `Automaton`. - /// - /// Returns `true` if the representation is okay, `false` otherwise. - fn check_representation(&self) -> Result<(), &'static str> { - macro_rules! bail_if { - ($condition:expr, $msg:expr) => { - if $condition { - return Err($msg); - } - }; - } - - bail_if!( - self.state_data.len() != self.transitions.len(), - "different number of states and transition sets" - ); - bail_if!( - self.final_states.is_empty(), - "the set of final states is empty" - ); - - bail_if!( - (self.start_state.0 as usize) >= self.transitions.len(), - "the start state is not a valid state" - ); - - for (f, _out) in &self.final_states { - bail_if!( - (f.0 as usize) >= self.transitions.len(), - "one of the final states is not a valid state" - ); - } - - // Walk the state transition graph and ensure that - // - // 1. there are no cycles, and - // - // 2. every path ends in a final state. - let mut on_stack = HashSet::new(); - let mut stack = vec![ - (Traversal::Stop, self.start_state), - (Traversal::Start, self.start_state), - ]; - loop { - match stack.pop() { - None => break, - Some((Traversal::Start, state)) => { - let is_new = on_stack.insert(state); - debug_assert!(is_new); - - let mut has_any_transitions = false; - for (_input, (to_state, _output)) in &self.transitions[state.0 as usize] { - has_any_transitions = true; - - // A transition to a state that we walked through to get - // here means that there is a cycle. - bail_if!( - on_stack.contains(to_state), - "there is a cycle in the state transition graph" - ); - - stack.extend( - iter::once((Traversal::Stop, *to_state)) - .chain(iter::once((Traversal::Start, *to_state))), - ); - } - - if !has_any_transitions { - // All paths must end in a final state. - bail_if!( - !self.final_states.contains_key(&state), - "a path through the state transition graph does not end in a final state" - ); - } - } - Some((Traversal::Stop, state)) => { - debug_assert!(on_stack.contains(&state)); - on_stack.remove(&state); - } - } - } - - return Ok(()); - - enum Traversal { - Start, - Stop, - } - } -} - -/// A low-level query of an `Automaton`. -/// -/// This allows you to incrementally query an `Automaton`, without providing the -/// full input sequence ahead of time, and also to incrementally build up the -/// output. -/// -/// The typical usage pattern is: -/// -/// * First, a series of [`next`][crate::Query::next] calls that each provide -/// one character of the input sequence. -/// -/// If this query is still on a path towards a known entry of the -/// automata, then `Some` is returned with the partial output of the -/// transition that was just taken. Otherwise, `None` is returned, signifying -/// that the input string has been rejected by the automata. -/// -/// You may also inspect the current state's associated custom data, if any, -/// in between `next` calls via the -/// [`current_state_data`][crate::Query::current_state_data] method. -/// -/// * When the input sequence is exhausted, call -/// [`is_in_final_state`][crate::Query::is_in_final_state] to determine if this -/// query is in a final state of the automata. If it is not, then the -/// input string has been rejected by the automata. -/// -/// * Given that the input sequence is exhausted, you may call -/// [`finish`][crate::Query::finish] to get the final bit of partial output. -#[derive(Debug, Clone)] -pub struct Query<'a, TAlphabet, TState, TOutput> -where - TAlphabet: Clone + Eq + Hash + Ord, - TState: Clone + Eq + Hash, - TOutput: Output, -{ - automata: &'a Automaton, - current_state: State, -} - -impl<'a, TAlphabet, TState, TOutput> Query<'a, TAlphabet, TState, TOutput> -where - TAlphabet: Clone + Eq + Hash + Ord, - TState: Clone + Eq + Hash, - TOutput: Output, -{ - /// Get the current state in the automaton that this query is at. - pub fn current_state(&self) -> State { - self.current_state - } - - /// Move this query to the given state in the automaton. - /// - /// This can be used to implement backtracking, if you can also reset your - /// output to the way it was when you previously visited the given `State`. - /// - /// Only use a `State` that came from this query's automaton! Mixing and - /// matching states between automata will result in bogus results and/or - /// panics! - pub fn go_to_state(&mut self, state: State) { - assert!((state.0 as usize) < self.automata.transitions.len()); - debug_assert_eq!( - self.automata.state_data.len(), - self.automata.transitions.len() - ); - self.current_state = state; - } - - /// Does the query's current state have a transition on the given input? - /// - /// Regardless whether a transition on the given input exists for the - /// current state or not, the query remains in the current state. - pub fn has_transition_on(&self, input: &TAlphabet) -> bool { - let State(i) = self.current_state; - self.automata.transitions[i as usize].contains_key(input) - } - - /// Transition to the next state given the next input character, and return - /// the partial output for that transition. - /// - /// If `None` is returned, then the input sequence has been rejected by the - /// automata, and this query remains in its current state. - #[inline] - pub fn next(&mut self, input: &TAlphabet) -> Option<&'a TOutput> { - let State(i) = self.current_state; - match self.automata.transitions[i as usize].get(input) { - None => None, - Some((next_state, output)) => { - self.current_state = *next_state; - Some(output) - } - } - } - - /// Get the current state's associated custom data, if any. - /// - /// See also - /// [`InsertionBuilder::set_state_data`][crate::InsertionBuilder::set_state_data]. - #[inline] - pub fn current_state_data(&self) -> Option<&'a TState> { - let State(i) = self.current_state; - self.automata.state_data[i as usize].as_ref() - } - - /// Is this query currently in a final state? - #[inline] - pub fn is_in_final_state(&self) -> bool { - self.automata.final_states.contains_key(&self.current_state) - } - - /// Given that the input sequence is exhausted, get the final bit of partial - /// output. - /// - /// Returns `None` if this query is not currently in a final state, meaning - /// that the automata has rejected this input sequence. You can check - /// whether that is the case or not with the - /// [`is_in_final_state`][crate::Query::is_in_final_state] method. - pub fn finish(self) -> Option<&'a TOutput> { - self.automata.final_states.get(&self.current_state) - } -} - -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); - } -} diff --git a/cranelift/peepmatic/crates/automata/src/output_impls.rs b/cranelift/peepmatic/crates/automata/src/output_impls.rs deleted file mode 100644 index 308c466d5b..0000000000 --- a/cranelift/peepmatic/crates/automata/src/output_impls.rs +++ /dev/null @@ -1,130 +0,0 @@ -use crate::Output; -use std::cmp; -use std::hash::Hash; - -impl Output for u64 { - fn empty() -> Self { - 0 - } - - fn prefix(a: &Self, b: &Self) -> Self { - cmp::min(*a, *b) - } - - fn difference(a: &Self, b: &Self) -> Self { - a - b - } - - fn concat(a: &Self, b: &Self) -> Self { - a + b - } -} - -impl Output for Vec -where - T: Clone + Eq + Hash, -{ - fn empty() -> Self { - vec![] - } - - fn is_empty(&self) -> bool { - self.is_empty() - } - - fn prefix(a: &Self, b: &Self) -> Self { - a.iter() - .cloned() - .zip(b.iter().cloned()) - .take_while(|(a, b)| a == b) - .map(|(a, _)| a) - .collect() - } - - fn difference(a: &Self, b: &Self) -> Self { - let i = a - .iter() - .zip(b.iter()) - .position(|(a, b)| a != b) - .unwrap_or(cmp::min(a.len(), b.len())); - a[i..].to_vec() - } - - fn concat(a: &Self, b: &Self) -> Self { - let mut c = a.clone(); - c.extend(b.iter().cloned()); - c - } -} - -impl Output for Box<[T]> -where - T: Clone + Eq + Hash, -{ - fn empty() -> Self { - vec![].into_boxed_slice() - } - - fn is_empty(&self) -> bool { - self.len() == 0 - } - - fn prefix(a: &Self, b: &Self) -> Self { - a.iter() - .cloned() - .zip(b.iter().cloned()) - .take_while(|(a, b)| a == b) - .map(|(a, _)| a) - .collect() - } - - fn difference(a: &Self, b: &Self) -> Self { - let i = a - .iter() - .zip(b.iter()) - .position(|(a, b)| a != b) - .unwrap_or(cmp::min(a.len(), b.len())); - a[i..].to_vec().into_boxed_slice() - } - - fn concat(a: &Self, b: &Self) -> Self { - let mut c = a.clone().to_vec(); - c.extend(b.iter().cloned()); - c.into_boxed_slice() - } -} - -#[cfg(test)] -mod tests { - use crate::Output; - use std::fmt::Debug; - - // Assert the laws that `Output` requires for correctness. `a` and `b` - // should be two different instances of an `Output` type. - fn assert_laws(a: O, b: O) - where - O: Clone + Debug + Output, - { - // Law 1 - assert_eq!(O::concat(&O::empty(), &a), a.clone()); - - // Law 2 - assert_eq!(O::prefix(&b, &a), O::prefix(&a, &b)); - - // Law 3 - assert_eq!(O::prefix(&O::empty(), &a), O::empty()); - - // Law 4 - assert_eq!(O::difference(&O::concat(&a, &b), &a), b); - } - - #[test] - fn impl_for_u64() { - assert_laws(3, 5); - } - - #[test] - fn impl_for_vec() { - assert_laws(vec![0, 1, 2, 3], vec![0, 2, 4, 6]); - } -} diff --git a/cranelift/peepmatic/crates/automata/src/serde_impls.rs b/cranelift/peepmatic/crates/automata/src/serde_impls.rs deleted file mode 100644 index 8a5d79214f..0000000000 --- a/cranelift/peepmatic/crates/automata/src/serde_impls.rs +++ /dev/null @@ -1,220 +0,0 @@ -//! `serde::Serialize` and `serde::Deserialize` implementations for `Automaton`. -//! -//! Rather than prefix each serialized field with which field it is, we always -//! serialize fields in alphabetical order. Make sure to maintain this if you -//! add or remove fields! -//! -//! Each time you add/remove a field, or change serialization in any other way, -//! make sure to bump `SERIALIZATION_VERSION`. - -use crate::{Automaton, Output, State}; -use serde::{ - de::{self, Deserializer, SeqAccess, Visitor}, - ser::SerializeTupleStruct, - Deserialize, Serialize, Serializer, -}; -use std::collections::BTreeMap; -use std::fmt; -use std::hash::Hash; -use std::marker::PhantomData; - -const SERIALIZATION_VERSION: u32 = 1; - -impl Serialize for State { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_u32(self.0) - } -} - -impl<'de> Deserialize<'de> for State { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - Ok(State(deserializer.deserialize_u32(U32Visitor)?)) - } -} - -struct U32Visitor; - -impl<'de> Visitor<'de> for U32Visitor { - type Value = u32; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("an integer between `0` and `2^32 - 1`") - } - - fn visit_u8(self, value: u8) -> Result - where - E: de::Error, - { - Ok(u32::from(value)) - } - - fn visit_u32(self, value: u32) -> Result - where - E: de::Error, - { - Ok(value) - } - - fn visit_u64(self, value: u64) -> Result - where - E: de::Error, - { - use std::u32; - if value <= u64::from(u32::MAX) { - Ok(value as u32) - } else { - Err(E::custom(format!("u32 out of range: {}", value))) - } - } -} - -impl Serialize for Automaton -where - TAlphabet: Serialize + Clone + Eq + Hash + Ord, - TState: Serialize + Clone + Eq + Hash, - TOutput: Serialize + Output, -{ - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let Automaton { - final_states, - start_state, - state_data, - transitions, - } = self; - - let mut s = serializer.serialize_tuple_struct("Automaton", 5)?; - s.serialize_field(&SERIALIZATION_VERSION)?; - s.serialize_field(final_states)?; - s.serialize_field(start_state)?; - s.serialize_field(state_data)?; - s.serialize_field(transitions)?; - s.end() - } -} - -impl<'de, TAlphabet, TState, TOutput> Deserialize<'de> for Automaton -where - TAlphabet: 'de + Deserialize<'de> + Clone + Eq + Hash + Ord, - TState: 'de + Deserialize<'de> + Clone + Eq + Hash, - TOutput: 'de + Deserialize<'de> + Output, -{ - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserializer.deserialize_tuple_struct( - "Automaton", - 5, - AutomatonVisitor { - phantom: PhantomData, - }, - ) - } -} - -struct AutomatonVisitor<'de, TAlphabet, TState, TOutput> -where - TAlphabet: 'de + Deserialize<'de> + Clone + Eq + Hash + Ord, - TState: 'de + Deserialize<'de> + Clone + Eq + Hash, - TOutput: 'de + Deserialize<'de> + Output, -{ - phantom: PhantomData<&'de (TAlphabet, TState, TOutput)>, -} - -impl<'de, TAlphabet, TState, TOutput> Visitor<'de> - for AutomatonVisitor<'de, TAlphabet, TState, TOutput> -where - TAlphabet: 'de + Deserialize<'de> + Clone + Eq + Hash + Ord, - TState: 'de + Deserialize<'de> + Clone + Eq + Hash, - TOutput: 'de + Deserialize<'de> + Output, -{ - type Value = Automaton; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("Automaton") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: SeqAccess<'de>, - { - match seq.next_element::()? { - Some(v) if v == SERIALIZATION_VERSION => {} - Some(v) => { - return Err(de::Error::invalid_value( - de::Unexpected::Unsigned(v as u64), - &self, - )); - } - None => { - return Err(de::Error::invalid_length( - 0, - &"Automaton expects 5 elements", - )) - } - } - - let final_states = match seq.next_element::>()? { - Some(x) => x, - None => { - return Err(de::Error::invalid_length( - 1, - &"Automaton expects 5 elements", - )) - } - }; - - let start_state = match seq.next_element::()? { - Some(x) => x, - None => { - return Err(de::Error::invalid_length( - 2, - &"Automaton expects 5 elements", - )) - } - }; - - let state_data = match seq.next_element::>>()? { - Some(x) => x, - None => { - return Err(de::Error::invalid_length( - 3, - &"Automaton expects 5 elements", - )) - } - }; - - let transitions = match seq.next_element::>>()? { - Some(x) => x, - None => { - return Err(de::Error::invalid_length( - 4, - &"Automaton expects 5 elements", - )) - } - }; - - let automata = Automaton { - final_states, - start_state, - state_data, - transitions, - }; - - // Ensure that the deserialized automata is well-formed. - automata - .check_representation() - .map_err(|msg| de::Error::custom(msg))?; - - Ok(automata) - } -} diff --git a/cranelift/peepmatic/crates/fuzzing/Cargo.toml b/cranelift/peepmatic/crates/fuzzing/Cargo.toml deleted file mode 100644 index 8130acf867..0000000000 --- a/cranelift/peepmatic/crates/fuzzing/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "peepmatic-fuzzing" -version = "0.66.0" -authors = ["Nick Fitzgerald "] -edition = "2018" -publish = false - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -arbitrary = { version = "1.0.0", features = ["derive"] } -bincode = "1.2.1" -env_logger = "0.8.1" -fst = "0.4.1" -log = "0.4.8" -peepmatic = { path = "../.." } -peepmatic-automata = { path = "../automata", features = ["serde"] } -peepmatic-runtime = { path = "../runtime", features = ["construct"] } -peepmatic-test = { path = "../test" } -peepmatic-test-operator = { path = "../test-operator" } -peepmatic-traits = { path = "../traits" } -rand = { version = "0.8.3", features = ["small_rng"] } -serde = "1.0.106" -wast = "38.0.0" diff --git a/cranelift/peepmatic/crates/fuzzing/src/automata.rs b/cranelift/peepmatic/crates/fuzzing/src/automata.rs deleted file mode 100644 index 21fe5c5fd2..0000000000 --- a/cranelift/peepmatic/crates/fuzzing/src/automata.rs +++ /dev/null @@ -1,201 +0,0 @@ -//! Helpers for fuzzing the `peepmatic-automata` crate. - -use peepmatic_automata::{Automaton, Builder, Output}; -use serde::{Deserialize, Serialize}; -use std::collections::{HashMap, HashSet}; -use std::hash::Hash; - -fn serde_roundtrip( - automata: Automaton, -) -> Automaton -where - TAlphabet: Serialize + for<'de> Deserialize<'de> + Clone + Eq + Hash + Ord, - TState: Serialize + for<'de> Deserialize<'de> + Clone + Eq + Hash, - TOutput: Serialize + for<'de> Deserialize<'de> + Output, -{ - let encoded: Vec = bincode::serialize(&automata).expect("should serialize OK"); - bincode::deserialize(&encoded).expect("should deserialize OK") -} - -const MAX_AUTOMATON_KEY_LEN: usize = 256; - -/// Construct an automaton from the the given input-output pairs, and assert -/// that: -/// -/// * Putting in each of the input strings should result in the expected output -/// string. -/// -/// * Putting in an input string that is not one of the given inputs from our -/// input-output pairs should never yield an output value. -pub fn simple_automata(input_output_pairs: Vec)>>) { - let _ = env_logger::try_init(); - - let full_input = |pair: &[(u8, Vec)]| { - let mut full_input = vec![]; - for (input, _) in pair { - full_input.push(*input); - } - full_input - }; - - let mut inputs = HashSet::new(); - - let mut input_output_pairs: Vec<_> = input_output_pairs - .into_iter() - .filter(|pair| { - if pair.is_empty() { - return false; - } - - // Make sure that we don't generate huge input keys. - let full_input = full_input(pair); - if full_input.len() >= MAX_AUTOMATON_KEY_LEN { - return false; - } - - // Make sure we don't have duplicate inputs. - let is_new = inputs.insert(full_input); - is_new - }) - .collect(); - - input_output_pairs.sort_by(|a, b| full_input(a).cmp(&full_input(b))); - - if input_output_pairs.is_empty() { - return; - } - - // A map from one of our concatenated input strings to its concatenated - // output. - let mut expected = HashMap::with_capacity(input_output_pairs.len()); - - let mut builder = Builder::>::new(); - for pair in &input_output_pairs { - let mut full_input = vec![]; - let mut full_output = vec![]; - - let mut ins = builder.insert(); - for (input, output) in pair.iter().cloned() { - full_input.push(input); - full_output.extend(output.iter().copied()); - - ins.next(input, output); - } - - let old = expected.insert(full_input, full_output); - assert!(old.is_none()); - - ins.finish(); - } - - let automata = builder.finish(); - let automata = serde_roundtrip(automata); - - // Assert that each of our input strings yields the expected output. - for (input, expected_output) in &expected { - log::debug!("Testing input: {:?}", input); - let actual_output = automata.get(input); - assert!(actual_output.is_some()); - assert_eq!(actual_output.as_ref().unwrap(), expected_output); - } - - // Test that mutations of our input strings (that aren't themselves other - // input strings!) do not yeild any output. - for input in expected.keys() { - for i in 0..input.len() { - let mut mutated = input.clone(); - mutated[i] = mutated[i].wrapping_add(1); - log::debug!("Testing mutated input: {:?}", mutated); - if !expected.contains_key(&mutated) { - assert!(automata.get(&mutated).is_none()); - } - } - } -} - -/// Do differential testing against the `fst` crate, which is another -/// implementation of the algorithm we use for finite-state transducer -/// construction in `peepmatic-automata`. -pub fn fst_differential(map: HashMap, u64>) { - let _ = env_logger::try_init(); - - let mut inputs: Vec<_> = map - .keys() - .filter(|k| !k.is_empty() && k.len() < MAX_AUTOMATON_KEY_LEN) - .cloned() - .collect(); - inputs.sort(); - inputs.dedup(); - if inputs.is_empty() { - return; - } - - let mut fst = fst::MapBuilder::memory(); - let mut builder = Builder::::new(); - - for inp in &inputs { - fst.insert(inp, map[inp]).unwrap(); - - let mut ins = builder.insert(); - for (i, ch) in inp.iter().enumerate() { - ins.next(*ch, if i == 0 { map[inp] } else { 0 }); - } - ins.finish(); - } - - let fst = fst.into_map(); - let automata = builder.finish(); - let automata = serde_roundtrip(automata); - - for inp in inputs { - // Check we have the same result as `fst` for inputs we know are in the - // automata. - log::debug!("Testing input {:?}", inp); - let expected = fst.get(&inp).expect("`fst` should have entry for `inp`"); - let actual = automata - .get(&inp) - .expect("automata should have entry for `inp`"); - assert_eq!(expected, actual); - - // Check that we have the same result as `fst` for inputs that may or - // may not be in the automata. - for i in 0..inp.len() { - let mut mutated = inp.clone(); - mutated[i] = mutated[i].wrapping_add(1); - log::debug!("Testing mutated input {:?}", mutated); - let expected = fst.get(&mutated); - let actual = automata.get(&mutated); - assert_eq!(expected, actual); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn check_simple_automata() { - crate::check(simple_automata); - } - - #[test] - fn check_fst_differential() { - crate::check(fst_differential); - } - - #[test] - fn regression_test_0() { - simple_automata(vec![vec![(0, vec![0]), (0, vec![1])], vec![(0, vec![2])]]); - } - - #[test] - fn regression_test_1() { - fst_differential(vec![(vec![1, 3], 5), (vec![1, 2], 4)].into_iter().collect()); - } - - #[test] - fn regression_test_2() { - simple_automata(vec![vec![(0, vec![11]), (0, vec![])], vec![(0, vec![11])]]); - } -} diff --git a/cranelift/peepmatic/crates/fuzzing/src/compile.rs b/cranelift/peepmatic/crates/fuzzing/src/compile.rs deleted file mode 100644 index c0e4dad59d..0000000000 --- a/cranelift/peepmatic/crates/fuzzing/src/compile.rs +++ /dev/null @@ -1,79 +0,0 @@ -//! Fuzz testing utilities related to AST pattern matching. - -use peepmatic_runtime::PeepholeOptimizations; -use peepmatic_test_operator::TestOperator; -use std::path::Path; -use std::str; - -// To avoid timeouts, don't deal with inputs larger than this. -const MAX_LEN: usize = 2048; - -/// Attempt to interpret the given bytes as UTF-8 and then compile them as if -/// they were source text of our DSL. -pub fn compile(data: &[u8]) { - if data.len() > MAX_LEN { - return; - } - - let source = match str::from_utf8(data) { - Err(_) => return, - Ok(s) => s, - }; - - let opt = match peepmatic::compile_str::(source, Path::new("fuzz")) { - Err(_) => return, - Ok(o) => o, - }; - - // Should be able to serialize and deserialize the peephole optimizer. - let opt_bytes = bincode::serialize(&opt).expect("should serialize peephole optimizations OK"); - let _: PeepholeOptimizations = - bincode::deserialize(&opt_bytes).expect("should deserialize peephole optimizations OK"); - - // Compiling the same source text again should be deterministic. - let opt2 = peepmatic::compile_str::(source, Path::new("fuzz")) - .expect("should be able to compile source text again, if it compiled OK the first time"); - let opt2_bytes = - bincode::serialize(&opt2).expect("should serialize second peephole optimizations OK"); - assert_eq!(opt_bytes, opt2_bytes); -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn check_compile() { - crate::check(|s: String| compile(s.as_bytes())); - } - - #[test] - fn regression_0() { - compile( - b" - (=> (bor (bor $x $y) $y) $x) - (=> (bor (bor $x $z) $y) $x) - ", - ); - } - - #[test] - fn regression_1() { - compile( - b" - (=> (bor (bor $x $y) 0) $x) - (=> (bor $x 0) $x) - (=> (bor $y $x) $x) - ", - ); - } - - #[test] - fn regression_2() { - compile( - b" - (=> (sshr $x 11111111110) $x) - ", - ); - } -} diff --git a/cranelift/peepmatic/crates/fuzzing/src/interp.rs b/cranelift/peepmatic/crates/fuzzing/src/interp.rs deleted file mode 100644 index b2d5355fc4..0000000000 --- a/cranelift/peepmatic/crates/fuzzing/src/interp.rs +++ /dev/null @@ -1,367 +0,0 @@ -//! Interpreting compiled peephole optimizations against test instruction sequences. - -use peepmatic::{ - Constraint, Dfs, DynAstRef, Optimizations, Pattern, Span, TraversalEvent, ValueLiteral, - Variable, -}; -use peepmatic_runtime::{ - cc::ConditionCode, - part::Constant, - r#type::BitWidth, - r#type::{Kind, Type}, -}; -use peepmatic_test::{Program, TestIsa}; -use peepmatic_test_operator::TestOperator; -use peepmatic_traits::{TypingContext as TypingContextTrait, TypingRules}; -use std::collections::{BTreeMap, HashMap}; -use std::path::Path; -use std::str; - -/// Compile the given source text, and if it is a valid set of optimizations, -/// then interpret the optimizations against test instruction sequences created -/// to reflect the optimizations. -pub fn interp(data: &[u8]) { - let _ = env_logger::try_init(); - - let source = match str::from_utf8(data) { - Err(_) => return, - Ok(s) => s, - }; - - let peep_opts = match peepmatic::compile_str(source, Path::new("fuzz")) { - Err(_) => return, - Ok(o) => o, - }; - let mut optimizer = peep_opts.optimizer(TestIsa { - native_word_size_in_bits: 32, - }); - - // Okay, we know it compiles and verifies alright, so (re)parse the AST. - let buf = wast::parser::ParseBuffer::new(&source).unwrap(); - let ast = wast::parser::parse::>(&buf).unwrap(); - - // And we need access to the assigned types, so re-verify it as well. - peepmatic::verify(&ast).unwrap(); - - // Walk over each optimization and create an instruction sequence that - // matches the optimization. - let mut program = Program::default(); - for opt in &ast.optimizations { - // The instruction sequence we generate must match an optimization (not - // necessarily *this* optimization, if there is another that is more - // specific but also matches) unless there is an `bit-width` - // precondition or an implicit `bit-width` precondition via a type - // ascription. When those things exist, we might have constructed - // instructions with the wrong bit widths to match. - let mut allow_no_match = false; - - // The last instruction we generated. After we've generated the full - // instruction sequence, this will be its root. - let mut last_inst = None; - - // Remember the instructions associated with variables and constants, so - // that when they appear multiple times, we reuse the same instruction. - let mut id_to_inst = HashMap::new(); - - // Map from a pattern's span to the instruction we generated for - // it. This allows parent operations to get the instructions for their - // children. - let mut span_to_inst = BTreeMap::new(); - - for (te, lhs) in Dfs::new(&opt.lhs) { - // NB: We use a post-order traversal because we want arguments to be - // generated before they are used. - if te != TraversalEvent::Exit { - continue; - } - - match lhs { - DynAstRef::Precondition(p) => { - allow_no_match |= p.constraint == Constraint::BitWidth; - } - - DynAstRef::Pattern(Pattern::Operation(op)) => { - allow_no_match |= op.r#type.get().is_some(); - - let num_imms = op.operator.immediates_arity() as usize; - - // Generate this operation's immediates. - let mut imm_tys = vec![]; - op.operator - .immediate_types((), &mut TypingContext, &mut imm_tys); - let imms: Vec<_> = op - .operands - .iter() - .take(num_imms) - .zip(imm_tys) - .map(|(pat, ty)| match pat { - Pattern::ValueLiteral(ValueLiteral::Integer(i)) => { - Constant::Int(i.value as _, BitWidth::ThirtyTwo).into() - } - Pattern::ValueLiteral(ValueLiteral::Boolean(b)) => { - Constant::Bool(b.value, BitWidth::One).into() - } - Pattern::ValueLiteral(ValueLiteral::ConditionCode(cc)) => cc.cc.into(), - Pattern::Constant(_) | Pattern::Variable(_) => match ty { - TypeOrConditionCode::ConditionCode => ConditionCode::Eq.into(), - TypeOrConditionCode::Type(ty) => match ty.kind { - Kind::Int => Constant::Int(1, ty.bit_width).into(), - Kind::Bool => Constant::Bool(false, ty.bit_width).into(), - Kind::Void | Kind::CpuFlags => { - unreachable!("void and cpu flags cannot be immediates") - } - }, - }, - Pattern::Operation(_) => { - unreachable!("operations not allowed as immediates") - } - }) - .collect(); - - // Generate (or collect already-generated) instructions for - // this operation's arguments. - let mut arg_tys = vec![]; - op.operator - .parameter_types((), &mut TypingContext, &mut arg_tys); - let args: Vec<_> = op - .operands - .iter() - .skip(num_imms) - .zip(arg_tys) - .map(|(pat, ty)| match pat { - Pattern::Operation(op) => span_to_inst[&op.span()], - Pattern::ValueLiteral(ValueLiteral::Integer(i)) => program.r#const( - Constant::Int(i.value as _, BitWidth::ThirtyTwo), - BitWidth::ThirtyTwo, - ), - Pattern::ValueLiteral(ValueLiteral::Boolean(b)) => program.r#const( - Constant::Bool(b.value, BitWidth::One), - BitWidth::ThirtyTwo, - ), - Pattern::ValueLiteral(ValueLiteral::ConditionCode(_)) => { - unreachable!("condition codes cannot be arguments") - } - Pattern::Constant(peepmatic::Constant { id, .. }) - | Pattern::Variable(Variable { id, .. }) => match ty { - TypeOrConditionCode::Type(ty) => { - *id_to_inst.entry(id).or_insert_with(|| match ty.kind { - Kind::Int => program.r#const( - Constant::Int(1, ty.bit_width), - BitWidth::ThirtyTwo, - ), - Kind::Bool => program.r#const( - Constant::Bool(false, ty.bit_width), - BitWidth::ThirtyTwo, - ), - Kind::CpuFlags => { - unreachable!("cpu flags cannot be an argument") - } - Kind::Void => unreachable!("void cannot be an argument"), - }) - } - TypeOrConditionCode::ConditionCode => { - unreachable!("condition codes cannot be arguments") - } - }, - }) - .collect(); - - let ty = match op.operator.result_type((), &mut TypingContext) { - TypeOrConditionCode::Type(ty) => ty, - TypeOrConditionCode::ConditionCode => { - unreachable!("condition codes cannot be operation results") - } - }; - let inst = program.new_instruction(op.operator, ty, imms, args); - last_inst = Some(inst); - let old_inst = span_to_inst.insert(op.span(), inst); - assert!(old_inst.is_none()); - } - _ => continue, - } - } - - // Run the optimizer on our newly generated instruction sequence. - if let Some(inst) = last_inst { - let replacement = optimizer.apply_one(&mut program, inst); - assert!( - replacement.is_some() || allow_no_match, - "an optimization should match the generated instruction sequence" - ); - } - } - - // Finally, just try and run the optimizer on every instruction we - // generated, just to potentially shake out some more bugs. - let instructions: Vec<_> = program.instructions().map(|(k, _)| k).collect(); - for inst in instructions { - let _ = optimizer.apply_one(&mut program, inst); - } -} - -enum TypeOrConditionCode { - Type(Type), - ConditionCode, -} - -struct TypingContext; - -impl<'a> TypingContextTrait<'a> for TypingContext { - type Span = (); - type TypeVariable = TypeOrConditionCode; - - fn cc(&mut self, _: ()) -> Self::TypeVariable { - TypeOrConditionCode::ConditionCode - } - - fn bNN(&mut self, _: ()) -> Self::TypeVariable { - TypeOrConditionCode::Type(Type::b1()) - } - - fn iNN(&mut self, _: ()) -> Self::TypeVariable { - TypeOrConditionCode::Type(Type::i32()) - } - - fn iMM(&mut self, _: ()) -> Self::TypeVariable { - TypeOrConditionCode::Type(Type::i32()) - } - - fn cpu_flags(&mut self, _: ()) -> Self::TypeVariable { - TypeOrConditionCode::Type(Type::cpu_flags()) - } - - fn b1(&mut self, _: ()) -> Self::TypeVariable { - TypeOrConditionCode::Type(Type::b1()) - } - - fn void(&mut self, _: ()) -> Self::TypeVariable { - TypeOrConditionCode::Type(Type::void()) - } - - fn bool_or_int(&mut self, _: ()) -> Self::TypeVariable { - TypeOrConditionCode::Type(Type::b1()) - } - - fn any_t(&mut self, _: ()) -> Self::TypeVariable { - TypeOrConditionCode::Type(Type::i32()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn check_interp() { - crate::check(|s: Vec| interp(String::from_utf8_lossy(&s).as_bytes())); - } - - #[test] - fn regression_0() { - interp(b"(=> (imul $x $x) $x)"); - } - - #[test] - fn regression_1() { - interp(b"(=> (when (imul $x $C) (is-power-of-two $C)) $x)"); - } - - #[test] - fn regression_2() { - interp( - b" - (=> (bor (bor $x $y) $x) (bor $x $y)) - (=> (bor (bor $x $C) 5) $x) - ", - ); - } - - #[test] - fn regression_3() { - interp( - b" - (=> (bor $y (bor $x 9)) $x) - (=> (bor (bor $x $y) $x) $x) - ", - ); - } - - #[test] - fn regression_4() { - interp( - b" - (=> (bor $C 33) 0) - (=> (bor $x 22) 1) - (=> (bor $x 11) 2) - ", - ); - } - - #[test] - fn regression_5() { - interp( - b" - (=> (bor $y (bor $x $y)) (bor $x $y)) - (=> (bor (bor $x $y) $z) $x) - (=> (bor (bor $x $y) $y) $x) - ", - ); - } - - #[test] - fn regression_6() { - interp(b"(=> (imul $x $f) of)"); - } - - #[test] - fn regression_7() { - interp( - b" - (=> (when (sdiv $x $C) - (fits-in-native-word $y)) - (sdiv $C $x)) - ", - ); - } - - #[test] - fn regression_9() { - interp( - b" - (=> (when $x) $x) - (=> (trapnz $x) (trapnz $x)) - ", - ); - } - - #[test] - fn regression_10() { - interp(b"(=> (sshr{i1} $x 0) $x)"); - } - - #[test] - fn regression_11() { - interp( - b" - (=> (when (ushr_imm $x (ishl 4 3)) - (bit-width $x 64)) - (sextend{i64} (ireduce{i32} $x))) - ", - ); - } - - #[test] - fn regression_12() { - interp(b"(=> (band $C1 (band_imm $C1 1)) 1)"); - } - - #[test] - fn regression_13() { - interp(b"(=> (brz (icmp eq 0 $x)) (brz (ireduce{i32} $x)))"); - } - - #[test] - fn regression_14() { - interp(b"(=> (brz (icmp $E 0 $x)) (brz $x))"); - } -} diff --git a/cranelift/peepmatic/crates/fuzzing/src/lib.rs b/cranelift/peepmatic/crates/fuzzing/src/lib.rs deleted file mode 100644 index 0fa128c539..0000000000 --- a/cranelift/peepmatic/crates/fuzzing/src/lib.rs +++ /dev/null @@ -1,98 +0,0 @@ -//! Utilities for fuzzing. -//! -//! The actual fuzz targets are defined in `peepmatic/fuzz/*`. This crate just -//! has oracles and generators for fuzzing. - -#![deny(missing_debug_implementations)] -#![deny(missing_docs)] - -use arbitrary::{Arbitrary, Unstructured}; -use rand::prelude::*; -use std::fmt::Debug; -use std::time; - -pub mod automata; -pub mod compile; -pub mod interp; -pub mod parser; - -/// A quickcheck-style runner for fuzz targets. -/// -/// This is *not* intended to replace a long-running, coverage-guided fuzzing -/// engine like libFuzzer! This is only for defining quick, purely random tests -/// for use with `cargo test` and CI. -pub fn check(mut f: impl FnMut(A)) -where - A: Clone + Debug + for<'a> Arbitrary<'a>, -{ - let seed = rand::thread_rng().gen(); - let mut rng = rand::rngs::SmallRng::seed_from_u64(seed); - - const INITIAL_LENGTH: usize = 16; - const MAX_LENGTH: usize = 4096; - - let mut buf: Vec = (0..INITIAL_LENGTH).map(|_| rng.gen()).collect(); - let mut num_checked = 0; - - let time_budget = time::Duration::from_secs(2); - let then = time::Instant::now(); - - loop { - if num_checked > 0 && time::Instant::now().duration_since(then) > time_budget { - eprintln!("Checked {} random inputs.", num_checked); - return; - } - - match ::arbitrary_take_rest(Unstructured::new(&buf)) { - Ok(input) => { - num_checked += 1; - eprintln!("Checking input: {:#?}", input); - f(input.clone()); - } - Err(e @ arbitrary::Error::NotEnoughData) => { - eprintln!("warning: {}", e); - if *buf.last().unwrap() == 0 { - if buf.len() < MAX_LENGTH { - let new_size = std::cmp::min(buf.len() * 2, MAX_LENGTH); - eprintln!("Growing buffer size to {}", new_size); - let delta = new_size - buf.len(); - buf.reserve(delta); - for _ in 0..delta { - buf.push(rng.gen()); - } - continue; - } else { - // Regenerate `buf` in the loop below and see if that - // fixes things... - eprintln!("Regenerating buffer data."); - } - } else { - // Shrink values in the end of `buf`, which is where - // `Arbitrary` pulls container lengths from. Then try again. - eprintln!("Shrinking buffer's tail values."); - let i = (buf.len() as f64).sqrt() as usize; - for j in i..buf.len() { - buf[j] /= 2; - } - continue; - } - } - Err(e) => { - eprintln!("warning: {}", e); - // Usually this happens because `A` requires a sequence utf-8 - // bytes but its given sequence wasn't valid utf-8. Just skip - // this iteration and try again after we've updated `buf` below. - } - }; - - // Double the size of the buffer every so often, so we don't only - // explore small inputs. - if num_checked == buf.len() { - buf.resize(std::cmp::min(buf.len() * 2, MAX_LENGTH), 0); - } - - for i in 0..buf.len() { - buf[i] = rng.gen(); - } - } -} diff --git a/cranelift/peepmatic/crates/fuzzing/src/parser.rs b/cranelift/peepmatic/crates/fuzzing/src/parser.rs deleted file mode 100644 index be59fa658b..0000000000 --- a/cranelift/peepmatic/crates/fuzzing/src/parser.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! Utilities for fuzzing our DSL's parser. - -use peepmatic::Optimizations; -use peepmatic_test_operator::TestOperator; -use std::str; - -/// Attempt to parse the given string as if it were a snippet of our DSL. -pub fn parse(data: &[u8]) { - let source = match str::from_utf8(data) { - Ok(s) => s, - Err(_) => return, - }; - - let buf = match wast::parser::ParseBuffer::new(&source) { - Ok(buf) => buf, - Err(_) => return, - }; - - let _ = wast::parser::parse::>(&buf); -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn check_parse() { - crate::check(|s: String| parse(s.as_bytes())); - } -} diff --git a/cranelift/peepmatic/crates/macro/Cargo.toml b/cranelift/peepmatic/crates/macro/Cargo.toml deleted file mode 100644 index 09c64e6481..0000000000 --- a/cranelift/peepmatic/crates/macro/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "peepmatic-macro" -version = "0.78.0" -authors = ["Nick Fitzgerald "] -edition = "2018" -license = "Apache-2.0 WITH LLVM-exception" -description = "Macros for peepmatic" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -proc-macro2 = "1.0.9" -quote = "1.0.3" -syn = { version = "1.0.16", features = ['extra-traits'] } - -[lib] -proc_macro = true -test = false -doctest = false diff --git a/cranelift/peepmatic/crates/macro/LICENSE b/cranelift/peepmatic/crates/macro/LICENSE deleted file mode 100644 index f9d81955f4..0000000000 --- a/cranelift/peepmatic/crates/macro/LICENSE +++ /dev/null @@ -1,220 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - ---- LLVM Exceptions to the Apache 2.0 License ---- - -As an exception, if, as a result of your compiling your source code, portions -of this Software are embedded into an Object form of such source code, you -may redistribute such embedded portions in such Object form without complying -with the conditions of Sections 4(a), 4(b) and 4(d) of the License. - -In addition, if you combine or link compiled forms of this Software with -software that is licensed under the GPLv2 ("Combined Software") and if a -court of competent jurisdiction determines that the patent provision (Section -3), the indemnity provision (Section 9) or other Section of the License -conflicts with the conditions of the GPLv2, you may retroactively and -prospectively choose to deem waived or otherwise exclude such Section(s) of -the License, but only in their entirety and only with respect to the Combined -Software. - diff --git a/cranelift/peepmatic/crates/macro/src/child_nodes.rs b/cranelift/peepmatic/crates/macro/src/child_nodes.rs deleted file mode 100644 index 85a5e10382..0000000000 --- a/cranelift/peepmatic/crates/macro/src/child_nodes.rs +++ /dev/null @@ -1,115 +0,0 @@ -use quote::quote; -use syn::DeriveInput; -use syn::{parse_quote, GenericParam, Generics, Result}; - -pub fn derive_child_nodes(input: &DeriveInput) -> Result { - let children = get_child_nodes(&input.data)?; - let name = &input.ident; - let generics = add_trait_bounds(input.generics.clone()); - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - - Ok(quote! { - impl #impl_generics ChildNodes<'a, 'a, TOperator> for #name #ty_generics #where_clause { - fn child_nodes(&'a self, children: &mut impl Extend>) { - #children - } - } - }) -} - -fn get_child_nodes(data: &syn::Data) -> Result { - match data { - syn::Data::Struct(s) => { - let mut fields = vec![]; - - match &s.fields { - syn::Fields::Named(n) => { - for f in n.named.iter() { - let opts = crate::PeepmaticOpts::from_attrs(&mut f.attrs.clone())?; - - if opts.skip_child { - continue; - } - - let field_name = f.ident.as_ref().unwrap(); - if opts.flatten { - fields.push(quote! { - self.#field_name.iter().map(DynAstRef::from) - }); - } else { - fields.push(quote! { - std::iter::once(DynAstRef::from(&self.#field_name)) - }); - } - } - } - syn::Fields::Unnamed(u) => { - for (i, f) in u.unnamed.iter().enumerate() { - let opts = crate::PeepmaticOpts::from_attrs(&mut f.attrs.clone())?; - if opts.skip_child { - continue; - } - if opts.flatten { - return Err(syn::Error::new( - u.paren_token.span, - "#[peepmatic(flatten)] is only allowed with named fields", - )); - } - fields.push(quote! { - std::iter::once(DynAstRef::from(&self.#i)) - }); - } - } - syn::Fields::Unit => {} - } - - Ok(match fields.as_slice() { - [] => quote! { let _ = children; }, - [f, rest @ ..] => { - let rest = rest.iter().map(|f| { - quote! { - .chain(#f) - } - }); - quote! { - children.extend( #f #( #rest )* ); - } - } - }) - } - syn::Data::Enum(e) => { - let mut match_arms = vec![]; - for v in e.variants.iter() { - match v.fields { - syn::Fields::Unnamed(ref u) if u.unnamed.len() == 1 => { - let variant = &v.ident; - match_arms.push(quote! { - Self::#variant(x) => children.extend(Some(x.into())), - }); - } - _ => panic!("#[derive(ChildNodes)] only supports enums whose variants all ahve a single unnamed field") - } - } - Ok(quote! { - match self { - #( #match_arms )* - } - }) - } - syn::Data::Union(_) => panic!("#[derive(ChildNodes)] is not supported on unions"), - } -} - -fn add_trait_bounds(mut generics: Generics) -> Generics { - for param in &mut generics.params { - if let GenericParam::Type(type_param) = param { - if type_param.ident == "TOperator" { - continue; - } - type_param - .bounds - .push(parse_quote!(ChildNodes<'a, 'a, TOperator>)); - } - } - generics -} diff --git a/cranelift/peepmatic/crates/macro/src/into_dyn_ast_ref.rs b/cranelift/peepmatic/crates/macro/src/into_dyn_ast_ref.rs deleted file mode 100644 index 04944ee311..0000000000 --- a/cranelift/peepmatic/crates/macro/src/into_dyn_ast_ref.rs +++ /dev/null @@ -1,23 +0,0 @@ -use quote::quote; -use syn::DeriveInput; -use syn::Result; - -pub fn derive_into_dyn_ast_ref(input: &DeriveInput) -> Result { - let ty = &input.ident; - - let opts = crate::PeepmaticOpts::from_attrs(&mut input.attrs.clone())?; - if opts.no_into_dyn_node { - return Ok(quote! {}); - } - - let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); - - Ok(quote! { - impl #impl_generics From<&'a #ty #ty_generics> for DynAstRef<'a, TOperator> #where_clause { - #[inline] - fn from(x: &'a #ty #ty_generics) -> Self { - Self::#ty(x) - } - } - }) -} diff --git a/cranelift/peepmatic/crates/macro/src/lib.rs b/cranelift/peepmatic/crates/macro/src/lib.rs deleted file mode 100644 index 22908bb947..0000000000 --- a/cranelift/peepmatic/crates/macro/src/lib.rs +++ /dev/null @@ -1,150 +0,0 @@ -extern crate proc_macro; - -use crate::proc_macro::TokenStream; -use proc_macro2::Span; -use quote::quote; -use syn::parse::{Parse, ParseStream}; -use syn::punctuated::Punctuated; -use syn::DeriveInput; -use syn::Error; -use syn::{parse_macro_input, Ident, Result}; - -mod child_nodes; -mod into_dyn_ast_ref; -mod span; - -#[proc_macro_derive(Ast, attributes(peepmatic))] -pub fn derive_ast(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - - let span_impl = match span::derive_span(&input) { - Ok(s) => s, - Err(e) => return e.to_compile_error().into(), - }; - - let child_nodes_impl = match child_nodes::derive_child_nodes(&input) { - Ok(c) => c, - Err(e) => return e.to_compile_error().into(), - }; - - let into_dyn_ast_ref_impl = match into_dyn_ast_ref::derive_into_dyn_ast_ref(&input) { - Ok(n) => n, - Err(e) => return e.to_compile_error().into(), - }; - - let expanded = quote! { - #span_impl - #child_nodes_impl - #into_dyn_ast_ref_impl - }; - - TokenStream::from(expanded) -} - -#[derive(Default)] -pub(crate) struct PeepmaticOpts { - // `ChildNodes` options. - skip_child: bool, - flatten: bool, - - // `From<&'a Self> for DynAstRef<'a>` options. - no_into_dyn_node: bool, - - // Peepmatic operator options. - immediates_paren: syn::token::Paren, - immediates: Vec, - params_paren: syn::token::Paren, - params: Vec, - result: Option, -} - -impl Parse for PeepmaticOpts { - fn parse(input: ParseStream) -> Result { - enum Attr { - Immediates(syn::token::Paren, Vec), - Params(syn::token::Paren, Vec), - Result(syn::Ident), - NoIntoDynNode, - SkipChild, - Flatten, - } - - let attrs = Punctuated::<_, syn::token::Comma>::parse_terminated(input)?; - let mut ret = PeepmaticOpts::default(); - for attr in attrs { - match attr { - Attr::Immediates(paren, imms) => { - ret.immediates_paren = paren; - ret.immediates = imms; - } - Attr::Params(paren, ps) => { - ret.params_paren = paren; - ret.params = ps; - } - Attr::Result(r) => ret.result = Some(r), - Attr::NoIntoDynNode => ret.no_into_dyn_node = true, - Attr::SkipChild => ret.skip_child = true, - Attr::Flatten => ret.flatten = true, - } - } - - return Ok(ret); - - impl Parse for Attr { - fn parse(input: ParseStream) -> Result { - let attr: Ident = input.parse()?; - if attr == "immediates" { - let inner; - let paren = syn::parenthesized!(inner in input); - let imms = Punctuated::<_, syn::token::Comma>::parse_terminated(&inner)?; - return Ok(Attr::Immediates(paren, imms.into_iter().collect())); - } - if attr == "params" { - let inner; - let paren = syn::parenthesized!(inner in input); - let params = Punctuated::<_, syn::token::Comma>::parse_terminated(&inner)?; - return Ok(Attr::Params(paren, params.into_iter().collect())); - } - if attr == "result" { - let inner; - syn::parenthesized!(inner in input); - return Ok(Attr::Result(syn::Ident::parse(&inner)?)); - } - if attr == "skip_child" { - return Ok(Attr::SkipChild); - } - if attr == "no_into_dyn_node" { - return Ok(Attr::NoIntoDynNode); - } - if attr == "flatten" { - return Ok(Attr::Flatten); - } - return Err(Error::new(attr.span(), "unexpected attribute")); - } - } - } -} - -fn peepmatic_attrs(attrs: &mut Vec) -> TokenStream { - let mut ret = proc_macro2::TokenStream::new(); - let ident = syn::Path::from(syn::Ident::new("peepmatic", Span::call_site())); - for i in (0..attrs.len()).rev() { - if attrs[i].path != ident { - continue; - } - let attr = attrs.remove(i); - let group = match attr.tokens.into_iter().next().unwrap() { - proc_macro2::TokenTree::Group(g) => g, - _ => panic!("#[peepmatic(...)] expected"), - }; - ret.extend(group.stream()); - ret.extend(quote! { , }); - } - return ret.into(); -} - -impl PeepmaticOpts { - pub(crate) fn from_attrs(attrs: &mut Vec) -> syn::Result { - syn::parse(peepmatic_attrs(attrs)) - } -} diff --git a/cranelift/peepmatic/crates/macro/src/span.rs b/cranelift/peepmatic/crates/macro/src/span.rs deleted file mode 100644 index a6cea5a262..0000000000 --- a/cranelift/peepmatic/crates/macro/src/span.rs +++ /dev/null @@ -1,55 +0,0 @@ -use quote::quote; -use syn::DeriveInput; -use syn::{parse_quote, GenericParam, Generics, Result}; - -pub fn derive_span(input: &DeriveInput) -> Result { - let ty = &input.ident; - - let body = match &input.data { - syn::Data::Struct(_) => quote! { self.span }, - syn::Data::Enum(e) => { - let variants = e.variants.iter().map(|v| match v.fields { - syn::Fields::Unnamed(ref fields) if fields.unnamed.len() == 1 => { - let variant = &v.ident; - quote! { #ty::#variant(x) => x.span(), } - } - _ => panic!( - "derive(Ast) on enums only supports variants with a single, unnamed field" - ), - }); - quote! { - match self { - #( #variants )* - } - } - } - syn::Data::Union(_) => { - panic!("derive(Ast) can only be used with structs and enums, not unions") - } - }; - - let generics = add_span_trait_bounds(input.generics.clone()); - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - - Ok(quote! { - impl #impl_generics Span for #ty #ty_generics #where_clause { - #[inline] - fn span(&self) -> wast::Span { - #body - } - } - }) -} - -// Add a bound `T: Span` to every type parameter `T`. -fn add_span_trait_bounds(mut generics: Generics) -> Generics { - for param in &mut generics.params { - if let GenericParam::Type(ref mut type_param) = *param { - if type_param.ident == "TOperator" { - continue; - } - type_param.bounds.push(parse_quote!(Span)); - } - } - generics -} diff --git a/cranelift/peepmatic/crates/runtime/Cargo.toml b/cranelift/peepmatic/crates/runtime/Cargo.toml deleted file mode 100644 index 44998bec06..0000000000 --- a/cranelift/peepmatic/crates/runtime/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "peepmatic-runtime" -version = "0.78.0" -authors = ["Nick Fitzgerald "] -edition = "2018" -license = "Apache-2.0 WITH LLVM-exception" -description = "Runtime support for peepmatic peephole optimizers" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -bincode = "1.2.1" -log = "0.4.8" -peepmatic-automata = { version = "=0.78.0", path = "../automata", features = ["serde"] } -peepmatic-traits = { version = "=0.78.0", path = "../traits" } -serde = { version = "1.0.105", features = ["derive"] } -thiserror = "1.0.15" -wast = { version = "38.0.0", optional = true } - -[dev-dependencies] -peepmatic-test-operator = { path = "../test-operator" } -serde_test = "1.0.114" - -[features] -# Enable support for a few extra methods that are required by the `peepmatic` -# crate when constructing peephole optimizers, but are not needed when simply -# using already-constructed peephole optimizers. -construct = ["wast"] diff --git a/cranelift/peepmatic/crates/runtime/LICENSE b/cranelift/peepmatic/crates/runtime/LICENSE deleted file mode 100644 index f9d81955f4..0000000000 --- a/cranelift/peepmatic/crates/runtime/LICENSE +++ /dev/null @@ -1,220 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - ---- LLVM Exceptions to the Apache 2.0 License ---- - -As an exception, if, as a result of your compiling your source code, portions -of this Software are embedded into an Object form of such source code, you -may redistribute such embedded portions in such Object form without complying -with the conditions of Sections 4(a), 4(b) and 4(d) of the License. - -In addition, if you combine or link compiled forms of this Software with -software that is licensed under the GPLv2 ("Combined Software") and if a -court of competent jurisdiction determines that the patent provision (Section -3), the indemnity provision (Section 9) or other Section of the License -conflicts with the conditions of the GPLv2, you may retroactively and -prospectively choose to deem waived or otherwise exclude such Section(s) of -the License, but only in their entirety and only with respect to the Combined -Software. - diff --git a/cranelift/peepmatic/crates/runtime/src/cc.rs b/cranelift/peepmatic/crates/runtime/src/cc.rs deleted file mode 100644 index d6cb489eab..0000000000 --- a/cranelift/peepmatic/crates/runtime/src/cc.rs +++ /dev/null @@ -1,92 +0,0 @@ -//! Condition codes. - -use serde::{Deserialize, Serialize}; -use std::convert::TryFrom; -use std::fmt; - -/// A condition code. -/// -/// This is a special kind of immediate for `icmp` instructions that dictate -/// which parts of the comparison result we care about. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[repr(u32)] -pub enum ConditionCode { - /// Equal. - // NB: We convert `ConditionCode` into `NonZeroU32`s with unchecked - // conversions; memory safety relies on no variant being zero. - Eq = 1, - - /// Not equal. - Ne, - - /// Signed less than. - Slt, - - /// Unsigned less than. - Ult, - - /// Signed greater than or equal. - Sge, - - /// Unsigned greater than or equal. - Uge, - - /// Signed greater than. - Sgt, - - /// Unsigned greater than. - Ugt, - - /// Signed less than or equal. - Sle, - - /// Unsigned less than or equal. - Ule, - - /// Overflow. - Of, - - /// No overflow. - Nof, -} - -impl TryFrom for ConditionCode { - type Error = &'static str; - - fn try_from(x: u32) -> Result { - Ok(match x { - x if Self::Eq as u32 == x => Self::Eq, - x if Self::Ne as u32 == x => Self::Ne, - x if Self::Slt as u32 == x => Self::Slt, - x if Self::Ult as u32 == x => Self::Ult, - x if Self::Sge as u32 == x => Self::Sge, - x if Self::Uge as u32 == x => Self::Uge, - x if Self::Sgt as u32 == x => Self::Sgt, - x if Self::Ugt as u32 == x => Self::Ugt, - x if Self::Sle as u32 == x => Self::Sle, - x if Self::Ule as u32 == x => Self::Ule, - x if Self::Of as u32 == x => Self::Of, - x if Self::Nof as u32 == x => Self::Nof, - _ => return Err("not a valid condition code value"), - }) - } -} - -impl fmt::Display for ConditionCode { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Eq => write!(f, "eq"), - Self::Ne => write!(f, "ne"), - Self::Slt => write!(f, "slt"), - Self::Ult => write!(f, "ult"), - Self::Sge => write!(f, "sge"), - Self::Uge => write!(f, "uge"), - Self::Sgt => write!(f, "sgt"), - Self::Ugt => write!(f, "ugt"), - Self::Sle => write!(f, "sle"), - Self::Ule => write!(f, "ule"), - Self::Of => write!(f, "of"), - Self::Nof => write!(f, "nof"), - } - } -} diff --git a/cranelift/peepmatic/crates/runtime/src/error.rs b/cranelift/peepmatic/crates/runtime/src/error.rs deleted file mode 100644 index 3453316238..0000000000 --- a/cranelift/peepmatic/crates/runtime/src/error.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! `Error` and `Result` types for this crate. - -use std::io; -use thiserror::Error; - -/// A result type containing `Ok(T)` or `Err(peepmatic_runtime::Error)`. -pub type Result = std::result::Result; - -/// Errors that `peepmatic_runtime` may generate. -#[derive(Debug, Error)] -#[error(transparent)] -pub struct Error { - #[from] - inner: Box, -} - -#[derive(Debug, Error)] -enum ErrorInner { - #[error(transparent)] - Io(#[from] io::Error), - - #[error(transparent)] - Bincode(#[from] bincode::Error), -} - -impl From for Error { - fn from(e: io::Error) -> Error { - let e: ErrorInner = e.into(); - e.into() - } -} - -impl From for Error { - fn from(e: bincode::Error) -> Error { - let e: ErrorInner = e.into(); - e.into() - } -} - -impl From for Error { - fn from(e: ErrorInner) -> Error { - Box::new(e).into() - } -} diff --git a/cranelift/peepmatic/crates/runtime/src/instruction_set.rs b/cranelift/peepmatic/crates/runtime/src/instruction_set.rs deleted file mode 100644 index b49d71da71..0000000000 --- a/cranelift/peepmatic/crates/runtime/src/instruction_set.rs +++ /dev/null @@ -1,144 +0,0 @@ -//! Interfacing with actual instructions. - -use crate::part::{Constant, Part}; -use crate::r#type::Type; -use std::fmt::Debug; -use std::hash::Hash; -use std::num::NonZeroU32; - -/// A trait for interfacing with actual instruction sequences. -/// -/// This trait enables both: -/// -/// * `peepmatic-runtime` to be used by `cranelift-codegen` without a circular -/// dependency from `peepmatic-runtime` to `cranelift-codegen` to get access -/// to Cranelift's IR types, and -/// -/// * enables us to write local tests that exercise peephole optimizers on a -/// simple, testing-only instruction set without pulling in all of Cranelift. -/// -/// Finally, this should also make the task of adding support for Cranelift's -/// new `MachInst` and vcode backend easier, since all that needs to be done is -/// "just" implementing this trait. (And probably add/modify some -/// `peepmatic_runtime::operation::Operation`s as well). -/// -/// ## Safety -/// -/// See doc comment for `instruction_result_bit_width`. -pub unsafe trait InstructionSet<'a> { - /// Mutable context passed into all trait methods. Can be whatever you want! - /// - /// In practice, this is a `FuncCursor` for `cranelift-codegen`'s trait - /// implementation. - type Context; - - /// An operator. - type Operator: 'static + Copy + Debug + Eq + Hash + Into; - - /// An instruction (or identifier for an instruction). - type Instruction: Copy + Debug + Eq; - - /// Replace the `old` instruction with `new`. - /// - /// `new` is either a `Part::Instruction` or a constant `Part::Boolean` or - /// `Part::Integer`. In the former case, it can directly replace `old`. In - /// the latter case, implementations of this trait should transparently - /// create an `iconst` or `bconst` instruction to wrap the given constant. - /// - /// `new` will never be `Part::ConditionCode`. - fn replace_instruction( - &self, - context: &mut Self::Context, - old: Self::Instruction, - new: Part, - ) -> Self::Instruction; - - /// Get the given instruction's operator. - /// - /// If the instruction isn't supported, then `None` should be returned. - /// - /// Additionally, if `Some` is returned, then the instruction's operands - /// must be pushed in order into `operands`. E.g. calling this method on - /// `(iadd $x $y)` would return `Some(iadd)` and extend `operands` with - /// `[$x, $y]`. - fn operator( - &self, - context: &mut Self::Context, - instr: Self::Instruction, - operands: &mut E, - ) -> Option - where - E: Extend>; - - /// Make a unary instruction. - /// - /// If the type is not given, then it should be inferred. - fn make_inst_1( - &self, - context: &mut Self::Context, - root: Self::Instruction, - operator: Self::Operator, - r#type: Type, - a: Part, - ) -> Self::Instruction; - - /// Make a binary instruction. - /// - /// Operands are given as immediates first and arguments following - /// them. Condition codes are treated as immediates. So if we are creating - /// an `iadd_imm` instruction, then `a` will be the constant integer - /// immediate and `b` will be the instruction whose result is the dynamic - /// argument. - fn make_inst_2( - &self, - context: &mut Self::Context, - root: Self::Instruction, - operator: Self::Operator, - r#type: Type, - a: Part, - b: Part, - ) -> Self::Instruction; - - /// Make a ternary instruction. - /// - /// Operands are given as immediates first and arguments following - /// them. Condition codes are treated as immediates. So if we are creating - /// an `icmp` instruction, then `a` will be the condition code, and `b` and - /// `c` will be instructions whose results are the dynamic arguments. - fn make_inst_3( - &self, - context: &mut Self::Context, - root: Self::Instruction, - operator: Self::Operator, - r#type: Type, - a: Part, - b: Part, - c: Part, - ) -> Self::Instruction; - - /// Try to resolve the given instruction into a constant value. - /// - /// If we can tell that the instruction returns a constant value, then - /// return that constant value as either a `Part::Boolean` or - /// `Part::Integer`. Otherwise, return `None`. - fn instruction_to_constant( - &self, - context: &mut Self::Context, - inst: Self::Instruction, - ) -> Option; - - /// Get the bit width of the given instruction's result. - /// - /// ## Safety - /// - /// There is code that makes memory-safety assumptions that the result is - /// always one of 1, 8, 16, 32, 64, or 128. Implementors must uphold this. - fn instruction_result_bit_width( - &self, - context: &mut Self::Context, - inst: Self::Instruction, - ) -> u8; - - /// Get the size of a native word in bits. - fn native_word_size_in_bits(&self, context: &mut Self::Context) -> u8; -} diff --git a/cranelift/peepmatic/crates/runtime/src/integer_interner.rs b/cranelift/peepmatic/crates/runtime/src/integer_interner.rs deleted file mode 100644 index 035027c2a2..0000000000 --- a/cranelift/peepmatic/crates/runtime/src/integer_interner.rs +++ /dev/null @@ -1,192 +0,0 @@ -//! Interner for (potentially large) integer values. -//! -//! We support matching on integers that can be represented by `u64`, but only -//! support automata results that fit in a `u32`. So we intern the (relatively -//! few compared to the full range of `u64`) integers we are matching against -//! here and then reference them by `IntegerId`. - -use serde::de::{Deserializer, SeqAccess, Visitor}; -use serde::ser::{SerializeSeq, Serializer}; -use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; -use std::fmt; -use std::marker::PhantomData; -use std::num::{NonZeroU16, NonZeroU32}; - -/// An identifier for an interned integer. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct IntegerId(#[doc(hidden)] pub NonZeroU16); - -/// An interner for integer values. -#[derive(Debug, Default)] -pub struct IntegerInterner { - // Note: we use `BTreeMap`s for deterministic serialization. - map: BTreeMap, - values: Vec, -} - -impl IntegerInterner { - /// Construct a new `IntegerInterner`. - #[inline] - pub fn new() -> Self { - Self::default() - } - - /// Intern a value into this `IntegerInterner`, returning its canonical - /// `IntegerId`. - #[inline] - pub fn intern(&mut self, value: impl Into) -> IntegerId { - debug_assert_eq!(self.map.len(), self.values.len()); - - let value = value.into(); - - if let Some(id) = self.map.get(&value) { - return *id; - } - - assert!((self.values.len() as u64) < (std::u16::MAX as u64)); - let id = IntegerId(unsafe { NonZeroU16::new_unchecked(self.values.len() as u16 + 1) }); - - self.values.push(value); - self.map.insert(value, id); - debug_assert_eq!(self.map.len(), self.values.len()); - - id - } - - /// Get the id of an already-interned integer, or `None` if it has not been - /// interned. - pub fn already_interned(&self, value: impl Into) -> Option { - let value = value.into(); - self.map.get(&value).copied() - } - - /// Lookup a previously interned integer by id. - #[inline] - pub fn lookup(&self, id: IntegerId) -> u64 { - let index = id.0.get() as usize - 1; - self.values[index] - } -} - -impl From for NonZeroU32 { - #[inline] - fn from(id: IntegerId) -> NonZeroU32 { - id.0.into() - } -} - -impl Serialize for IntegerInterner { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut seq = serializer.serialize_seq(Some(self.values.len()))?; - for p in &self.values { - seq.serialize_element(&p)?; - } - seq.end() - } -} - -impl<'de> Deserialize<'de> for IntegerInterner { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserializer.deserialize_seq(IntegerInternerVisitor { - marker: PhantomData, - }) - } -} - -struct IntegerInternerVisitor { - marker: PhantomData IntegerInterner>, -} - -impl<'de> Visitor<'de> for IntegerInternerVisitor { - type Value = IntegerInterner; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - write!( - formatter, - "a `peepmatic_runtime::integer_interner::IntegerInterner`" - ) - } - - fn visit_seq(self, mut access: M) -> Result - where - M: SeqAccess<'de>, - { - const DEFAULT_CAPACITY: usize = 16; - let capacity = access.size_hint().unwrap_or(DEFAULT_CAPACITY); - - let mut interner = IntegerInterner { - map: BTreeMap::new(), - values: Vec::with_capacity(capacity), - }; - - while let Some(path) = access.next_element::()? { - interner.intern(path); - } - - Ok(interner) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use serde_test::{assert_tokens, Token}; - use std::iter::successors; - - #[derive(Debug, Serialize, Deserialize)] - #[serde(transparent)] - pub struct OrderedIntegerInterner(IntegerInterner); - - impl PartialEq for OrderedIntegerInterner { - fn eq(&self, other: &OrderedIntegerInterner) -> bool { - self.0.values.iter().eq(other.0.values.iter()) - } - } - - fn intern_fib(interner: &mut IntegerInterner, skip: usize, take: usize) { - successors(Some((0, 1)), |(a, b): &(u64, u64)| { - a.checked_add(*b).map(|c| (*b, c)) - }) - .skip(skip) - .take(take) - .for_each(|(i, _)| { - interner.intern(i); - }) - } - - #[test] - fn test_ser_de_empty_interner() { - let interner = IntegerInterner::new(); - - assert_tokens( - &OrderedIntegerInterner(interner), - &[Token::Seq { len: Some(0) }, Token::SeqEnd], - ); - } - - #[test] - fn test_ser_de_fibonacci_interner() { - let mut interner = IntegerInterner::new(); - intern_fib(&mut interner, 10, 5); - - assert_tokens( - &OrderedIntegerInterner(interner), - &[ - Token::Seq { len: Some(5) }, - Token::U64(55), - Token::U64(89), - Token::U64(144), - Token::U64(233), - Token::U64(377), - Token::SeqEnd, - ], - ); - } -} diff --git a/cranelift/peepmatic/crates/runtime/src/lib.rs b/cranelift/peepmatic/crates/runtime/src/lib.rs deleted file mode 100644 index 5689dd2d20..0000000000 --- a/cranelift/peepmatic/crates/runtime/src/lib.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! Runtime support for `peepmatic`'s peephole optimizers. -//! -//! This crate contains everything required to use a `peepmatic`-generated -//! peephole optimizer. -//! -//! ## Why is this a different crate from `peepmatic`? -//! -//! In short: build times and code size. -//! -//! If you are just using a peephole optimizer, you shouldn't need the functions -//! to construct it from scratch from the DSL (and the implied code size and -//! compilation time), let alone even build it at all. You should just -//! deserialize an already-built peephole optimizer, and then use it. -//! -//! That's all that is contained here in this crate. - -#![deny(missing_docs)] -#![deny(missing_debug_implementations)] - -pub mod cc; -pub mod error; -pub mod instruction_set; -pub mod integer_interner; -pub mod linear; -pub mod optimizations; -pub mod optimizer; -pub mod part; -pub mod r#type; -pub mod unquote; - -pub use error::{Error, Result}; -pub use optimizations::PeepholeOptimizations; -pub use optimizer::PeepholeOptimizer; diff --git a/cranelift/peepmatic/crates/runtime/src/linear.rs b/cranelift/peepmatic/crates/runtime/src/linear.rs deleted file mode 100644 index 575edde9e9..0000000000 --- a/cranelift/peepmatic/crates/runtime/src/linear.rs +++ /dev/null @@ -1,241 +0,0 @@ -//! A linear IR for optimizations. -//! -//! This IR is designed such that it should be easy to combine multiple linear -//! optimizations into a single automata. -//! -//! See also `src/linearize.rs` for the AST to linear IR translation pass. - -use crate::cc::ConditionCode; -use crate::integer_interner::{IntegerId, IntegerInterner}; -use crate::r#type::{BitWidth, Type}; -use crate::unquote::UnquoteOperator; -use serde::{Deserialize, Serialize}; -use std::fmt::Debug; -use std::hash::Hash; -use std::num::NonZeroU32; - -/// A set of linear optimizations. -#[derive(Debug)] -pub struct Optimizations -where - TOperator: 'static + Copy + Debug + Eq + Hash, -{ - /// The linear optimizations. - pub optimizations: Vec>, - - /// The integer literals referenced by these optimizations. - pub integers: IntegerInterner, -} - -/// A linearized optimization. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Optimization -where - TOperator: 'static + Copy + Debug + Eq + Hash, -{ - /// The chain of match operations and expected results for this - /// optimization. - pub matches: Vec, - - /// Actions to perform, given that the operation resulted in the expected - /// value. - pub actions: Vec>, -} - -/// Match any value. -/// -/// This can be used to create fallback, wildcard-style transitions between -/// states. -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] -pub struct Else; - -/// The result of evaluating a `MatchOp`. -/// -/// This is either a specific non-zero `u32`, or a fallback that matches -/// everything. -pub type MatchResult = Result; - -/// Convert a boolean to a `MatchResult`. -#[inline] -pub fn bool_to_match_result(b: bool) -> MatchResult { - let b = b as u32; - unsafe { Ok(NonZeroU32::new_unchecked(b + 1)) } -} - -/// A partial match of an optimization's LHS. -/// -/// An match is composed of a matching operation and the expected result of that -/// operation. Each match will basically become a state and a transition edge -/// out of that state in the final automata. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Match { - /// The matching operation to perform. - pub operation: MatchOp, - - /// The expected result of our matching operation, that enables us to - /// continue to the next match, or `Else` for "don't care" wildcard-style - /// matching. - pub expected: MatchResult, -} - -/// A matching operation to be performed on some Cranelift instruction as part -/// of determining whether an optimization is applicable. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] -pub enum MatchOp { - /// Switch on the opcode of an instruction. - /// - /// Upon successfully matching an instruction's opcode, bind each of its - /// operands to a LHS temporary. - Opcode(LhsId), - - /// Does an instruction have a constant value? - IsConst(LhsId), - - /// Is the constant value a power of two? - IsPowerOfTwo(LhsId), - - /// Switch on the bit width of a value. - BitWidth(LhsId), - - /// Does the value fit in our target architecture's native word size? - FitsInNativeWord(LhsId), - - /// Are the instructions (or immediates) the same? - Eq(LhsId, LhsId), - - /// Switch on the constant integer value of an instruction. - IntegerValue(LhsId), - - /// Switch on the constant boolean value of an instruction. - BooleanValue(LhsId), - - /// Switch on a condition code. - ConditionCode(LhsId), - - /// No operation. Always evaluates to `Else`. - /// - /// Never appears in real optimizations; nonetheless required to support - /// corner cases of the DSL, such as a LHS pattern that is nothing but a - /// variable. - Nop, -} - -/// A canonicalized identifier for a left-hand side value that was bound in a -/// pattern. -/// -/// These are defined in a pre-order traversal of the LHS pattern by successful -/// `MatchOp::Opcode` matches. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] -pub struct LhsId(pub u16); - -/// A canonicalized identifier for a right-hand side value. -/// -/// These are defined by RHS actions. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct RhsId(pub u16); - -/// An action to perform when transitioning between states in the automata. -/// -/// When evaluating actions, the `i^th` action implicitly defines the -/// `RhsId(i)`. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub enum Action { - /// Reuse something from the left-hand side. - GetLhs { - /// The left-hand side instruction or value. - lhs: LhsId, - }, - - /// Perform compile-time evaluation. - UnaryUnquote { - /// The unquote operator. - operator: UnquoteOperator, - /// The constant operand to this unquote. - operand: RhsId, - }, - - /// Perform compile-time evaluation. - BinaryUnquote { - /// The unquote operator. - operator: UnquoteOperator, - /// The constant operands to this unquote. - operands: [RhsId; 2], - }, - - /// Create an integer constant. - MakeIntegerConst { - /// The constant integer value. - value: IntegerId, - /// The bit width of this constant. - bit_width: BitWidth, - }, - - /// Create a boolean constant. - MakeBooleanConst { - /// The constant boolean value. - value: bool, - /// The bit width of this constant. - bit_width: BitWidth, - }, - - /// Create a condition code. - MakeConditionCode { - /// The condition code. - cc: ConditionCode, - }, - - /// Make a unary instruction. - MakeUnaryInst { - /// The operand for this instruction. - operand: RhsId, - /// The type of this instruction's result. - r#type: Type, - /// The operator for this instruction. - operator: TOperator, - }, - - /// Make a binary instruction. - MakeBinaryInst { - /// The opcode for this instruction. - operator: TOperator, - /// The type of this instruction's result. - r#type: Type, - /// The operands for this instruction. - operands: [RhsId; 2], - }, - - /// Make a ternary instruction. - MakeTernaryInst { - /// The opcode for this instruction. - operator: TOperator, - /// The type of this instruction's result. - r#type: Type, - /// The operands for this instruction. - operands: [RhsId; 3], - }, -} - -#[cfg(test)] -mod tests { - use super::*; - use peepmatic_test_operator::TestOperator; - - // These types all end up in the automaton, so we should take care that they - // are small and don't fill up the data cache (or take up too much - // serialized size). - - #[test] - fn match_result_size() { - assert_eq!(std::mem::size_of::(), 4); - } - - #[test] - fn match_op_size() { - assert_eq!(std::mem::size_of::(), 6); - } - - #[test] - fn action_size() { - assert_eq!(std::mem::size_of::>(), 16); - } -} diff --git a/cranelift/peepmatic/crates/runtime/src/optimizations.rs b/cranelift/peepmatic/crates/runtime/src/optimizations.rs deleted file mode 100644 index 02ec18dc38..0000000000 --- a/cranelift/peepmatic/crates/runtime/src/optimizations.rs +++ /dev/null @@ -1,93 +0,0 @@ -//! Compiled peephole optimizations. - -use crate::error::Result; -use crate::instruction_set::InstructionSet; -use crate::integer_interner::IntegerInterner; -use crate::linear::{Action, MatchOp, MatchResult}; -use crate::optimizer::PeepholeOptimizer; -use peepmatic_automata::Automaton; -use serde::{Deserialize, Serialize}; -use std::fmt::Debug; -use std::hash::Hash; - -#[cfg(feature = "construct")] -use std::fs; -#[cfg(feature = "construct")] -use std::path::Path; - -/// A compiled set of peephole optimizations. -/// -/// This is the compilation result of the `peepmatic` crate, after its taken a -/// bunch of optimizations written in the DSL and lowered and combined them. -#[derive(Debug, Serialize, Deserialize)] -pub struct PeepholeOptimizations -where - TOperator: 'static + Copy + Debug + Eq + Hash, -{ - /// Not all integers we're matching on fit in the `u32` that we use as the - /// result of match operations. So we intern them and refer to them by id. - pub integers: IntegerInterner, - - /// The underlying automata for matching optimizations' left-hand sides, and - /// building up the corresponding right-hand side. - pub automata: Automaton]>>, -} - -impl PeepholeOptimizations -where - TOperator: 'static + Copy + Debug + Eq + Hash, -{ - /// Deserialize a `PeepholeOptimizations` from bytes. - pub fn deserialize<'a>(serialized: &'a [u8]) -> Result - where - TOperator: serde::Deserialize<'a>, - { - let peep_opt: Self = bincode::deserialize(serialized)?; - Ok(peep_opt) - } - - /// Serialize these peephole optimizations out to the file at the given path. - /// - /// Requires that the `"construct"` cargo feature is enabled. - #[cfg(feature = "construct")] - pub fn serialize_to_file(&self, path: &Path) -> Result<()> - where - TOperator: serde::Serialize, - { - let file = fs::File::create(path)?; - bincode::serialize_into(file, self)?; - Ok(()) - } -} - -impl PeepholeOptimizations -where - TOperator: 'static + Copy + Debug + Eq + Hash, -{ - /// Create a new peephole optimizer instance from this set of peephole - /// optimizations. - /// - /// The peephole optimizer instance can be used to apply these peephole - /// optimizations. When checking multiple instructions for whether they can - /// be optimized, it is more performant to reuse a single peephole optimizer - /// instance, rather than create a new one for each instruction. Reusing the - /// peephole optimizer instance allows the reuse of a few internal - /// allocations. - pub fn optimizer<'peep, 'ctx, TInstructionSet>( - &'peep self, - instr_set: TInstructionSet, - ) -> PeepholeOptimizer<'peep, 'ctx, TInstructionSet> - where - TInstructionSet: InstructionSet<'ctx, Operator = TOperator>, - TOperator: Into, - { - PeepholeOptimizer { - peep_opt: self, - instr_set, - left_hand_sides: vec![], - right_hand_sides: vec![], - actions: vec![], - backtracking_states: vec![], - } - } -} diff --git a/cranelift/peepmatic/crates/runtime/src/optimizer.rs b/cranelift/peepmatic/crates/runtime/src/optimizer.rs deleted file mode 100644 index 63a15c69f1..0000000000 --- a/cranelift/peepmatic/crates/runtime/src/optimizer.rs +++ /dev/null @@ -1,551 +0,0 @@ -//! An optimizer for a set of peephole optimizations. - -use crate::instruction_set::InstructionSet; -use crate::linear::{bool_to_match_result, Action, Else, MatchOp, MatchResult}; -use crate::optimizations::PeepholeOptimizations; -use crate::part::{Constant, Part}; -use crate::r#type::{BitWidth, Type}; -use crate::unquote::UnquoteOperator; -use peepmatic_automata::State; -use std::convert::TryFrom; -use std::fmt::{self, Debug}; -use std::mem; -use std::num::NonZeroU32; - -/// A peephole optimizer instance that can apply a set of peephole -/// optimizations to instructions. -/// -/// These are created from a set of peephole optimizations with the -/// [`PeepholeOptimizations::optimizer`] method. -/// -/// Reusing an instance when applying peephole optimizations to different -/// instruction sequences means that you reuse internal allocations that are -/// used to match left-hand sides and build up right-hand sides. -pub struct PeepholeOptimizer<'peep, 'ctx, TInstructionSet> -where - TInstructionSet: InstructionSet<'ctx>, -{ - pub(crate) peep_opt: &'peep PeepholeOptimizations, - pub(crate) instr_set: TInstructionSet, - pub(crate) left_hand_sides: Vec>, - pub(crate) right_hand_sides: Vec>, - pub(crate) actions: Vec>, - pub(crate) backtracking_states: Vec<(State, usize, usize)>, -} - -impl<'peep, 'ctx, TInstructionSet> Debug for PeepholeOptimizer<'peep, 'ctx, TInstructionSet> -where - TInstructionSet: InstructionSet<'ctx>, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let PeepholeOptimizer { - peep_opt, - instr_set: _, - left_hand_sides, - right_hand_sides, - actions, - backtracking_states, - } = self; - f.debug_struct("PeepholeOptimizer") - .field("peep_opt", peep_opt) - .field("instr_set", &"_") - .field("left_hand_sides", left_hand_sides) - .field("right_hand_sides", right_hand_sides) - .field("actions", actions) - .field("backtracking_states", backtracking_states) - .finish() - } -} - -impl<'peep, 'ctx, TInstructionSet> PeepholeOptimizer<'peep, 'ctx, TInstructionSet> -where - TInstructionSet: InstructionSet<'ctx>, -{ - fn eval_unquote_1(&self, operator: UnquoteOperator, a: Constant) -> Constant { - use Constant::*; - - macro_rules! map_int { - ( $c:expr , | $x:ident | $e:expr ) => { - match $c { - Int($x, w) => Int($e, w), - Bool(..) => panic!("not an integer"), - } - }; - } - - match operator { - UnquoteOperator::Log2 => map_int!(a, |x| x.trailing_zeros() as _), - UnquoteOperator::Neg => map_int!(a, |x| x.wrapping_neg()), - UnquoteOperator::Band - | UnquoteOperator::Bor - | UnquoteOperator::Bxor - | UnquoteOperator::Iadd - | UnquoteOperator::Imul - | UnquoteOperator::Isub => unreachable!("not a unary unquote operator: {:?}", operator), - } - } - - fn eval_unquote_2(&self, operator: UnquoteOperator, a: Constant, b: Constant) -> Constant { - use Constant::*; - - macro_rules! fold_ints { - ( $c1:expr , $c2:expr , | $x:ident , $y:ident | $e:expr ) => { - match ($c1, $c2) { - (Int($x, w1), Int($y, w2)) if w1 == w2 => Int($e, w1), - _ => panic!("not two integers of the same width"), - } - }; - } - - match operator { - UnquoteOperator::Band => fold_ints!(a, b, |x, y| x & y), - UnquoteOperator::Bor => fold_ints!(a, b, |x, y| x | y), - UnquoteOperator::Bxor => fold_ints!(a, b, |x, y| x ^ y), - UnquoteOperator::Iadd => fold_ints!(a, b, |x, y| x.wrapping_add(y)), - UnquoteOperator::Imul => fold_ints!(a, b, |x, y| x.wrapping_mul(y)), - UnquoteOperator::Isub => fold_ints!(a, b, |x, y| x.wrapping_sub(y)), - UnquoteOperator::Log2 | UnquoteOperator::Neg => { - unreachable!("not a binary unquote operator: {:?}", operator) - } - } - } - - fn eval_actions( - &mut self, - context: &mut TInstructionSet::Context, - root: TInstructionSet::Instruction, - ) { - let mut actions = mem::replace(&mut self.actions, vec![]); - - for action in actions.drain(..) { - log::trace!("Evaluating action: {:?}", action); - match action { - Action::GetLhs { lhs } => { - let lhs = self.left_hand_sides[lhs.0 as usize]; - self.right_hand_sides.push(lhs); - } - Action::UnaryUnquote { operator, operand } => { - let operand = self.right_hand_sides[operand.0 as usize]; - let operand = match operand { - Part::Instruction(i) => self - .instr_set - .instruction_to_constant(context, i) - .expect("cannot convert instruction to constant for unquote operand"), - Part::Constant(c) => c, - Part::ConditionCode(_) => { - panic!("cannot use a condition code as an unquote operand") - } - }; - let result = self.eval_unquote_1(operator, operand); - self.right_hand_sides.push(result.into()); - } - Action::BinaryUnquote { operator, operands } => { - let a = self.right_hand_sides[operands[0].0 as usize]; - let a = match a { - Part::Instruction(i) => self - .instr_set - .instruction_to_constant(context, i) - .expect("cannot convert instruction to constant for unquote operand"), - Part::Constant(c) => c, - Part::ConditionCode(_) => { - panic!("cannot use a condition code as an unquote operand") - } - }; - - let b = self.right_hand_sides[operands[1].0 as usize]; - let b = match b { - Part::Instruction(i) => self - .instr_set - .instruction_to_constant(context, i) - .expect("cannot convert instruction to constant for unquote operand"), - Part::Constant(c) => c, - Part::ConditionCode(_) => { - panic!("cannot use a condition code as an unquote operand") - } - }; - - let result = self.eval_unquote_2(operator, a, b); - self.right_hand_sides.push(result.into()); - } - Action::MakeIntegerConst { - value, - mut bit_width, - } => { - let value = self.peep_opt.integers.lookup(value); - if bit_width.is_polymorphic() { - bit_width = BitWidth::try_from( - self.instr_set.instruction_result_bit_width(context, root), - ) - .unwrap(); - } - self.right_hand_sides - .push(Constant::Int(value, bit_width).into()); - } - Action::MakeBooleanConst { - value, - mut bit_width, - } => { - if bit_width.is_polymorphic() { - bit_width = BitWidth::try_from( - self.instr_set.instruction_result_bit_width(context, root), - ) - .unwrap(); - } - self.right_hand_sides - .push(Constant::Bool(value, bit_width).into()); - } - Action::MakeConditionCode { cc } => { - self.right_hand_sides.push(Part::ConditionCode(cc)); - } - Action::MakeUnaryInst { - operator, - r#type: - Type { - kind, - mut bit_width, - }, - operand, - } => { - if bit_width.is_polymorphic() { - bit_width = BitWidth::try_from( - self.instr_set.instruction_result_bit_width(context, root), - ) - .unwrap(); - } - let ty = Type { kind, bit_width }; - let operand = self.right_hand_sides[operand.0 as usize]; - let inst = self - .instr_set - .make_inst_1(context, root, operator, ty, operand); - self.right_hand_sides.push(Part::Instruction(inst)); - } - Action::MakeBinaryInst { - operator, - r#type: - Type { - kind, - mut bit_width, - }, - operands, - } => { - if bit_width.is_polymorphic() { - bit_width = BitWidth::try_from( - self.instr_set.instruction_result_bit_width(context, root), - ) - .unwrap(); - } - let ty = Type { kind, bit_width }; - let a = self.right_hand_sides[operands[0].0 as usize]; - let b = self.right_hand_sides[operands[1].0 as usize]; - let inst = self - .instr_set - .make_inst_2(context, root, operator, ty, a, b); - self.right_hand_sides.push(Part::Instruction(inst)); - } - Action::MakeTernaryInst { - operator, - r#type: - Type { - kind, - mut bit_width, - }, - operands, - } => { - if bit_width.is_polymorphic() { - bit_width = BitWidth::try_from( - self.instr_set.instruction_result_bit_width(context, root), - ) - .unwrap(); - } - let ty = Type { kind, bit_width }; - let a = self.right_hand_sides[operands[0].0 as usize]; - let b = self.right_hand_sides[operands[1].0 as usize]; - let c = self.right_hand_sides[operands[2].0 as usize]; - let inst = self - .instr_set - .make_inst_3(context, root, operator, ty, a, b, c); - self.right_hand_sides.push(Part::Instruction(inst)); - } - } - } - - // Reuse the heap elements allocation. - self.actions = actions; - } - - fn eval_match_op( - &mut self, - context: &mut TInstructionSet::Context, - root: TInstructionSet::Instruction, - match_op: MatchOp, - ) -> MatchResult { - use crate::linear::MatchOp::*; - - log::trace!("Evaluating match operation: {:?}", match_op); - let result: MatchResult = (|| match match_op { - Opcode(id) => { - let part = self.left_hand_sides[id.0 as usize]; - let inst = part.as_instruction().ok_or(Else)?; - let op = self - .instr_set - .operator(context, inst, &mut self.left_hand_sides) - .ok_or(Else)?; - Ok(op.into()) - } - IsConst(id) => { - let part = self.left_hand_sides[id.0 as usize]; - let is_const = match part { - Part::Instruction(i) => { - self.instr_set.instruction_to_constant(context, i).is_some() - } - Part::ConditionCode(_) | Part::Constant(_) => true, - }; - bool_to_match_result(is_const) - } - IsPowerOfTwo(id) => { - let part = self.left_hand_sides[id.0 as usize]; - match part { - Part::Constant(c) => { - let is_pow2 = c.as_int().unwrap().is_power_of_two(); - bool_to_match_result(is_pow2) - } - Part::Instruction(i) => { - let c = self - .instr_set - .instruction_to_constant(context, i) - .ok_or(Else)?; - let is_pow2 = c.as_int().unwrap().is_power_of_two(); - bool_to_match_result(is_pow2) - } - Part::ConditionCode(_) => unreachable!("IsPowerOfTwo on a condition code"), - } - } - BitWidth(id) => { - let part = self.left_hand_sides[id.0 as usize]; - let bit_width = match part { - Part::Instruction(i) => self.instr_set.instruction_result_bit_width(context, i), - Part::Constant(Constant::Int(_, w)) | Part::Constant(Constant::Bool(_, w)) => { - w.fixed_width().unwrap_or_else(|| { - self.instr_set.instruction_result_bit_width(context, root) - }) - } - Part::ConditionCode(_) => panic!("BitWidth on condition code"), - }; - debug_assert!( - bit_width != 0, - "`InstructionSet` implementors must uphold the contract that \ - `instruction_result_bit_width` returns one of 1, 8, 16, 32, 64, or 128" - ); - Ok(unsafe { NonZeroU32::new_unchecked(bit_width as u32) }) - } - FitsInNativeWord(id) => { - let native_word_size = self.instr_set.native_word_size_in_bits(context); - debug_assert!(native_word_size.is_power_of_two()); - - let part = self.left_hand_sides[id.0 as usize]; - let fits = match part { - Part::Instruction(i) => { - let size = self.instr_set.instruction_result_bit_width(context, i); - size <= native_word_size - } - Part::Constant(c) => { - let root_width = self.instr_set.instruction_result_bit_width(context, root); - let size = c.bit_width(root_width); - size <= native_word_size - } - Part::ConditionCode(_) => panic!("FitsInNativeWord on condition code"), - }; - bool_to_match_result(fits) - } - Eq(a, b) => { - let part_a = self.left_hand_sides[a.0 as usize]; - let part_b = self.left_hand_sides[b.0 as usize]; - let eq = match (part_a, part_b) { - (Part::Instruction(inst), Part::Constant(c1)) - | (Part::Constant(c1), Part::Instruction(inst)) => { - match self.instr_set.instruction_to_constant(context, inst) { - Some(c2) => c1 == c2, - None => false, - } - } - (a, b) => a == b, - }; - bool_to_match_result(eq) - } - IntegerValue(id) => { - let part = self.left_hand_sides[id.0 as usize]; - match part { - Part::Constant(c) => { - let x = c.as_int().ok_or(Else)?; - let id = self.peep_opt.integers.already_interned(x).ok_or(Else)?; - Ok(id.into()) - } - Part::Instruction(i) => { - let c = self - .instr_set - .instruction_to_constant(context, i) - .ok_or(Else)?; - let x = c.as_int().ok_or(Else)?; - let id = self.peep_opt.integers.already_interned(x).ok_or(Else)?; - Ok(id.into()) - } - Part::ConditionCode(_) => unreachable!("IntegerValue on condition code"), - } - } - BooleanValue(id) => { - let part = self.left_hand_sides[id.0 as usize]; - match part { - Part::Constant(c) => { - let b = c.as_bool().ok_or(Else)?; - bool_to_match_result(b) - } - Part::Instruction(i) => { - let c = self - .instr_set - .instruction_to_constant(context, i) - .ok_or(Else)?; - let b = c.as_bool().ok_or(Else)?; - bool_to_match_result(b) - } - Part::ConditionCode(_) => unreachable!("IntegerValue on condition code"), - } - } - ConditionCode(id) => { - let part = self.left_hand_sides[id.0 as usize]; - let cc = part.as_condition_code().ok_or(Else)?; - let cc = cc as u32; - debug_assert!(cc != 0); - Ok(unsafe { NonZeroU32::new_unchecked(cc) }) - } - MatchOp::Nop => Err(Else), - })(); - log::trace!("Evaluated match operation: {:?} = {:?}", match_op, result); - result - } - - /// Attempt to apply a single peephole optimization to the given root - /// instruction. - /// - /// If an optimization is applied, then the `root` is replaced with the - /// optimization's right-hand side, and the root of the right-hand side is - /// returned as `Some`. - /// - /// If no optimization's left-hand side matches `root`, then `root` is left - /// untouched and `None` is returned. - pub fn apply_one( - &mut self, - context: &mut TInstructionSet::Context, - root: TInstructionSet::Instruction, - ) -> Option { - log::trace!("PeepholeOptimizer::apply_one"); - - self.backtracking_states.clear(); - self.actions.clear(); - self.right_hand_sides.clear(); - self.left_hand_sides.clear(); - - // `LhsId(0)` is always the root. - self.left_hand_sides.push(Part::Instruction(root)); - - let mut r#final = None; - - let mut query = self.peep_opt.automata.query(); - loop { - log::trace!("Current state: {:?}", query.current_state()); - log::trace!( - "self.left_hand_sides = {:#?}", - self.left_hand_sides.iter().enumerate().collect::>() - ); - - if query.is_in_final_state() { - // If we're in a final state (which means an optimization is - // applicable) then record that fact, but keep going. We don't - // want to stop yet, because we might discover another, - // more-specific optimization that is also applicable if we keep - // going. And we always want to apply the most specific - // optimization that matches. - log::trace!("Found a match at state {:?}", query.current_state()); - r#final = Some((query.current_state(), self.actions.len())); - } - - // Anything following a `Else` transition doesn't care about the - // result of this match operation, so if we partially follow the - // current non-`Else` path, but don't ultimately find a matching - // optimization, we want to be able to backtrack to this state and - // then try taking the `Else` transition. - if query.has_transition_on(&Err(Else)) { - self.backtracking_states.push(( - query.current_state(), - self.actions.len(), - self.left_hand_sides.len(), - )); - } - - let match_op = match query.current_state_data() { - None => break, - Some(op) => op, - }; - - let input = self.eval_match_op(context, root, *match_op); - - let actions = if let Some(actions) = query.next(&input) { - actions - } else if r#final.is_some() { - break; - } else if let Some((state, actions_len, lhs_len)) = self.backtracking_states.pop() { - query.go_to_state(state); - self.actions.truncate(actions_len); - self.left_hand_sides.truncate(lhs_len); - query - .next(&Err(Else)) - .expect("backtracking states always have `Else` transitions") - } else { - break; - }; - - self.actions.extend(actions.iter().copied()); - } - - // If `final` is none, then we didn't encounter any final states, so - // there are no applicable optimizations. - let (final_state, actions_len) = match r#final { - Some(f) => f, - None => { - log::trace!("No optimizations matched"); - return None; - } - }; - - // Go to the last final state we saw, reset the LHS and RHS to how - // they were at the time we saw the final state, and process the - // final actions. - self.actions.truncate(actions_len); - query.go_to_state(final_state); - let final_actions = query.finish().expect("should be in a final state"); - self.actions.extend(final_actions.iter().copied()); - self.eval_actions(context, root); - - // And finally, the root of the RHS for this optimization is the - // last entry in `self.right_hand_sides`, so replace the old root - // instruction with this one! - let result = self.right_hand_sides.pop().unwrap(); - let new_root = self.instr_set.replace_instruction(context, root, result); - Some(new_root) - } - - /// Keep applying peephole optimizations to the given instruction until none - /// can be applied anymore. - pub fn apply_all( - &mut self, - context: &mut TInstructionSet::Context, - mut inst: TInstructionSet::Instruction, - ) { - loop { - if let Some(new_inst) = self.apply_one(context, inst) { - inst = new_inst; - } else { - break; - } - } - } -} diff --git a/cranelift/peepmatic/crates/runtime/src/part.rs b/cranelift/peepmatic/crates/runtime/src/part.rs deleted file mode 100644 index 6190b8e980..0000000000 --- a/cranelift/peepmatic/crates/runtime/src/part.rs +++ /dev/null @@ -1,128 +0,0 @@ -//! Parts of instructions. - -use crate::cc::ConditionCode; -use crate::r#type::BitWidth; -use std::fmt::Debug; - -/// A constant value. -/// -/// Whether an integer is interpreted as signed or unsigned depends on the -/// operations applied to it. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum Constant { - /// A boolean of the given width. - Bool(bool, BitWidth), - - /// An integer constant of the given width, - Int(u64, BitWidth), -} - -/// A part of an instruction, or a whole instruction itself. -/// -/// These are the different values that can be matched in an optimization's -/// left-hand side and then built up in its right-hand side. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum Part -where - I: Copy + Debug + Eq, -{ - /// An instruction (or result of an instruction). - Instruction(I), - - /// A constant value. - Constant(Constant), - - /// A condition code. - ConditionCode(ConditionCode), -} - -impl From for Part -where - I: Copy + Debug + Eq, -{ - #[inline] - fn from(c: Constant) -> Part { - Part::Constant(c) - } -} - -impl From for Part -where - I: Copy + Debug + Eq, -{ - #[inline] - fn from(c: ConditionCode) -> Part { - Part::ConditionCode(c) - } -} - -macro_rules! accessors { - ( $( $variant:ident , $result:ty , $getter:ident , $is:ident , $unwrap:ident ; )* ) => { - $( - #[inline] - #[allow(missing_docs)] - pub fn $getter(&self) -> Option<$result> { - match *self { - Self::$variant(x, ..) => Some(x), - _ => None - } - } - - #[inline] - #[allow(missing_docs)] - pub fn $is(&self) -> bool { - self.$getter().is_some() - } - - #[inline] - #[allow(missing_docs)] - pub fn $unwrap(&self) -> $result { - self.$getter().expect(concat!("failed to unwrap `", stringify!($variant), "`")) - } - )* - } -} - -impl Constant { - /// If this is any kind of integer constant, get it as a 64-bit unsigned - /// integer. - pub fn as_int(&self) -> Option { - match *self { - Constant::Bool(..) => None, - Constant::Int(x, _) => Some(x), - } - } - - /// If this is any kind of boolean constant, get its value. - pub fn as_bool(&self) -> Option { - match *self { - Constant::Bool(b, _) => Some(b), - Constant::Int(..) => None, - } - } - - /// The number of bits required to represent this constant value's type. - pub fn bit_width(&self, root_width: u8) -> u8 { - match *self { - Constant::Bool(_, w) | Constant::Int(_, w) => { - if let Some(w) = w.fixed_width() { - w - } else { - debug_assert!(w.is_polymorphic()); - root_width - } - } - } - } -} - -impl Part -where - I: Copy + Debug + Eq, -{ - accessors! { - Instruction, I, as_instruction, is_instruction, unwrap_instruction; - Constant, Constant, as_constant, is_constant, unwrap_constant; - ConditionCode, ConditionCode, as_condition_code, is_condition_code, unwrap_condition_code; - } -} diff --git a/cranelift/peepmatic/crates/runtime/src/type.rs b/cranelift/peepmatic/crates/runtime/src/type.rs deleted file mode 100644 index e4a08c9851..0000000000 --- a/cranelift/peepmatic/crates/runtime/src/type.rs +++ /dev/null @@ -1,262 +0,0 @@ -//! Types. - -use serde::{Deserialize, Serialize}; -use std::convert::TryFrom; -use std::fmt; - -/// A bit width of a type. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[repr(u8)] -pub enum BitWidth { - /// Polymorphic over bit width, with the same width as the root of the - /// optimization's LHS and RHS. - Polymorphic = 0, - - /// A fixed bit width of 1. - One = 1, - - /// A fixed bit width of 8. - Eight = 8, - - /// A fixed bit width of 16. - Sixteen = 16, - - /// A fixed bit width of 32. - ThirtyTwo = 32, - - /// A fixed bit width of 64. - SixtyFour = 64, - - /// A fixed bit width of 128. - OneTwentyEight = 128, -} - -/// The kind of type we are looking at: either an integer kind or boolean kind. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub enum Kind { - /// Integer kind. - Int, - - /// Boolean kind. - Bool, - - /// CPU flags kind. - CpuFlags, - - /// Void kind. - Void, -} - -/// A type a value or the result of an operation. -#[allow(missing_docs)] -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct Type { - /// This type's kind. - pub kind: Kind, - - /// This type's bit width. - pub bit_width: BitWidth, -} - -impl TryFrom for BitWidth { - type Error = &'static str; - - #[inline] - fn try_from(x: u8) -> Result { - Ok(match x { - 1 => Self::One, - 8 => Self::Eight, - 16 => Self::Sixteen, - 32 => Self::ThirtyTwo, - 64 => Self::SixtyFour, - 128 => Self::OneTwentyEight, - _ => return Err("not a valid bit width"), - }) - } -} - -impl fmt::Display for Type { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.kind { - Kind::CpuFlags => return write!(f, "cpu-flags"), - Kind::Void => return write!(f, "void"), - Kind::Int => write!(f, "i")?, - Kind::Bool => write!(f, "b")?, - } - match self.bit_width { - BitWidth::Polymorphic => write!(f, "NN"), - otherwise => write!(f, "{}", otherwise as u8), - } - } -} - -impl BitWidth { - /// Is this a polymorphic bit width? - pub fn is_polymorphic(&self) -> bool { - matches!(self, BitWidth::Polymorphic) - } - - /// Get this width in bits, unless this is a polymorphic bit width. - pub fn fixed_width(&self) -> Option { - if self.is_polymorphic() { - None - } else { - Some(*self as u8) - } - } -} - -macro_rules! type_ctors { - ( $( $( #[$attr:meta] )* $name:ident ( $kind:ident , $width:ident ) ; )* ) => { - $( - $( #[$attr] )* - pub const fn $name() -> Self { - Type { - kind: Kind::$kind, - bit_width: BitWidth::$width, - } - } - )* - } -} - -impl Type { - type_ctors! { - /// Get the `i1` type. - i1(Int, One); - /// Get the `i8` type. - i8(Int, Eight); - /// Get the `i16` type. - i16(Int, Sixteen); - /// Get the `i32` type. - i32(Int, ThirtyTwo); - /// Get the `i64` type. - i64(Int, SixtyFour); - /// Get the `i128` type. - i128(Int, OneTwentyEight); - /// Get the `b1` type. - b1(Bool, One); - /// Get the `b8` type. - b8(Bool, Eight); - /// Get the `b16` type. - b16(Bool, Sixteen); - /// Get the `b32` type. - b32(Bool, ThirtyTwo); - /// Get the `b64` type. - b64(Bool, SixtyFour); - /// Get the `b128` type. - b128(Bool, OneTwentyEight); - /// Get the CPU flags type. - cpu_flags(CpuFlags, One); - /// Get the void type. - void(Void, One); - } -} - -#[cfg(feature = "construct")] -mod tok { - use wast::custom_keyword; - custom_keyword!(b1); - custom_keyword!(b8); - custom_keyword!(b16); - custom_keyword!(b32); - custom_keyword!(b64); - custom_keyword!(b128); - custom_keyword!(i1); - custom_keyword!(i8); - custom_keyword!(i16); - custom_keyword!(i32); - custom_keyword!(i64); - custom_keyword!(i128); -} - -#[cfg(feature = "construct")] -impl<'a> wast::parser::Parse<'a> for Type { - fn parse(p: wast::parser::Parser<'a>) -> wast::parser::Result { - if p.peek::() { - p.parse::()?; - return Ok(Type { - kind: Kind::Bool, - bit_width: BitWidth::One, - }); - } - if p.peek::() { - p.parse::()?; - return Ok(Type { - kind: Kind::Bool, - bit_width: BitWidth::Eight, - }); - } - if p.peek::() { - p.parse::()?; - return Ok(Type { - kind: Kind::Bool, - bit_width: BitWidth::Sixteen, - }); - } - if p.peek::() { - p.parse::()?; - return Ok(Type { - kind: Kind::Bool, - bit_width: BitWidth::ThirtyTwo, - }); - } - if p.peek::() { - p.parse::()?; - return Ok(Type { - kind: Kind::Bool, - bit_width: BitWidth::SixtyFour, - }); - } - if p.peek::() { - p.parse::()?; - return Ok(Type { - kind: Kind::Bool, - bit_width: BitWidth::OneTwentyEight, - }); - } - if p.peek::() { - p.parse::()?; - return Ok(Type { - kind: Kind::Int, - bit_width: BitWidth::One, - }); - } - if p.peek::() { - p.parse::()?; - return Ok(Type { - kind: Kind::Int, - bit_width: BitWidth::Eight, - }); - } - if p.peek::() { - p.parse::()?; - return Ok(Type { - kind: Kind::Int, - bit_width: BitWidth::Sixteen, - }); - } - if p.peek::() { - p.parse::()?; - return Ok(Type { - kind: Kind::Int, - bit_width: BitWidth::ThirtyTwo, - }); - } - if p.peek::() { - p.parse::()?; - return Ok(Type { - kind: Kind::Int, - bit_width: BitWidth::SixtyFour, - }); - } - if p.peek::() { - p.parse::()?; - return Ok(Type { - kind: Kind::Int, - bit_width: BitWidth::OneTwentyEight, - }); - } - Err(p.error("expected an ascribed type")) - } -} diff --git a/cranelift/peepmatic/crates/runtime/src/unquote.rs b/cranelift/peepmatic/crates/runtime/src/unquote.rs deleted file mode 100644 index 216eb903b5..0000000000 --- a/cranelift/peepmatic/crates/runtime/src/unquote.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! Unquote operator definition. - -peepmatic_traits::define_operator! { - /// Compile-time unquote operators. - /// - /// These are used in the right-hand side to perform compile-time evaluation of - /// constants matched on the left-hand side. - #[allow(missing_docs)] - UnquoteOperator { - band => Band { - parameters(iNN, iNN); - result(iNN); - } - bor => Bor { - parameters(iNN, iNN); - result(iNN); - } - bxor => Bxor { - parameters(iNN, iNN); - result(iNN); - } - iadd => Iadd { - parameters(iNN, iNN); - result(iNN); - } - imul => Imul { - parameters(iNN, iNN); - result(iNN); - } - isub => Isub { - parameters(iNN, iNN); - result(iNN); - } - log2 => Log2 { - parameters(iNN); - result(iNN); - } - neg => Neg { - parameters(iNN); - result(iNN); - } - } - parse_cfg(feature = "construct"); -} diff --git a/cranelift/peepmatic/crates/souper/Cargo.toml b/cranelift/peepmatic/crates/souper/Cargo.toml deleted file mode 100644 index 4f2f080ce0..0000000000 --- a/cranelift/peepmatic/crates/souper/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "peepmatic-souper" -version = "0.78.0" -authors = ["Nick Fitzgerald "] -edition = "2018" -license = "Apache-2.0 WITH LLVM-exception" -description = "Converting Souper optimizations into Peepmatic DSL" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -anyhow = "1" -souper-ir = { version = "2.1.0", features = ["parse"] } -log = "0.4.8" - -[dev-dependencies] -peepmatic = { path = "../..", version = "=0.78.0" } -peepmatic-test-operator = { version = "=0.78.0", path = "../test-operator" } -wast = "38.0.0" diff --git a/cranelift/peepmatic/crates/souper/LICENSE b/cranelift/peepmatic/crates/souper/LICENSE deleted file mode 100644 index f9d81955f4..0000000000 --- a/cranelift/peepmatic/crates/souper/LICENSE +++ /dev/null @@ -1,220 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - ---- LLVM Exceptions to the Apache 2.0 License ---- - -As an exception, if, as a result of your compiling your source code, portions -of this Software are embedded into an Object form of such source code, you -may redistribute such embedded portions in such Object form without complying -with the conditions of Sections 4(a), 4(b) and 4(d) of the License. - -In addition, if you combine or link compiled forms of this Software with -software that is licensed under the GPLv2 ("Combined Software") and if a -court of competent jurisdiction determines that the patent provision (Section -3), the indemnity provision (Section 9) or other Section of the License -conflicts with the conditions of the GPLv2, you may retroactively and -prospectively choose to deem waived or otherwise exclude such Section(s) of -the License, but only in their entirety and only with respect to the Combined -Software. - diff --git a/cranelift/peepmatic/crates/souper/src/lib.rs b/cranelift/peepmatic/crates/souper/src/lib.rs deleted file mode 100644 index fa1e4a2748..0000000000 --- a/cranelift/peepmatic/crates/souper/src/lib.rs +++ /dev/null @@ -1,710 +0,0 @@ -//! Converting Souper optimizations into Peepmatic DSL. -//! -//! Conversion from Souper into Peepmatic is implemented with a straightforward, -//! top-down recursive traversal of the optimization's left- and right-hand side -//! expression DAGs. Most Souper instructions have a corresponding Peepmatic -//! instruction. If we run into an instruction where that isn't the case, we -//! skip that Souper optimization and move on to the next one. -//! -//! Note that Souper fully supports DAGs, for example: -//! -//! ```text -//! %0 = var -//! %1 = add 1, %0 -//! %2 = add %1, %1 ;; Two edges to `%1` makes this a DAG. -//! ``` -//! -//! On the other hand, Peepmatic only currently supports trees, so shared -//! subexpressions are duplicated: -//! -//! ```text -//! (iadd (iadd 1 $x) -//! (iadd 1 $x)) ;; The shared subexpression is duplicated. -//! ``` -//! -//! This does not affect correctness. - -#![deny(missing_docs)] - -use anyhow::{Context, Result}; -use souper_ir::ast; -use std::path::Path; - -/// Maximum recursion depth, to avoid blowing the stack. -const MAX_DEPTH: u8 = 50; - -/// Convert a file containing Souper optimizations into Peepmatic DSL. -pub fn convert_file(path: &Path) -> Result { - let souper = std::fs::read_to_string(path) - .with_context(|| format!("failed to read: {}", path.display()))?; - convert_str(&souper, Some(path)) -} - -/// Convert a string of Souper optimizations into Peepmatic DSL. -/// -/// The optional `filename` parameter is used for better error messages. -pub fn convert_str(souper: &str, filename: Option<&Path>) -> Result { - let mut peepmatic = String::new(); - - let replacements = souper_ir::parse::parse_replacements_str(souper, filename)?; - for replacement in replacements { - let (statements, lhs, rhs) = match replacement { - ast::Replacement::LhsRhs { - statements, - lhs, - rhs, - } => { - if !lhs.attributes.is_empty() { - log::warn!("cannot translate Souper attributes to Peepmatic DSL"); - continue; - } - (statements, lhs.value, rhs) - } - ast::Replacement::Cand { statements, cand } => { - if !cand.attributes.is_empty() { - log::warn!("cannot translate Souper attributes to Peepmatic DSL"); - continue; - } - let lhs = match cand.lhs { - ast::Operand::Value(v) => v, - ast::Operand::Constant(_) => { - log::warn!("optimization's LHS must not be a constant"); - continue; - } - }; - (statements, lhs, cand.rhs) - } - }; - - if let Some(s) = convert_replacement(&statements, lhs, rhs) { - peepmatic.push_str(&s); - peepmatic.push('\n'); - } - } - - Ok(peepmatic) -} - -fn convert_replacement( - statements: &ast::Arena, - lhs: ast::ValueId, - rhs: ast::Operand, -) -> Option { - let lhs = convert_lhs(statements, lhs)?; - let rhs = convert_rhs(statements, rhs)?; - Some(format!("(=> {}\n {})\n", lhs, rhs)) -} - -fn convert_lhs(statements: &ast::Arena, lhs: ast::ValueId) -> Option { - let mut tys = vec![]; - let pattern = convert_operand(statements, lhs.into(), &mut tys, 0)?; - - Some(if tys.is_empty() { - pattern - } else { - let mut lhs = format!("(when {}", pattern); - for (name, width) in tys { - lhs.push_str("\n "); - lhs.push_str(&format!("(bit-width {} {})", name, width)); - } - lhs.push(')'); - lhs - }) -} - -fn convert_name(name: &str) -> String { - debug_assert!(name.starts_with('%')); - debug_assert!(name.len() >= 2); - let c = name.as_bytes()[1]; - if b'a' <= c && c <= b'z' { - format!("${}", &name[1..]) - } else { - format!("$v{}", &name[1..]) - } -} - -fn convert_operand( - statements: &ast::Arena, - value: ast::Operand, - tys: &mut Vec<(String, u16)>, - depth: u8, -) -> Option { - if depth > MAX_DEPTH { - log::warn!("optimization too deep to translate recursively; skipping"); - return None; - } - - let value = match value { - ast::Operand::Value(v) => v, - ast::Operand::Constant(c) => return Some(format!("{}", c.value)), - }; - - match &statements[value.into()] { - ast::Statement::Pc(_) | ast::Statement::BlockPc(_) => { - log::warn!("Peepmatic does not support path conditions yet"); - None - } - ast::Statement::Assignment(assn) => { - debug_assert!(assn.name.starts_with('%')); - - if !assn.attributes.is_empty() { - log::warn!("Peepmatic does not support attributes"); - return None; - } - - match assn.value { - ast::AssignmentRhs::Block(_) - | ast::AssignmentRhs::Phi(_) - | ast::AssignmentRhs::ReservedConst - | ast::AssignmentRhs::ReservedInst => { - log::warn!("Peepmatic does not support {:?}", assn.value); - return None; - } - ast::AssignmentRhs::Var => { - if let Some(ast::Type { width }) = assn.r#type { - match width { - 1 | 8 | 16 | 32 | 64 | 128 => { - tys.push((convert_name(&assn.name), width)) - } - _ => { - log::warn!("unsupported bit width: {}", width); - return None; - } - } - } - Some(format!("{}", convert_name(&assn.name))) - } - ast::AssignmentRhs::Instruction(inst) => match inst { - // Unsupported instructions. - ast::Instruction::Bswap { .. } - | ast::Instruction::SaddWithOverflow { .. } - | ast::Instruction::UaddWithOverflow { .. } - | ast::Instruction::SsubWithOverflow { .. } - | ast::Instruction::UsubWithOverflow { .. } - | ast::Instruction::SmulWithOverflow { .. } - | ast::Instruction::UmulWithOverflow { .. } - | ast::Instruction::Fshl { .. } - | ast::Instruction::Fshr { .. } - | ast::Instruction::ExtractValue { .. } - | ast::Instruction::Hole - | ast::Instruction::Freeze { .. } => { - log::warn!("Operation is not supported by Peepmatic: {:?}", inst); - return None; - } - - // Comparison instructions return an `i1` in Souper but a - // `b1` in clif/Peepmatic. The `b1` needs to be extended - // into an `i{8,16,32,654,128}` for us to continue, so these - // instructions are special cased when handling `sext` and - // `zext` conversions. - ast::Instruction::Eq { .. } - | ast::Instruction::Ne { .. } - | ast::Instruction::Ult { .. } - | ast::Instruction::Slt { .. } - | ast::Instruction::Ule { .. } - | ast::Instruction::Sle { .. } => { - log::warn!("unsupported comparison"); - return None; - } - - // These instructions require type ascriptions. - ast::Instruction::Zext { a } => { - let width = require_width(assn.r#type)?; - let cmp = try_convert_comparison(statements, a, tys, depth)?; - if let Some(cmp) = cmp { - Some(format!("(bint {})", cmp)) - } else { - convert_operation(statements, "uextend", &[a], Some(width), tys, depth) - } - } - ast::Instruction::Sext { a } => { - let width = require_width(assn.r#type)?; - let cmp = try_convert_comparison(statements, a, tys, depth)?; - if let Some(cmp) = cmp { - Some(format!("(bint {})", cmp)) - } else { - convert_operation(statements, "sextend", &[a], Some(width), tys, depth) - } - } - ast::Instruction::Trunc { a } => { - let width = require_width(assn.r#type)?; - convert_operation(statements, "ireduce", &[a], Some(width), tys, depth) - } - - ast::Instruction::Add { a, b } - | ast::Instruction::AddNsw { a, b } - | ast::Instruction::AddNuw { a, b } - | ast::Instruction::AddNw { a, b } => convert_commutative_operation( - statements, "iadd", "iadd_imm", a, b, tys, depth, - ), - ast::Instruction::Sub { a, b } - | ast::Instruction::SubNsw { a, b } - | ast::Instruction::SubNuw { a, b } - | ast::Instruction::SubNw { a, b } => { - if let ast::Operand::Constant(c) = b { - Some(format!( - "(iadd_imm -{} {})", - c.value, - convert_operand(statements, a, tys, depth + 1)?, - )) - } else if let ast::Operand::Constant(c) = a { - Some(format!( - "(irsub_imm {} {})", - c.value, - convert_operand(statements, b, tys, depth + 1)?, - )) - } else { - convert_operation(statements, "isub", &[a, b], None, tys, depth) - } - } - ast::Instruction::Mul { a, b } - | ast::Instruction::MulNsw { a, b } - | ast::Instruction::MulNuw { a, b } - | ast::Instruction::MulNw { a, b } => convert_commutative_operation( - statements, "imul", "imul_imm", a, b, tys, depth, - ), - ast::Instruction::Udiv { a, b } | ast::Instruction::UdivExact { a, b } => { - if let ast::Operand::Constant(c) = b { - Some(format!( - "(udiv_imm {} {})", - c.value, - convert_operand(statements, a, tys, depth + 1)?, - )) - } else { - convert_operation(statements, "udiv", &[a, b], None, tys, depth) - } - } - ast::Instruction::Sdiv { a, b } | ast::Instruction::SdivExact { a, b } => { - if let ast::Operand::Constant(c) = b { - Some(format!( - "(sdiv_imm {} {})", - c.value, - convert_operand(statements, a, tys, depth + 1)?, - )) - } else { - convert_operation(statements, "sdiv", &[a, b], None, tys, depth) - } - } - ast::Instruction::Urem { a, b } => { - if let ast::Operand::Constant(c) = b { - Some(format!( - "(urem_imm {} {})", - c.value, - convert_operand(statements, a, tys, depth + 1)?, - )) - } else { - convert_operation(statements, "urem", &[a, b], None, tys, depth) - } - } - ast::Instruction::Srem { a, b } => { - if let ast::Operand::Constant(c) = b { - Some(format!( - "(srem_imm {} {})", - c.value, - convert_operand(statements, a, tys, depth + 1)?, - )) - } else { - convert_operation(statements, "srem", &[a, b], None, tys, depth) - } - } - ast::Instruction::And { a, b } => convert_commutative_operation( - statements, "band", "band_imm", a, b, tys, depth, - ), - ast::Instruction::Or { a, b } => convert_commutative_operation( - statements, "bor", "bor_imm", a, b, tys, depth, - ), - ast::Instruction::Xor { a, b } => convert_commutative_operation( - statements, "bxor", "bxor_imm", a, b, tys, depth, - ), - ast::Instruction::Shl { a, b } - | ast::Instruction::ShlNsw { a, b } - | ast::Instruction::ShlNuw { a, b } - | ast::Instruction::ShlNw { a, b } => { - if let ast::Operand::Constant(c) = b { - Some(format!( - "(ishl_imm {} {})", - c.value, - convert_operand(statements, a, tys, depth + 1)?, - )) - } else { - convert_operation(statements, "ishl", &[a, b], None, tys, depth) - } - } - ast::Instruction::Lshr { a, b } | ast::Instruction::LshrExact { a, b } => { - if let ast::Operand::Constant(c) = b { - Some(format!( - "(ushr_imm {} {})", - c.value, - convert_operand(statements, a, tys, depth + 1)?, - )) - } else { - convert_operation(statements, "ushr", &[a, b], None, tys, depth) - } - } - ast::Instruction::Ashr { a, b } | ast::Instruction::AshrExact { a, b } => { - if let ast::Operand::Constant(c) = b { - Some(format!( - "(sshr_imm {} {})", - c.value, - convert_operand(statements, a, tys, depth + 1)?, - )) - } else { - convert_operation(statements, "sshr", &[a, b], None, tys, depth) - } - } - ast::Instruction::Select { a, b, c } => { - convert_operation(statements, "select", &[a, b, c], None, tys, depth) - } - ast::Instruction::Ctpop { a } => { - convert_operation(statements, "popcnt", &[a], None, tys, depth) - } - ast::Instruction::BitReverse { a } => { - convert_operation(statements, "bitrev", &[a], None, tys, depth) - } - ast::Instruction::Cttz { a } => { - convert_operation(statements, "ctz", &[a], None, tys, depth) - } - ast::Instruction::Ctlz { a } => { - convert_operation(statements, "clz", &[a], None, tys, depth) - } - ast::Instruction::SaddSat { a, b } => { - convert_operation(statements, "sadd_sat", &[a, b], None, tys, depth) - } - ast::Instruction::UaddSat { a, b } => { - convert_operation(statements, "uadd_sat", &[a, b], None, tys, depth) - } - ast::Instruction::SsubSat { a, b } => { - convert_operation(statements, "ssub_sat", &[a, b], None, tys, depth) - } - ast::Instruction::UsubSat { a, b } => { - convert_operation(statements, "usub_sat", &[a, b], None, tys, depth) - } - }, - } - } - } -} - -/// Try and convert `value` into an `icmp` comparison. -/// -/// Returns `Some(Some(icmp))` if the conversion is successful. -/// -/// Returns `Some(None)` if `value` is not a comparison. -/// -/// Returns `None` in the case where `value` cannot be converted to Peepmatic -/// DSL at all. -fn try_convert_comparison( - statements: &ast::Arena, - value: ast::Operand, - tys: &mut Vec<(String, u16)>, - depth: u8, -) -> Option> { - let value = match value { - ast::Operand::Value(v) => v, - ast::Operand::Constant(_) => return None, - }; - let assn = match &statements[value.into()] { - ast::Statement::Assignment(a) => a, - _ => return None, - }; - Some(match assn.value { - ast::AssignmentRhs::Instruction(ast::Instruction::Eq { a, b }) => Some(convert_operation( - statements, - "icmp eq", - &[a, b], - None, - tys, - depth, - )?), - ast::AssignmentRhs::Instruction(ast::Instruction::Ne { a, b }) => Some(convert_operation( - statements, - "icmp ne", - &[a, b], - None, - tys, - depth, - )?), - ast::AssignmentRhs::Instruction(ast::Instruction::Ult { a, b }) => Some(convert_operation( - statements, - "icmp ult", - &[a, b], - None, - tys, - depth, - )?), - ast::AssignmentRhs::Instruction(ast::Instruction::Slt { a, b }) => Some(convert_operation( - statements, - "icmp slt", - &[a, b], - None, - tys, - depth, - )?), - ast::AssignmentRhs::Instruction(ast::Instruction::Ule { a, b }) => Some(convert_operation( - statements, - "icmp ule", - &[a, b], - None, - tys, - depth, - )?), - ast::AssignmentRhs::Instruction(ast::Instruction::Sle { a, b }) => Some(convert_operation( - statements, - "icmp sle", - &[a, b], - None, - tys, - depth, - )?), - _ => None, - }) -} - -fn require_width(ty: Option) -> Option { - match ty { - Some(ast::Type { width: w @ 8 }) - | Some(ast::Type { width: w @ 16 }) - | Some(ast::Type { width: w @ 32 }) - | Some(ast::Type { width: w @ 64 }) - | Some(ast::Type { width: w @ 128 }) => Some(w), - Some(ty) => { - log::warn!("unsupported bit width: {}", ty.width); - None - } - None => { - log::warn!("required bit width is missing"); - None - } - } -} - -fn convert_operation( - statements: &ast::Arena, - operator: &str, - operands: &[ast::Operand], - ty: Option, - tys: &mut Vec<(String, u16)>, - depth: u8, -) -> Option { - let mut op = format!("({}", operator); - - if let Some(width) = ty { - op.push_str(&format!(" {{i{}}}", width)); - } - - for operand in operands { - op.push(' '); - let operand = convert_operand(statements, *operand, tys, depth + 1)?; - op.push_str(&operand); - } - op.push(')'); - Some(op) -} - -/// Convert a commutative operation, using the `_imm` form if any of its -/// operands is a constant. -fn convert_commutative_operation( - statements: &ast::Arena, - operator: &str, - operator_imm: &str, - a: ast::Operand, - b: ast::Operand, - tys: &mut Vec<(String, u16)>, - depth: u8, -) -> Option { - Some(match (a, b) { - (ast::Operand::Constant(c), _) => format!( - "({} {} {})", - operator_imm, - c.value, - convert_operand(statements, b, tys, depth + 1)?, - ), - (_, ast::Operand::Constant(c)) => format!( - "({} {} {})", - operator_imm, - c.value, - convert_operand(statements, a, tys, depth + 1)?, - ), - _ => format!( - "({} {} {})", - operator, - convert_operand(statements, a, tys, depth + 1)?, - convert_operand(statements, b, tys, depth + 1)?, - ), - }) -} - -fn convert_rhs(statements: &ast::Arena, rhs: ast::Operand) -> Option { - let mut tys = vec![]; - convert_operand(statements, rhs, &mut tys, 0) -} - -#[cfg(test)] -mod tests { - use super::*; - use peepmatic_test_operator::TestOperator; - - fn assert_converts(name: &str, souper: &str, expected: &str) { - let expected = expected.trim(); - eprintln!("expected:\n{}", expected); - - let actual = convert_str(souper, Some(Path::new(name))).unwrap(); - let actual = actual.trim(); - eprintln!("actual:\n{}", actual); - - assert_eq!(expected, actual); - - // Assert that the generated Peepmatic DSL parses and verifies. - let buf = wast::parser::ParseBuffer::new(actual).expect("peepmatic DSL should lex OK"); - let opts = match wast::parser::parse::>(&buf) { - Ok(opts) => opts, - Err(mut e) => { - e.set_path(Path::new(name)); - e.set_text(actual); - eprintln!("{}", e); - panic!("peepmatic DSL should parse OK") - } - }; - if let Err(mut e) = peepmatic::verify(&opts) { - e.set_path(Path::new(name)); - e.set_text(actual); - eprintln!("{}", e); - panic!("peepmatic DSL should verify OK") - } - } - - macro_rules! test { - ( $( - $name:ident => converts($souper:expr, $peepmatic:expr $(,)? ); - )* ) => { - $( - #[test] - fn $name() { - assert_converts(stringify!($name), $souper, $peepmatic); - } - )* - }; - } - - test! { - simple_lhs_rhs => converts( - " - %0 = var - %1 = mul %0, 2 - infer %1 - %2 = shl %0, 1 - result %2 - ", - "\ -(=> (imul_imm 2 $v0) - (ishl_imm 1 $v0))", - ); - - simple_cand => converts( - " - %0 = var - %1 = mul %0, 2 - %2 = shl %0, 1 - cand %1 %2 - ", - "\ -(=> (imul_imm 2 $v0) - (ishl_imm 1 $v0))", - ); - - // These instructions require type ascriptions, so our conversion better - // have them. - trunc => converts( - " - %0:i64 = var - %1:i32 = trunc %0 - cand %1 0 - ", - "\ -(=> (when (ireduce {i32} $v0) - (bit-width $v0 64)) - 0)", - ); - sext => converts( - " - %0:i32 = var - %1:i64 = sext %0 - cand %1 0 - ", - "\ -(=> (when (sextend {i64} $v0) - (bit-width $v0 32)) - 0)", - ); - zext => converts( - " - %0:i32 = var - %1:i64 = zext %0 - cand %1 0 - ", - "\ -(=> (when (uextend {i64} $v0) - (bit-width $v0 32)) - 0)", - ); - - // Type annotations on intermediate values (e.g. on %1, %2, and %3) do - // not turn into type ascriptions in the Peepmatic DSL. - unnecessary_types => converts( - " - %0:i32 = var - %1:i32 = add 1, %0 - %2:i32 = add 1, %1 - %3:i32 = add 1, %2 - %4:i32 = add 3, %0 - cand %3 %4 - ", - "\ -(=> (when (iadd_imm 1 (iadd_imm 1 (iadd_imm 1 $v0))) - (bit-width $v0 32)) - (iadd_imm 3 $v0))", - ); - - // Comparisons need to add a `bint` instruction in Peepmatic, since clif - // has a separate `b1` type that needs to be extended into an integer. - comparison_has_bint => converts( - " - %0:i32 = var - %1:i32 = var - %2:i1 = eq %0, %1 - %3:i32 = zext %2 - cand %3 0 - ", - "\ -(=> (when (bint (icmp eq $v0 $v1)) - (bit-width $v0 32) - (bit-width $v1 32)) - 0)", - ); - - // We correctly introduce `_imm` variants of instructions, regardless of - // which side the constant is on for commutative instructions. - iadd_imm_right => converts( - " - %0:i32 = var - %1:i32 = add %0, 1 - cand %1 0 - ", - "\ -(=> (when (iadd_imm 1 $v0) - (bit-width $v0 32)) - 0)" - ); - iadd_imm_left => converts( - " - %0:i32 = var - %1:i32 = add 1, %0 - cand %1 0 - ", - "\ -(=> (when (iadd_imm 1 $v0) - (bit-width $v0 32)) - 0)" - ); - } -} diff --git a/cranelift/peepmatic/crates/test-operator/Cargo.toml b/cranelift/peepmatic/crates/test-operator/Cargo.toml deleted file mode 100644 index 2ee1c07ba6..0000000000 --- a/cranelift/peepmatic/crates/test-operator/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "peepmatic-test-operator" -description = "Operator for usage in peepmatic tests" -license = "Apache-2.0 WITH LLVM-exception" -version = "0.78.0" -authors = ["Nick Fitzgerald "] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -peepmatic-traits = { version = "=0.78.0", path = "../traits" } -serde = { version = "1.0.105", features = ["derive"] } -wast = "38.0.0" diff --git a/cranelift/peepmatic/crates/test-operator/LICENSE b/cranelift/peepmatic/crates/test-operator/LICENSE deleted file mode 100644 index f9d81955f4..0000000000 --- a/cranelift/peepmatic/crates/test-operator/LICENSE +++ /dev/null @@ -1,220 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - ---- LLVM Exceptions to the Apache 2.0 License ---- - -As an exception, if, as a result of your compiling your source code, portions -of this Software are embedded into an Object form of such source code, you -may redistribute such embedded portions in such Object form without complying -with the conditions of Sections 4(a), 4(b) and 4(d) of the License. - -In addition, if you combine or link compiled forms of this Software with -software that is licensed under the GPLv2 ("Combined Software") and if a -court of competent jurisdiction determines that the patent provision (Section -3), the indemnity provision (Section 9) or other Section of the License -conflicts with the conditions of the GPLv2, you may retroactively and -prospectively choose to deem waived or otherwise exclude such Section(s) of -the License, but only in their entirety and only with respect to the Combined -Software. - diff --git a/cranelift/peepmatic/crates/test-operator/src/lib.rs b/cranelift/peepmatic/crates/test-operator/src/lib.rs deleted file mode 100644 index b3dff3c9eb..0000000000 --- a/cranelift/peepmatic/crates/test-operator/src/lib.rs +++ /dev/null @@ -1,211 +0,0 @@ -//! This crate defines `TestOperator`: a `TOperator` type for usage in tests. -//! -//! This allows us to write Peepmatic-specific tests that do not depend on -//! building all of Cranelift. - -peepmatic_traits::define_operator! { - /// A `TOperator` type for use inside tests. - TestOperator { - band => Band { - parameters(iNN, iNN); - result(iNN); - } - band_imm => BandImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - bconst => Bconst { - immediates(b1); - result(bNN); - } - bint => Bint { - parameters(bNN); - result(iNN); - } - bor => Bor { - parameters(iNN, iNN); - result(iNN); - } - bor_imm => BorImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - brnz => Brnz { - parameters(bool_or_int); - result(void); - } - brz => Brz { - parameters(bool_or_int); - result(void); - } - bxor => Bxor { - parameters(iNN, iNN); - result(iNN); - } - bxor_imm => BxorImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - iadd => Iadd { - parameters(iNN, iNN); - result(iNN); - } - iadd_imm => IaddImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - icmp => Icmp { - immediates(cc); - parameters(iNN, iNN); - result(b1); - } - icmp_imm => IcmpImm { - immediates(cc, iNN); - parameters(iNN); - result(b1); - } - iconst => Iconst { - immediates(iNN); - result(iNN); - } - ifcmp => Ifcmp { - parameters(iNN, iNN); - result(cpu_flags); - } - ifcmp_imm => IfcmpImm { - immediates(iNN); - parameters(iNN); - result(cpu_flags); - } - imul => Imul { - parameters(iNN, iNN); - result(iNN); - } - imul_imm => ImulImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - ireduce => Ireduce { - parameters(iNN); - result(iMM); - is_reduce(true); - } - irsub_imm => IrsubImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - ishl => Ishl { - parameters(iNN, iNN); - result(iNN); - } - ishl_imm => IshlImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - isub => Isub { - parameters(iNN, iNN); - result(iNN); - } - rotl => Rotl { - parameters(iNN, iNN); - result(iNN); - } - rotl_imm => RotlImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - rotr => Rotr { - parameters(iNN, iNN); - result(iNN); - } - rotr_imm => RotrImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - sdiv => Sdiv { - parameters(iNN, iNN); - result(iNN); - } - sdiv_imm => SdivImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - select => Select { - parameters(bool_or_int, any_t, any_t); - result(any_t); - } - sextend => Sextend { - parameters(iNN); - result(iMM); - is_extend(true); - } - srem => Srem { - parameters(iNN, iNN); - result(iNN); - } - srem_imm => SremImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - sshr => Sshr { - parameters(iNN, iNN); - result(iNN); - } - sshr_imm => SshrImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - trapnz => Trapnz { - parameters(bool_or_int); - result(void); - } - trapz => Trapz { - parameters(bool_or_int); - result(void); - } - udiv => Udiv { - parameters(iNN, iNN); - result(iNN); - } - udiv_imm => UdivImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - uextend => Uextend { - parameters(iNN); - result(iMM); - is_extend(true); - } - urem => Urem { - parameters(iNN, iNN); - result(iNN); - } - urem_imm => UremImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - ushr => Ushr { - parameters(iNN, iNN); - result(iNN); - } - ushr_imm => UshrImm { - immediates(iNN); - parameters(iNN); - result(iNN); - } - } -} diff --git a/cranelift/peepmatic/crates/test/Cargo.toml b/cranelift/peepmatic/crates/test/Cargo.toml deleted file mode 100644 index c9db815147..0000000000 --- a/cranelift/peepmatic/crates/test/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "peepmatic-test" -version = "0.2.0" -authors = ["Nick Fitzgerald "] -edition = "2018" -publish = false - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -env_logger = "0.8.1" -log = "0.4.8" -peepmatic = { path = "../.." } -peepmatic-runtime = { path = "../runtime" } -peepmatic-test-operator = { path = "../test-operator" } -peepmatic-traits = { path = "../traits" } diff --git a/cranelift/peepmatic/crates/test/src/lib.rs b/cranelift/peepmatic/crates/test/src/lib.rs deleted file mode 100644 index 948c4abc80..0000000000 --- a/cranelift/peepmatic/crates/test/src/lib.rs +++ /dev/null @@ -1,517 +0,0 @@ -//! Testing utilities and a testing-only instruction set for `peepmatic`. - -#![deny(missing_debug_implementations)] - -use peepmatic_runtime::{ - cc::ConditionCode, - instruction_set::InstructionSet, - part::{Constant, Part}, - r#type::{BitWidth, Kind, Type}, -}; -use peepmatic_test_operator::TestOperator; -use peepmatic_traits::TypingRules; -use std::cell::RefCell; -use std::collections::BTreeMap; -use std::convert::TryFrom; - -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] -pub struct Instruction(pub usize); - -#[derive(Debug)] -pub struct InstructionData { - pub operator: TestOperator, - pub r#type: Type, - pub immediates: Vec, - pub arguments: Vec, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Immediate { - Constant(Constant), - ConditionCode(ConditionCode), -} - -impl Immediate { - fn unwrap_constant(&self) -> Constant { - match *self { - Immediate::Constant(c) => c, - _ => panic!("not a constant"), - } - } -} - -impl From for Immediate { - fn from(c: Constant) -> Immediate { - Immediate::Constant(c) - } -} - -impl From for Immediate { - fn from(cc: ConditionCode) -> Immediate { - Immediate::ConditionCode(cc) - } -} - -impl From for Part { - fn from(imm: Immediate) -> Part { - match imm { - Immediate::Constant(c) => Part::Constant(c), - Immediate::ConditionCode(cc) => Part::ConditionCode(cc), - } - } -} - -impl TryFrom> for Immediate { - type Error = &'static str; - - fn try_from(part: Part) -> Result { - match part { - Part::Constant(c) => Ok(Immediate::Constant(c)), - Part::ConditionCode(c) => Ok(Immediate::ConditionCode(c)), - Part::Instruction(_) => Err("instruction parts cannot be converted into immediates"), - } - } -} - -#[derive(Debug, Default)] -pub struct Program { - instr_counter: usize, - instruction_data: BTreeMap, - replacements: RefCell>, -} - -impl Program { - /// Are `a` and `b` structurally equivalent, even if they use different - /// `Instruction`s for various arguments? - pub fn structurally_eq(&mut self, a: Instruction, b: Instruction) -> bool { - macro_rules! ensure_eq { - ($a:expr, $b:expr) => {{ - let a = &$a; - let b = &$b; - if a != b { - log::debug!( - "{} != {} ({:?} != {:?})", - stringify!($a), - stringify!($b), - a, - b - ); - return false; - } - }}; - } - - let a = self.resolve(a); - let b = self.resolve(b); - if a == b { - return true; - } - - let a = self.data(a); - let b = self.data(b); - log::debug!("structurally_eq({:?}, {:?})", a, b); - - ensure_eq!(a.operator, b.operator); - ensure_eq!(a.r#type, b.r#type); - ensure_eq!(a.immediates, b.immediates); - ensure_eq!(a.arguments.len(), b.arguments.len()); - a.arguments - .clone() - .into_iter() - .zip(b.arguments.clone().into_iter()) - .all(|(a, b)| self.structurally_eq(a, b)) - } - - pub fn instructions(&self) -> impl Iterator { - self.instruction_data.iter().map(|(k, v)| (*k, v)) - } - - pub fn replace_instruction(&mut self, old: Instruction, new: Instruction) { - log::debug!("replacing {:?} with {:?}", old, new); - - let old = self.resolve(old); - let new = self.resolve(new); - if old == new { - return; - } - - let mut replacements = self.replacements.borrow_mut(); - let existing_replacement = replacements.insert(old, new); - assert!(existing_replacement.is_none()); - - let old_data = self.instruction_data.remove(&old); - assert!(old_data.is_some()); - } - - pub fn resolve(&self, inst: Instruction) -> Instruction { - let mut replacements = self.replacements.borrow_mut(); - let mut replacements_followed = 0; - let mut resolved = inst; - while let Some(i) = replacements.get(&resolved).cloned() { - log::trace!("resolving replaced instruction: {:?} -> {:?}", resolved, i); - replacements_followed += 1; - assert!( - replacements_followed <= replacements.len(), - "cyclic replacements" - ); - - resolved = i; - continue; - } - - if inst != resolved { - let old_replacement = replacements.insert(inst, resolved); - assert!(old_replacement.is_some()); - } - - resolved - } - - pub fn data(&self, inst: Instruction) -> &InstructionData { - let inst = self.resolve(inst); - &self.instruction_data[&inst] - } - - pub fn new_instruction( - &mut self, - operator: TestOperator, - r#type: Type, - immediates: Vec, - arguments: Vec, - ) -> Instruction { - assert_eq!( - operator.immediates_arity() as usize, - immediates.len(), - "wrong number of immediates for {:?}: expected {}, found {}", - operator, - operator.immediates_arity(), - immediates.len(), - ); - assert_eq!( - operator.parameters_arity() as usize, - arguments.len(), - "wrong number of arguments for {:?}: expected {}, found {}", - operator, - operator.parameters_arity(), - arguments.len(), - ); - - assert!(!r#type.bit_width.is_polymorphic()); - assert!(immediates.iter().all(|imm| match imm { - Immediate::Constant(Constant::Bool(_, w)) - | Immediate::Constant(Constant::Int(_, w)) => !w.is_polymorphic(), - Immediate::ConditionCode(_) => true, - })); - - let inst = Instruction(self.instr_counter); - self.instr_counter += 1; - - let data = InstructionData { - operator, - r#type, - immediates, - arguments, - }; - - log::trace!("new instruction: {:?} = {:?}", inst, data); - self.instruction_data.insert(inst, data); - inst - } - - pub fn r#const(&mut self, c: Constant, root_bit_width: BitWidth) -> Instruction { - assert!(!root_bit_width.is_polymorphic()); - match c { - Constant::Bool(_, bit_width) => self.new_instruction( - TestOperator::Bconst, - if bit_width.is_polymorphic() { - Type { - kind: Kind::Bool, - bit_width: root_bit_width, - } - } else { - Type { - kind: Kind::Bool, - bit_width, - } - }, - vec![c.into()], - vec![], - ), - Constant::Int(_, bit_width) => self.new_instruction( - TestOperator::Iconst, - if bit_width.is_polymorphic() { - Type { - kind: Kind::Int, - bit_width: root_bit_width, - } - } else { - Type { - kind: Kind::Int, - bit_width, - } - }, - vec![c.into()], - vec![], - ), - } - } - - fn instruction_to_constant(&mut self, inst: Instruction) -> Option { - match self.data(inst) { - InstructionData { - operator: TestOperator::Iconst, - immediates, - .. - } => Some(immediates[0].unwrap_constant()), - InstructionData { - operator: TestOperator::Bconst, - immediates, - .. - } => Some(immediates[0].unwrap_constant()), - _ => None, - } - } - - fn part_to_immediate(&mut self, part: Part) -> Result { - match part { - Part::Instruction(i) => self - .instruction_to_constant(i) - .map(|c| c.into()) - .ok_or("non-constant instructions cannot be converted into immediates"), - Part::Constant(c) => Ok(c.into()), - Part::ConditionCode(cc) => Ok(Immediate::ConditionCode(cc)), - } - } - - fn part_to_instruction( - &mut self, - root: Instruction, - part: Part, - ) -> Result { - match part { - Part::Instruction(inst) => { - let inst = self.resolve(inst); - Ok(inst) - } - Part::Constant(c) => { - let root_width = self.data(root).r#type.bit_width; - Ok(self.r#const(c, root_width)) - } - Part::ConditionCode(_) => Err("condition codes cannot be converted into instructions"), - } - } -} - -#[derive(Debug)] -pub struct TestIsa { - pub native_word_size_in_bits: u8, -} - -// Unsafe because we must ensure that `instruction_result_bit_width` never -// returns zero. -unsafe impl<'a> InstructionSet<'a> for TestIsa { - type Operator = TestOperator; - - type Context = Program; - - type Instruction = Instruction; - - fn replace_instruction( - &self, - program: &mut Program, - old: Instruction, - new: Part, - ) -> Instruction { - log::debug!("replace_instruction({:?}, {:?})", old, new); - let new = program.part_to_instruction(old, new).unwrap(); - program.replace_instruction(old, new); - new - } - - fn operator( - &self, - program: &mut Program, - instr: Self::Instruction, - operands: &mut E, - ) -> Option - where - E: Extend>, - { - log::debug!("operator({:?})", instr); - let data = program.data(instr); - operands.extend( - data.immediates - .iter() - .map(|imm| Part::from(*imm)) - .chain(data.arguments.iter().map(|inst| Part::Instruction(*inst))), - ); - Some(data.operator) - } - - fn make_inst_1( - &self, - program: &mut Program, - root: Instruction, - operator: TestOperator, - r#type: Type, - a: Part, - ) -> Instruction { - log::debug!( - "make_inst_1(\n\toperator = {:?},\n\ttype = {},\n\ta = {:?},\n)", - operator, - r#type, - a, - ); - - let (imms, args) = match operator.immediates_arity() { - 0 => { - assert_eq!(operator.parameters_arity(), 1); - (vec![], vec![program.part_to_instruction(root, a).unwrap()]) - } - 1 => { - assert_eq!(operator.parameters_arity(), 0); - (vec![program.part_to_immediate(a).unwrap()], vec![]) - } - _ => unreachable!(), - }; - program.new_instruction(operator, r#type, imms, args) - } - - fn make_inst_2( - &self, - program: &mut Program, - root: Instruction, - operator: TestOperator, - r#type: Type, - a: Part, - b: Part, - ) -> Instruction { - log::debug!( - "make_inst_2(\n\toperator = {:?},\n\ttype = {},\n\ta = {:?},\n\tb = {:?},\n)", - operator, - r#type, - a, - b, - ); - - let (imms, args) = match operator.immediates_arity() { - 0 => { - assert_eq!(operator.parameters_arity(), 2); - ( - vec![], - vec![ - program.part_to_instruction(root, a).unwrap(), - program.part_to_instruction(root, b).unwrap(), - ], - ) - } - 1 => { - assert_eq!(operator.parameters_arity(), 1); - ( - vec![program.part_to_immediate(a).unwrap()], - vec![program.part_to_instruction(root, b).unwrap()], - ) - } - 2 => { - assert_eq!(operator.parameters_arity(), 0); - ( - vec![ - program.part_to_immediate(a).unwrap(), - program.part_to_immediate(b).unwrap(), - ], - vec![], - ) - } - _ => unreachable!(), - }; - program.new_instruction(operator, r#type, imms, args) - } - - fn make_inst_3( - &self, - program: &mut Program, - root: Instruction, - operator: TestOperator, - r#type: Type, - a: Part, - b: Part, - c: Part, - ) -> Instruction { - log::debug!( - "make_inst_3(\n\toperator = {:?},\n\ttype = {},\n\ta = {:?},\n\tb = {:?},\n\tc = {:?},\n)", - operator, - r#type, - a, - b, - c, - ); - let (imms, args) = match operator.immediates_arity() { - 0 => { - assert_eq!(operator.parameters_arity(), 3); - ( - vec![], - vec![ - program.part_to_instruction(root, a).unwrap(), - program.part_to_instruction(root, b).unwrap(), - program.part_to_instruction(root, c).unwrap(), - ], - ) - } - 1 => { - assert_eq!(operator.parameters_arity(), 2); - ( - vec![program.part_to_immediate(a).unwrap()], - vec![ - program.part_to_instruction(root, b).unwrap(), - program.part_to_instruction(root, c).unwrap(), - ], - ) - } - 2 => { - assert_eq!(operator.parameters_arity(), 1); - ( - vec![ - program.part_to_immediate(a).unwrap(), - program.part_to_immediate(b).unwrap(), - ], - vec![program.part_to_instruction(root, c).unwrap()], - ) - } - 3 => { - assert_eq!(operator.parameters_arity(), 0); - ( - vec![ - program.part_to_immediate(a).unwrap(), - program.part_to_immediate(b).unwrap(), - program.part_to_immediate(c).unwrap(), - ], - vec![], - ) - } - _ => unreachable!(), - }; - program.new_instruction(operator, r#type, imms, args) - } - - fn instruction_to_constant( - &self, - program: &mut Program, - inst: Instruction, - ) -> Option { - log::debug!("instruction_to_constant({:?})", inst); - program.instruction_to_constant(inst) - } - - fn instruction_result_bit_width(&self, program: &mut Program, inst: Instruction) -> u8 { - log::debug!("instruction_result_bit_width({:?})", inst); - let ty = program.data(inst).r#type; - let width = ty.bit_width.fixed_width().unwrap(); - assert!(width != 0); - width - } - - fn native_word_size_in_bits(&self, _program: &mut Program) -> u8 { - log::debug!("native_word_size_in_bits"); - self.native_word_size_in_bits - } -} diff --git a/cranelift/peepmatic/crates/test/tests/tests.rs b/cranelift/peepmatic/crates/test/tests/tests.rs deleted file mode 100644 index ced151c529..0000000000 --- a/cranelift/peepmatic/crates/test/tests/tests.rs +++ /dev/null @@ -1,393 +0,0 @@ -use peepmatic_runtime::{ - cc::ConditionCode, - part::Constant, - r#type::{BitWidth, Type}, -}; -use peepmatic_test::*; -use peepmatic_test_operator::TestOperator; - -const TEST_ISA: TestIsa = TestIsa { - native_word_size_in_bits: 32, -}; - -macro_rules! optimizer { - ($opts:ident, $source:expr) => {{ - let _ = env_logger::try_init(); - $opts = peepmatic::compile_str($source, std::path::Path::new("peepmatic-test")).unwrap(); - $opts.optimizer(TEST_ISA) - }}; -} - -#[test] -fn opcode() { - let opts; - let mut optimizer = optimizer!(opts, "(=> (iadd $x 0) $x)"); - - let mut program = Program::default(); - let five = program.r#const(Constant::Int(5, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo); - let zero = program.r#const(Constant::Int(0, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo); - let add = program.new_instruction(TestOperator::Iadd, Type::i32(), vec![], vec![five, zero]); - - let new = optimizer.apply_one(&mut program, add); - let new = new.expect("optimization should have applied"); - assert!(program.structurally_eq(new, five)); - - let add = program.new_instruction(TestOperator::Iadd, Type::i32(), vec![], vec![five, five]); - let replacement = optimizer.apply_one(&mut program, add); - assert!(replacement.is_none()); -} - -#[test] -fn constant() { - let opts; - let mut optimizer = optimizer!(opts, "(=> (iadd $C $x) (iadd_imm $C $x))"); - - let mut program = Program::default(); - let five = program.r#const(Constant::Int(5, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo); - let zero = program.r#const(Constant::Int(0, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo); - let add = program.new_instruction(TestOperator::Iadd, Type::i32(), vec![], vec![five, zero]); - - let expected = program.new_instruction( - TestOperator::IaddImm, - Type::i32(), - vec![Constant::Int(5, BitWidth::ThirtyTwo).into()], - vec![zero], - ); - - let new = optimizer.apply_one(&mut program, add); - let new = new.expect("optimization should have applied"); - assert!(program.structurally_eq(new, expected)); - - let mul = program.new_instruction(TestOperator::Imul, Type::i32(), vec![], vec![five, zero]); - let add = program.new_instruction(TestOperator::Imul, Type::i32(), vec![], vec![mul, five]); - let replacement = optimizer.apply_one(&mut program, add); - assert!(replacement.is_none()); -} - -#[test] -fn boolean() { - let opts; - let mut optimizer = optimizer!(opts, "(=> (bint true) 1)"); - - let mut program = Program::default(); - let t = program.r#const(Constant::Bool(true, BitWidth::One), BitWidth::One); - let bint = program.new_instruction(TestOperator::Bint, Type::i1(), vec![], vec![t]); - let one = program.r#const(Constant::Int(1, BitWidth::One), BitWidth::ThirtyTwo); - - let new = optimizer.apply_one(&mut program, bint); - let new = new.expect("optimization should have applied"); - assert!(program.structurally_eq(new, one)); - - let f = program.r#const(Constant::Bool(false, BitWidth::One), BitWidth::One); - let bint = program.new_instruction(TestOperator::Bint, Type::i1(), vec![], vec![f]); - let replacement = optimizer.apply_one(&mut program, bint); - assert!(replacement.is_none()); -} - -#[test] -fn condition_codes() { - let opts; - let mut optimizer = optimizer!(opts, "(=> (icmp eq $x $x) true)"); - - let mut program = Program::default(); - let five = program.r#const(Constant::Int(5, BitWidth::ThirtyTwo), BitWidth::One); - let icmp_eq = program.new_instruction( - TestOperator::Icmp, - Type::b1(), - vec![ConditionCode::Eq.into()], - vec![five, five], - ); - let t = program.r#const(Constant::Bool(true, BitWidth::One), BitWidth::One); - - let new = optimizer.apply_one(&mut program, icmp_eq); - let new = new.expect("optimization should have applied"); - assert!(program.structurally_eq(new, t)); - - let icmp_ne = program.new_instruction( - TestOperator::Icmp, - Type::b1(), - vec![ConditionCode::Ne.into()], - vec![five, five], - ); - let replacement = optimizer.apply_one(&mut program, icmp_ne); - assert!(replacement.is_none()); -} - -#[test] -fn is_power_of_two() { - let opts; - let mut optimizer = optimizer!( - opts, - " -(=> (when (imul $x $C) - (is-power-of-two $C)) - (ishl $x $(log2 $C))) -" - ); - - let mut program = Program::default(); - let five = program.r#const(Constant::Int(5, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo); - let two = program.r#const(Constant::Int(2, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo); - let imul = program.new_instruction(TestOperator::Imul, Type::i32(), vec![], vec![five, two]); - - let one = program.r#const(Constant::Int(1, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo); - let ishl = program.new_instruction(TestOperator::Ishl, Type::i32(), vec![], vec![five, one]); - - let new = optimizer.apply_one(&mut program, imul); - let new = new.expect("optimization should have applied"); - assert!(program.structurally_eq(new, ishl)); - - let three = program.r#const(Constant::Int(3, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo); - let imul = program.new_instruction(TestOperator::Imul, Type::i32(), vec![], vec![five, three]); - - let replacement = optimizer.apply_one(&mut program, imul); - assert!(replacement.is_none()); -} - -#[test] -fn bit_width() { - let opts; - let mut optimizer = optimizer!( - opts, - " -(=> (when (imul $C $x) - (bit-width $C 32)) - (imul_imm $C $x)) -" - ); - - let mut program = Program::default(); - let five = program.r#const(Constant::Int(5, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo); - let two = program.r#const(Constant::Int(2, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo); - let imul = program.new_instruction(TestOperator::Imul, Type::i32(), vec![], vec![five, two]); - - let imul_imm = program.new_instruction( - TestOperator::ImulImm, - Type::i32(), - vec![Constant::Int(5, BitWidth::ThirtyTwo).into()], - vec![two], - ); - - let new = optimizer.apply_one(&mut program, imul); - let new = new.expect("optimization should have applied"); - assert!(program.structurally_eq(new, imul_imm)); - - let five = program.r#const(Constant::Int(5, BitWidth::SixtyFour), BitWidth::SixtyFour); - let two = program.r#const(Constant::Int(2, BitWidth::SixtyFour), BitWidth::SixtyFour); - let imul = program.new_instruction(TestOperator::Imul, Type::i32(), vec![], vec![five, two]); - - let replacement = optimizer.apply_one(&mut program, imul); - assert!(replacement.is_none()); -} - -#[test] -fn fits_in_native_word() { - let opts; - let mut optimizer = optimizer!( - opts, - " -(=> (when (imul $C $x) - (fits-in-native-word $C)) - (imul_imm $C $x)) -" - ); - - let mut program = Program::default(); - let five = program.r#const(Constant::Int(5, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo); - let two = program.r#const(Constant::Int(2, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo); - let imul = program.new_instruction(TestOperator::Imul, Type::i32(), vec![], vec![five, two]); - - let imul_imm = program.new_instruction( - TestOperator::ImulImm, - Type::i32(), - vec![Constant::Int(5, BitWidth::ThirtyTwo).into()], - vec![two], - ); - - let new = optimizer.apply_one(&mut program, imul); - let new = new.expect("optimization should have applied"); - assert!(program.structurally_eq(new, imul_imm)); - - let five = program.r#const(Constant::Int(5, BitWidth::SixtyFour), BitWidth::SixtyFour); - let two = program.r#const(Constant::Int(2, BitWidth::SixtyFour), BitWidth::SixtyFour); - let imul = program.new_instruction(TestOperator::Imul, Type::i64(), vec![], vec![five, two]); - - let replacement = optimizer.apply_one(&mut program, imul); - assert!(replacement.is_none()); -} - -#[test] -fn unquote_neg() { - let opts; - let mut optimizer = optimizer!( - opts, - " -(=> (isub $x $C) - (iadd_imm $(neg $C) $x)) -" - ); - - let mut program = Program::default(); - let five = program.r#const(Constant::Int(5, BitWidth::SixtyFour), BitWidth::SixtyFour); - let two = program.r#const(Constant::Int(2, BitWidth::SixtyFour), BitWidth::SixtyFour); - let isub = program.new_instruction(TestOperator::Isub, Type::i64(), vec![], vec![five, two]); - - let iadd_imm = program.new_instruction( - TestOperator::IaddImm, - Type::i64(), - vec![Constant::Int(-2 as _, BitWidth::SixtyFour).into()], - vec![five], - ); - - let new = optimizer.apply_one(&mut program, isub); - let new = new.expect("optimization should have applied"); - assert!(program.structurally_eq(new, iadd_imm)); -} - -#[test] -fn subsumption() { - let opts; - let mut optimizer = optimizer!( - opts, - " -;; NB: the following optimizations are ordered from least to most general, so -;; the first applicable optimization should be the one that is applied. - -(=> (iadd (iadd (iadd $w $x) $y) $z) - (iadd (iadd $w $x) (iadd $y $z))) - -(=> (iadd $C1 $C2) - $(iadd $C1 $C2)) - -(=> (iadd $C $x) - (iadd_imm $C $x)) - -(=> (iadd $x $x) - (ishl_imm 1 $x)) -" - ); - - let mut program = Program::default(); - - let w = program.r#const(Constant::Int(11, BitWidth::SixtyFour), BitWidth::SixtyFour); - let x = program.r#const(Constant::Int(22, BitWidth::SixtyFour), BitWidth::SixtyFour); - let y = program.r#const(Constant::Int(33, BitWidth::SixtyFour), BitWidth::SixtyFour); - let z = program.r#const(Constant::Int(44, BitWidth::SixtyFour), BitWidth::SixtyFour); - - log::debug!("(iadd (iadd (iadd w x) y) z) => (iadd (iadd w x) (iadd y z))"); - - let iadd = program.new_instruction(TestOperator::Iadd, Type::i64(), vec![], vec![w, x]); - let iadd = program.new_instruction(TestOperator::Iadd, Type::i64(), vec![], vec![iadd, y]); - let iadd = program.new_instruction(TestOperator::Iadd, Type::i64(), vec![], vec![iadd, z]); - let expected_lhs = program.new_instruction(TestOperator::Iadd, Type::i64(), vec![], vec![w, x]); - let expected_rhs = program.new_instruction(TestOperator::Iadd, Type::i64(), vec![], vec![y, z]); - let expected = program.new_instruction( - TestOperator::Iadd, - Type::i64(), - vec![], - vec![expected_lhs, expected_rhs], - ); - - let new = optimizer.apply_one(&mut program, iadd); - let new = new.expect("optimization should have applied"); - assert!(program.structurally_eq(new, expected)); - - log::debug!("(iadd w x) => y"); - - let iadd = program.new_instruction(TestOperator::Iadd, Type::i64(), vec![], vec![w, x]); - let new = optimizer.apply_one(&mut program, iadd); - let new = new.expect("optimization should have applied"); - assert!(program.structurally_eq(new, y)); - - log::debug!("(iadd x (iadd y z)) => (iadd_imm x (iadd y z))"); - - let iadd_y_z = program.new_instruction(TestOperator::Iadd, Type::i64(), vec![], vec![y, z]); - let iadd = program.new_instruction(TestOperator::Iadd, Type::i64(), vec![], vec![x, iadd_y_z]); - let iadd_imm = program.new_instruction( - TestOperator::IaddImm, - Type::i64(), - vec![Constant::Int(22, BitWidth::SixtyFour).into()], - vec![iadd_y_z], - ); - let new = optimizer.apply_one(&mut program, iadd); - let new = new.expect("optimization should have applied"); - assert!(program.structurally_eq(new, iadd_imm)); - - log::debug!("(iadd (imul_imm x 1) (imul_imm x 1)) => (ishl_imm 1 (imul_imm x 1))"); - - let imul_imm = program.new_instruction( - TestOperator::ImulImm, - Type::i64(), - vec![Constant::Int(1, BitWidth::SixtyFour).into()], - vec![x], - ); - let iadd = program.new_instruction( - TestOperator::Iadd, - Type::i64(), - vec![], - vec![imul_imm, imul_imm], - ); - let ishl_imm = program.new_instruction( - TestOperator::IshlImm, - Type::i64(), - vec![Constant::Int(1, BitWidth::SixtyFour).into()], - vec![imul_imm], - ); - let new = optimizer.apply_one(&mut program, iadd); - let new = new.expect("optimization should have applied"); - assert!(program.structurally_eq(new, ishl_imm)); - - log::debug!("(iadd (imul w x) (imul y z)) does not match any optimization."); - - let imul_w_x = program.new_instruction(TestOperator::Imul, Type::i64(), vec![], vec![w, x]); - let imul_y_z = program.new_instruction(TestOperator::Imul, Type::i64(), vec![], vec![y, z]); - let iadd = program.new_instruction( - TestOperator::Iadd, - Type::i64(), - vec![], - vec![imul_w_x, imul_y_z], - ); - - let replacement = optimizer.apply_one(&mut program, iadd); - assert!(replacement.is_none()); -} - -#[test] -fn polymorphic_bit_widths() { - let opts; - let mut optimizer = optimizer!(opts, "(=> (iadd $C $x) (iadd_imm $C $x))"); - - let mut program = Program::default(); - - // Applies to 32 bit adds. - - let x = program.r#const(Constant::Int(42, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo); - let y = program.r#const(Constant::Int(420, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo); - let iadd = program.new_instruction(TestOperator::Iadd, Type::i32(), vec![], vec![x, y]); - let iadd_imm = program.new_instruction( - TestOperator::IaddImm, - Type::i32(), - vec![Constant::Int(42, BitWidth::ThirtyTwo).into()], - vec![y], - ); - - let new = optimizer.apply_one(&mut program, iadd); - let new = new.expect("optimization should have applied"); - assert!(program.structurally_eq(new, iadd_imm)); - - // Applies to 16 bit adds. - - let x = program.r#const(Constant::Int(42, BitWidth::Sixteen), BitWidth::Sixteen); - let y = program.r#const(Constant::Int(420, BitWidth::Sixteen), BitWidth::Sixteen); - let iadd = program.new_instruction(TestOperator::Iadd, Type::i16(), vec![], vec![x, y]); - let iadd_imm = program.new_instruction( - TestOperator::IaddImm, - Type::i16(), - vec![Constant::Int(42, BitWidth::Sixteen).into()], - vec![y], - ); - - let new = optimizer.apply_one(&mut program, iadd); - let new = new.expect("optimization should have applied"); - assert!(program.structurally_eq(new, iadd_imm)); -} diff --git a/cranelift/peepmatic/crates/traits/Cargo.toml b/cranelift/peepmatic/crates/traits/Cargo.toml deleted file mode 100644 index 828ea285df..0000000000 --- a/cranelift/peepmatic/crates/traits/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "peepmatic-traits" -version = "0.78.0" -authors = ["Nick Fitzgerald "] -edition = "2018" -license = "Apache-2.0 WITH LLVM-exception" -description = 'Common traits for peepmatic' - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] diff --git a/cranelift/peepmatic/crates/traits/src/lib.rs b/cranelift/peepmatic/crates/traits/src/lib.rs deleted file mode 100644 index 2da48bbee9..0000000000 --- a/cranelift/peepmatic/crates/traits/src/lib.rs +++ /dev/null @@ -1,26 +0,0 @@ -//! Shared traits, types, and macros for Peepmatic. -//! -//! This crate is used both at build time when constructing peephole optimizers -//! (i.e. in the `peepmatic` crate), and at run time when using pre-built -//! peephole optimizers (i.e. in the `peepmatic-runtime` crate and in -//! Cranelift's Peepmatic integration at `cranelift/codegen/src/peepmatic.rs`). -//! -//! This crate is similar to a header file: it should generally only contain -//! trait/type/macro definitions, not any code. - -#![deny(missing_docs)] -#![deny(missing_debug_implementations)] - -#[macro_use] -mod operator; -pub use operator::*; - -mod typing; -pub use typing::*; - -/// Raise a panic about an unsupported operation. -#[cold] -#[inline(never)] -pub fn unsupported(msg: &str) -> ! { - panic!("unsupported: {}", msg) -} diff --git a/cranelift/peepmatic/crates/traits/src/operator.rs b/cranelift/peepmatic/crates/traits/src/operator.rs deleted file mode 100644 index ac1c2650a3..0000000000 --- a/cranelift/peepmatic/crates/traits/src/operator.rs +++ /dev/null @@ -1,317 +0,0 @@ -/// Define a `wast::parser::Parse` implementation for an operator type. -#[macro_export] -macro_rules! define_parse_impl_for_operator { - ( - $operator:ident { - $( - $keyword:ident => $variant:ident; - )* - } - ) => { - impl<'a> wast::parser::Parse<'a> for $operator { - fn parse(p: wast::parser::Parser<'a>) -> wast::parser::Result<$operator> { - /// Token definitions for our `Opcode` keywords. - mod tok { - $( - wast::custom_keyword!($keyword); - )* - } - - // Peek at the next token, and if it is the variant's - // keyword, then consume it with `parse`, and finally return - // the `Opcode` variant. - $( - if p.peek::() { - p.parse::()?; - return Ok(Self::$variant); - } - )* - - // If none of the keywords matched, then we get a parse error. - Err(p.error(concat!("expected `", stringify!($operator), "`"))) - } - } - } -} - -/// Define a `peepmatic_traits::TypingRules` implementation for the given -/// operator type. -#[macro_export] -macro_rules! define_typing_rules_impl_for_operator { - ( - $operator:ident { - $( - $variant:ident { - $( immediates( $($immediate:ident),* ); )? - $( parameters( $($parameter:ident),* ); )? - result( $result:ident ); - $( is_reduce($is_reduce:expr); )? - $( is_extend($is_extend:expr); )? - } - )* - } - ) => { - impl $crate::TypingRules for $operator { - fn result_type<'a, C>( - &self, - span: C::Span, - typing_context: &mut C, - ) -> C::TypeVariable - where - C: $crate::TypingContext<'a> { - match self { - $( - Self::$variant => typing_context.$result(span), - )* - - #[allow(dead_code)] - _ => $crate::unsupported("no typing rules defined for variant"), - } - } - - fn immediates_arity(&self) -> u8 { - match self { - $( - Self::$variant => $crate::define_typing_rules_impl_for_operator!( - @arity; - $( $( $immediate, )* )? - ), - )* - - #[allow(dead_code)] - _ => $crate::unsupported("no typing rules defined for variant"), - } - } - - fn immediate_types<'a, C>( - &self, - span: C::Span, - typing_context: &mut C, - types: &mut impl Extend, - ) - where - C: $crate::TypingContext<'a> - { - match self { - $( - Self::$variant => types.extend( - None.into_iter() - $( - $( - .chain(Some(typing_context.$immediate(span))) - )* - )? - ), - )* - - #[allow(dead_code)] - _ => $crate::unsupported("no typing rules defined for variant"), - } - } - - fn parameters_arity(&self) -> u8 { - match self { - $( - Self::$variant => $crate::define_typing_rules_impl_for_operator!( - @arity; - $( $( $parameter, )* )? - ), - )* - - #[allow(dead_code)] - _ => $crate::unsupported("no typing rules defined for variant"), - } - } - - fn parameter_types<'a, C>( - &self, - span: C::Span, - typing_context: &mut C, - types: &mut impl Extend, - ) - where - C: $crate::TypingContext<'a> - { - match self { - $( - Self::$variant => types.extend( - None.into_iter() - $( - $( - .chain(Some(typing_context.$parameter(span))) - )* - )? - ), - )* - - #[allow(dead_code)] - _ => $crate::unsupported("no typing rules defined for variant"), - } - } - - fn is_reduce(&self) -> bool { - match self { - $( - Self::$variant if false $( || $is_reduce )? => false $( || $is_reduce )?, - )* - _ => false, - } - } - - fn is_extend(&self) -> bool { - match self { - $( - Self::$variant if false $( || $is_extend )? => false $( || $is_extend )?, - )* - _ => false, - } - } - } - }; - - // Base case: zero arity. - ( - @arity; - ) => { - 0 - }; - - // Recursive case: count one for the head and add that to the arity of the - // rest. - ( - @arity; - $head:ident, - $( $rest:ident, )* - ) => { - 1 + $crate::define_typing_rules_impl_for_operator!( - @arity; - $( $rest, )* - ) - } -} - -/// Define both a `wast::parser::Parse` implementation and a -/// `peepmatic_traits::TypingRules` implementation for the given operator type. -#[macro_export] -macro_rules! define_parse_and_typing_rules_for_operator { - ( - $operator:ident { - $( - $keyword:ident => $variant:ident { - $( immediates( $($immediate:ident),* ); )? - $( parameters( $($parameter:ident),* ); )? - result( $result:ident ); - $( is_reduce($is_reduce:expr); )? - $( is_extend($is_extend:expr); )? - } - )* - } - $( parse_cfg($parse_cfg:meta); )? - ) => { - $( #[cfg($parse_cfg)] )? - $crate::define_parse_impl_for_operator! { - $operator { - $( - $keyword => $variant; - )* - } - } - - $crate::define_typing_rules_impl_for_operator! { - $operator { - $( - $variant { - $( immediates( $($immediate),* ); )? - $( parameters( $($parameter),* ); )? - result( $result ); - $( is_reduce($is_reduce); )? - $( is_extend($is_extend); )? - } - )* - } - } - } -} - -/// Define an operator type, as well as its parsing and typing rules. -#[macro_export] -macro_rules! define_operator { - ( - $( #[$attr:meta] )* - $operator:ident { - $( - $keywrord:ident => $variant:ident { - $( immediates( $($immediate:ident),* ); )? - $( parameters( $($parameter:ident),* ); )? - result( $result:ident ); - $( is_reduce($is_reduce:expr); )? - $( is_extend($is_extend:expr); )? - } - )* - } - $( parse_cfg($parse_cfg:meta); )? - ) => { - $( #[$attr] )* - #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] - #[repr(u32)] - pub enum $operator { - $( - $variant, - )* - } - - impl From<$operator> for u32 { - #[inline] - fn from(x: $operator) -> u32 { - x as u32 - } - } - - impl From<$operator> for core::num::NonZeroU32 { - #[inline] - fn from(x: $operator) -> core::num::NonZeroU32 { - let x: u32 = x.into(); - core::num::NonZeroU32::new(x.checked_add(1).unwrap()).unwrap() - } - } - - impl core::convert::TryFrom for $operator { - type Error = (); - - #[inline] - fn try_from(x: u32) -> Result { - match x { - $( - x if x == Self::$variant.into() => Ok(Self::$variant), - )* - _ => Err(()) - } - } - } - - impl core::convert::TryFrom for $operator { - type Error = (); - - #[inline] - fn try_from(x: core::num::NonZeroU32) -> Result { - let x = x.get().checked_sub(1).ok_or(())?; - Self::try_from(x) - } - } - - $crate::define_parse_and_typing_rules_for_operator! { - $operator { - $( - $keywrord => $variant { - $( immediates( $($immediate),* ); )? - $( parameters( $($parameter),* ); )? - result( $result ); - $( is_reduce($is_reduce); )? - $( is_extend($is_extend); )? - } - )* - } - $( parse_cfg($parse_cfg); )? - } - } -} diff --git a/cranelift/peepmatic/crates/traits/src/typing.rs b/cranelift/peepmatic/crates/traits/src/typing.rs deleted file mode 100644 index 19babdf097..0000000000 --- a/cranelift/peepmatic/crates/traits/src/typing.rs +++ /dev/null @@ -1,97 +0,0 @@ -/// A trait to represent a typing context. -/// -/// This is used by the macro-generated operator methods that create the type -/// variables for their immediates, parameters, and results. This trait is -/// implemented by the concrete typing context in `peepmatic/src/verify.rs`. -pub trait TypingContext<'a> { - /// A source span. - type Span: Copy; - - /// A type variable. - type TypeVariable; - - /// Create a condition code type. - fn cc(&mut self, span: Self::Span) -> Self::TypeVariable; - - /// Create a boolean type with a polymorphic bit width. - /// - /// Each use of `bNN` by the same operator refers to the same type variable. - #[allow(non_snake_case)] - fn bNN(&mut self, span: Self::Span) -> Self::TypeVariable; - - /// Create an integer type with a polymorphic bit width. - /// - /// Each use of `iNN` by the same operator refers to the same type variable. - #[allow(non_snake_case)] - fn iNN(&mut self, span: Self::Span) -> Self::TypeVariable; - - /// Create an integer type with a polymorphic bit width. - /// - /// Each use of `iMM` by the same operator refers to the same type variable. - #[allow(non_snake_case)] - fn iMM(&mut self, span: Self::Span) -> Self::TypeVariable; - - /// Create the CPU flags type variable. - fn cpu_flags(&mut self, span: Self::Span) -> Self::TypeVariable; - - /// Create a boolean type of size one bit. - fn b1(&mut self, span: Self::Span) -> Self::TypeVariable; - - /// Create the void type, used as the result of operators that branch away, - /// or do not return anything. - fn void(&mut self, span: Self::Span) -> Self::TypeVariable; - - /// Create a type variable that may be either a boolean or an integer. - fn bool_or_int(&mut self, span: Self::Span) -> Self::TypeVariable; - - /// Create a type variable that can be any type T. - /// - /// Each use of `any_t` by the same operator refers to the same type - /// variable. - fn any_t(&mut self, span: Self::Span) -> Self::TypeVariable; -} - -/// The typing rules for a `TOperator` type. -/// -/// This trait describes the types of immediates, parameters, and results of an -/// operator type, as well as their arity. -pub trait TypingRules { - /// Get the result type of this operator. - fn result_type<'a, C>(&self, span: C::Span, typing_context: &mut C) -> C::TypeVariable - where - C: TypingContext<'a>; - - /// Get the number of immediates this operator has. - fn immediates_arity(&self) -> u8; - - /// Get the types of this operator's immediates. - fn immediate_types<'a, C>( - &self, - span: C::Span, - typing_context: &mut C, - types: &mut impl Extend, - ) where - C: TypingContext<'a>; - - /// Get the number of parameters this operator has. - fn parameters_arity(&self) -> u8; - - /// Get the types of this operator's parameters. - fn parameter_types<'a, C>( - &self, - span: C::Span, - typing_context: &mut C, - types: &mut impl Extend, - ) where - C: TypingContext<'a>; - - /// Is this a bit width reducing instruction? - /// - /// E.g. Cranelift's `ireduce` instruction. - fn is_reduce(&self) -> bool; - - /// Is this a bit width extending instruction? - /// - /// E.g. Cranelift's `uextend` and `sextend` instructions. - fn is_extend(&self) -> bool; -} diff --git a/cranelift/peepmatic/examples/mul-by-pow2.peepmatic b/cranelift/peepmatic/examples/mul-by-pow2.peepmatic deleted file mode 100644 index cca78a372f..0000000000 --- a/cranelift/peepmatic/examples/mul-by-pow2.peepmatic +++ /dev/null @@ -1,3 +0,0 @@ -(=> (when (imul $x $C) - (is-power-of-two $C)) - (ishl $x $(log2 $C))) diff --git a/cranelift/peepmatic/examples/redundant-bor.peepmatic b/cranelift/peepmatic/examples/redundant-bor.peepmatic deleted file mode 100644 index d8d6f4a144..0000000000 --- a/cranelift/peepmatic/examples/redundant-bor.peepmatic +++ /dev/null @@ -1,13 +0,0 @@ -;; Remove redundant bitwise OR instructions that are no-ops. - -(=> (bor $x (bor $x $y)) - (bor $x $y)) - -(=> (bor $y (bor $x $y)) - (bor $x $y)) - -(=> (bor (bor $x $y) $x) - (bor $x $y)) - -(=> (bor (bor $x $y) $y) - (bor $x $y)) diff --git a/cranelift/peepmatic/examples/redundant-bor.png b/cranelift/peepmatic/examples/redundant-bor.png deleted file mode 100644 index d7b2641a61c160949bcc1c3a76c86fa31620be76..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 258513 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

w{Wg*DHJ0)s0A^=C1~ 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�*CKg08S5uBa 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

PeMYsCg#AivVJf{mT}a|y?bFtt=lA%FhRRp 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­>GANJI5k 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 G>p!1BV)e(~~hAULHOGkZIA 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

fZS#^{MmMEr$bZI_M(R`x|k9OJc=;Y$g!qr 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 (bor $x (bor $x $y)) - (bor $x $y)) - -(=> (bor $y (bor $x $y)) - (bor $x $y)) - -(=> (bor (bor $x $y) $x) - (bor $x $y)) - -(=> (bor (bor $x $y) $y) - (bor $x $y)) diff --git a/cranelift/peepmatic/src/ast.rs b/cranelift/peepmatic/src/ast.rs deleted file mode 100644 index 2cd0643bc4..0000000000 --- a/cranelift/peepmatic/src/ast.rs +++ /dev/null @@ -1,534 +0,0 @@ -//! Abstract syntax tree type definitions. -//! -//! This file makes fairly heavy use of macros, which are defined in the -//! `peepmatic_macro` crate that lives at `crates/macro`. Notably, the following -//! traits are all derived via `derive(Ast)`: -//! -//! * `Span` -- access the `wast::Span` where an AST node was parsed from. For -//! `struct`s, there must be a `span: wast::Span` field, because the macro -//! always generates an implementation that returns `self.span` for -//! `struct`s. For `enum`s, every variant must have a single, unnamed field -//! which implements the `Span` trait. The macro will generate code to return -//! the span of whatever variant it is. -//! -//! * `ChildNodes` -- get each of the child AST nodes that a given node -//! references. Some fields in an AST type aren't actually considered an AST -//! node (like spans) and these are ignored via the `#[peepmatic(skip_child)]` -//! attribute. Some fields contain multiple AST nodes (like vectors of -//! operands) and these are flattened with `#[peepmatic(flatten)]`. -//! -//! * `From<&'a Self> for DynAstRef<'a>` -- convert a particular AST type into -//! `DynAstRef`, which is an `enum` of all the different kinds of AST nodes. - -use peepmatic_macro::Ast; -use peepmatic_runtime::{ - r#type::{BitWidth, Type}, - unquote::UnquoteOperator, -}; -use std::cell::Cell; -use std::hash::{Hash, Hasher}; -use std::marker::PhantomData; -use wast::Id; - -/// A reference to any AST node. -#[derive(Debug, Clone, Copy)] -pub enum DynAstRef<'a, TOperator> { - /// A reference to an `Optimizations`. - Optimizations(&'a Optimizations<'a, TOperator>), - - /// A reference to an `Optimization`. - Optimization(&'a Optimization<'a, TOperator>), - - /// A reference to an `Lhs`. - Lhs(&'a Lhs<'a, TOperator>), - - /// A reference to an `Rhs`. - Rhs(&'a Rhs<'a, TOperator>), - - /// A reference to a `Pattern`. - Pattern(&'a Pattern<'a, TOperator>), - - /// A reference to a `Precondition`. - Precondition(&'a Precondition<'a, TOperator>), - - /// A reference to a `ConstraintOperand`. - ConstraintOperand(&'a ConstraintOperand<'a, TOperator>), - - /// A reference to a `ValueLiteral`. - ValueLiteral(&'a ValueLiteral<'a, TOperator>), - - /// A reference to a `Constant`. - Constant(&'a Constant<'a, TOperator>), - - /// A reference to a `PatternOperation`. - PatternOperation(&'a Operation<'a, TOperator, Pattern<'a, TOperator>>), - - /// A reference to a `Variable`. - Variable(&'a Variable<'a, TOperator>), - - /// A reference to an `Integer`. - Integer(&'a Integer<'a, TOperator>), - - /// A reference to a `Boolean`. - Boolean(&'a Boolean<'a, TOperator>), - - /// A reference to a `ConditionCode`. - ConditionCode(&'a ConditionCode<'a, TOperator>), - - /// A reference to an `Unquote`. - Unquote(&'a Unquote<'a, TOperator>), - - /// A reference to an `RhsOperation`. - RhsOperation(&'a Operation<'a, TOperator, Rhs<'a, TOperator>>), -} - -impl<'a, 'b, TOperator> ChildNodes<'a, 'b, TOperator> for DynAstRef<'a, TOperator> { - fn child_nodes(&'b self, sink: &mut impl Extend>) { - match self { - Self::Optimizations(x) => x.child_nodes(sink), - Self::Optimization(x) => x.child_nodes(sink), - Self::Lhs(x) => x.child_nodes(sink), - Self::Rhs(x) => x.child_nodes(sink), - Self::Pattern(x) => x.child_nodes(sink), - Self::Precondition(x) => x.child_nodes(sink), - Self::ConstraintOperand(x) => x.child_nodes(sink), - Self::ValueLiteral(x) => x.child_nodes(sink), - Self::Constant(x) => x.child_nodes(sink), - Self::PatternOperation(x) => x.child_nodes(sink), - Self::Variable(x) => x.child_nodes(sink), - Self::Integer(x) => x.child_nodes(sink), - Self::Boolean(x) => x.child_nodes(sink), - Self::ConditionCode(x) => x.child_nodes(sink), - Self::Unquote(x) => x.child_nodes(sink), - Self::RhsOperation(x) => x.child_nodes(sink), - } - } -} - -/// A trait implemented by all AST nodes. -/// -/// All AST nodes can: -/// -/// * Enumerate their children via `ChildNodes`. -/// -/// * Give you the `wast::Span` where they were defined. -/// -/// * Be converted into a `DynAstRef`. -/// -/// This trait is blanked implemented for everything that does those three -/// things, and in practice those three thrings are all implemented by the -/// `derive(Ast)` macro. -pub trait Ast<'a, TOperator>: 'a + ChildNodes<'a, 'a, TOperator> + Span -where - DynAstRef<'a, TOperator>: From<&'a Self>, - TOperator: 'a, -{ -} - -impl<'a, T, TOperator> Ast<'a, TOperator> for T -where - T: 'a + ?Sized + ChildNodes<'a, 'a, TOperator> + Span, - DynAstRef<'a, TOperator>: From<&'a Self>, - TOperator: 'a, -{ -} - -/// Enumerate the child AST nodes of a given node. -pub trait ChildNodes<'a, 'b, TOperator> -where - TOperator: 'a, -{ - /// Get each of this AST node's children, in order. - fn child_nodes(&'b self, sink: &mut impl Extend>); -} - -/// A trait for getting the span where an AST node was defined. -pub trait Span { - /// Get the span where this AST node was defined. - fn span(&self) -> wast::Span; -} - -/// A set of optimizations. -/// -/// This is the root AST node. -#[derive(Debug, Ast)] -pub struct Optimizations<'a, TOperator> { - /// Where these `Optimizations` were defined. - #[peepmatic(skip_child)] - pub span: wast::Span, - - /// The optimizations. - #[peepmatic(flatten)] - pub optimizations: Vec>, -} - -/// A complete optimization: a left-hand side to match against and a right-hand -/// side replacement. -#[derive(Debug, Ast)] -pub struct Optimization<'a, TOperator> { - /// Where this `Optimization` was defined. - #[peepmatic(skip_child)] - pub span: wast::Span, - - /// The left-hand side that matches when this optimization applies. - pub lhs: Lhs<'a, TOperator>, - - /// The new sequence of instructions to replace an old sequence that matches - /// the left-hand side with. - pub rhs: Rhs<'a, TOperator>, -} - -/// A left-hand side describes what is required for a particular optimization to -/// apply. -/// -/// A left-hand side has two parts: a structural pattern for describing -/// candidate instruction sequences, and zero or more preconditions that add -/// additional constraints upon instruction sequences matched by the pattern. -#[derive(Debug, Ast)] -pub struct Lhs<'a, TOperator> { - /// Where this `Lhs` was defined. - #[peepmatic(skip_child)] - pub span: wast::Span, - - /// A pattern that describes sequences of instructions to match. - pub pattern: Pattern<'a, TOperator>, - - /// Additional constraints that a match must satisfy in addition to - /// structually matching the pattern, e.g. some constant must be a power of - /// two. - #[peepmatic(flatten)] - pub preconditions: Vec>, -} - -/// A structural pattern, potentially with wildcard variables for matching whole -/// subtrees. -#[derive(Debug, Ast)] -pub enum Pattern<'a, TOperator> { - /// A specific value. These are written as `1234` or `0x1234` or `true` or - /// `false`. - ValueLiteral(ValueLiteral<'a, TOperator>), - - /// A constant that matches any constant value. This subsumes value - /// patterns. These are upper-case identifiers like `$C`. - Constant(Constant<'a, TOperator>), - - /// An operation pattern with zero or more operand patterns. These are - /// s-expressions like `(iadd $x $y)`. - Operation(Operation<'a, TOperator, Pattern<'a, TOperator>>), - - /// A variable that matches any kind of subexpression. This subsumes all - /// other patterns. These are lower-case identifiers like `$x`. - Variable(Variable<'a, TOperator>), -} - -/// An integer or boolean value literal. -#[derive(Debug, Ast)] -pub enum ValueLiteral<'a, TOperator> { - /// An integer value. - Integer(Integer<'a, TOperator>), - - /// A boolean value: `true` or `false`. - Boolean(Boolean<'a, TOperator>), - - /// A condition code: `eq`, `ne`, etc... - ConditionCode(ConditionCode<'a, TOperator>), -} - -/// An integer literal. -#[derive(Debug, PartialEq, Eq, Ast)] -pub struct Integer<'a, TOperator> { - /// Where this `Integer` was defined. - #[peepmatic(skip_child)] - pub span: wast::Span, - - /// The integer value. - /// - /// Note that although Cranelift allows 128 bits wide values, the widest - /// supported constants as immediates are 64 bits. - #[peepmatic(skip_child)] - pub value: i64, - - /// The bit width of this integer. - /// - /// This is either a fixed bit width, or polymorphic over the width of the - /// optimization. - /// - /// This field is initialized from `None` to `Some` by the type checking - /// pass in `src/verify.rs`. - #[peepmatic(skip_child)] - pub bit_width: Cell>, - - #[allow(missing_docs)] - #[peepmatic(skip_child)] - pub marker: PhantomData<&'a TOperator>, -} - -impl Hash for Integer<'_, TOperator> { - fn hash(&self, state: &mut H) - where - H: Hasher, - { - let Integer { - span, - value, - bit_width, - marker: _, - } = self; - span.hash(state); - value.hash(state); - let bit_width = bit_width.get(); - bit_width.hash(state); - } -} - -/// A boolean literal. -#[derive(Debug, PartialEq, Eq, Ast)] -pub struct Boolean<'a, TOperator> { - /// Where this `Boolean` was defined. - #[peepmatic(skip_child)] - pub span: wast::Span, - - /// The boolean value. - #[peepmatic(skip_child)] - pub value: bool, - - /// The bit width of this boolean. - /// - /// This is either a fixed bit width, or polymorphic over the width of the - /// optimization. - /// - /// This field is initialized from `None` to `Some` by the type checking - /// pass in `src/verify.rs`. - #[peepmatic(skip_child)] - pub bit_width: Cell>, - - #[allow(missing_docs)] - #[peepmatic(skip_child)] - pub marker: PhantomData<&'a TOperator>, -} - -impl Hash for Boolean<'_, TOperator> { - fn hash(&self, state: &mut H) - where - H: Hasher, - { - let Boolean { - span, - value, - bit_width, - marker: _, - } = self; - span.hash(state); - value.hash(state); - let bit_width = bit_width.get(); - bit_width.hash(state); - } -} - -/// A condition code. -#[derive(Debug, Ast)] -pub struct ConditionCode<'a, TOperator> { - /// Where this `ConditionCode` was defined. - #[peepmatic(skip_child)] - pub span: wast::Span, - - /// The actual condition code. - #[peepmatic(skip_child)] - pub cc: peepmatic_runtime::cc::ConditionCode, - - #[allow(missing_docs)] - #[peepmatic(skip_child)] - pub marker: PhantomData<&'a TOperator>, -} - -/// A symbolic constant. -/// -/// These are identifiers containing uppercase letters: `$C`, `$MY-CONST`, -/// `$CONSTANT1`. -#[derive(Debug, Ast)] -pub struct Constant<'a, TOperator> { - /// Where this `Constant` was defined. - #[peepmatic(skip_child)] - pub span: wast::Span, - - /// This constant's identifier. - #[peepmatic(skip_child)] - pub id: Id<'a>, - - #[allow(missing_docs)] - #[peepmatic(skip_child)] - pub marker: PhantomData<&'a TOperator>, -} - -/// A variable that matches any subtree. -/// -/// Duplicate uses of the same variable constrain each occurrence's match to -/// being the same as each other occurrence as well, e.g. `(iadd $x $x)` matches -/// `(iadd 5 5)` but not `(iadd 1 2)`. -#[derive(Debug, Ast)] -pub struct Variable<'a, TOperator> { - /// Where this `Variable` was defined. - #[peepmatic(skip_child)] - pub span: wast::Span, - - /// This variable's identifier. - #[peepmatic(skip_child)] - pub id: Id<'a>, - - #[allow(missing_docs)] - #[peepmatic(skip_child)] - pub marker: PhantomData<&'a TOperator>, -} - -/// An operation with an operator, and operands of type `T`. -#[derive(Debug, Ast)] -#[peepmatic(no_into_dyn_node)] -pub struct Operation<'a, TOperator, TOperand> -where - TOperator: 'a, - TOperand: 'a + Ast<'a, TOperator>, - DynAstRef<'a, TOperator>: From<&'a TOperand>, -{ - /// The span where this operation was written. - #[peepmatic(skip_child)] - pub span: wast::Span, - - /// The operator for this operation, e.g. `imul` or `iadd`. - #[peepmatic(skip_child)] - pub operator: TOperator, - - /// An optional ascribed or inferred type for the operator. - #[peepmatic(skip_child)] - pub r#type: Cell>, - - /// This operation's operands. - /// - /// When `Operation` is used in a pattern, these are the sub-patterns for - /// the operands. When `Operation is used in a right-hand side replacement, - /// these are the sub-replacements for the operands. - #[peepmatic(flatten)] - pub operands: Vec, - - #[allow(missing_docs)] - #[peepmatic(skip_child)] - pub marker: PhantomData<&'a ()>, -} - -impl<'a, TOperator> From<&'a Operation<'a, TOperator, Pattern<'a, TOperator>>> - for DynAstRef<'a, TOperator> -{ - #[inline] - fn from(o: &'a Operation<'a, TOperator, Pattern<'a, TOperator>>) -> DynAstRef<'a, TOperator> { - DynAstRef::PatternOperation(o) - } -} - -impl<'a, TOperator> From<&'a Operation<'a, TOperator, Rhs<'a, TOperator>>> - for DynAstRef<'a, TOperator> -{ - #[inline] - fn from(o: &'a Operation<'a, TOperator, Rhs<'a, TOperator>>) -> DynAstRef<'a, TOperator> { - DynAstRef::RhsOperation(o) - } -} - -/// A precondition adds additional constraints to a pattern, such as "$C must be -/// a power of two". -#[derive(Debug, Ast)] -pub struct Precondition<'a, TOperator> { - /// Where this `Precondition` was defined. - #[peepmatic(skip_child)] - pub span: wast::Span, - - /// The constraint operator. - #[peepmatic(skip_child)] - pub constraint: Constraint, - - /// The operands of the constraint. - #[peepmatic(flatten)] - pub operands: Vec>, - - #[allow(missing_docs)] - #[peepmatic(skip_child)] - pub marker: PhantomData<&'a TOperator>, -} - -/// Contraint operators. -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] -pub enum Constraint { - /// Is the operand a power of two? - IsPowerOfTwo, - - /// Check the bit width of a value. - BitWidth, - - /// Does the argument fit within our target architecture's native word size? - FitsInNativeWord, -} - -/// An operand of a precondition's constraint. -#[derive(Debug, Ast)] -pub enum ConstraintOperand<'a, TOperator> { - /// A value literal operand. - ValueLiteral(ValueLiteral<'a, TOperator>), - - /// A constant operand. - Constant(Constant<'a, TOperator>), - - /// A variable operand. - Variable(Variable<'a, TOperator>), -} - -/// The right-hand side of an optimization that contains the instructions to -/// replace any matched left-hand side with. -#[derive(Debug, Ast)] -pub enum Rhs<'a, TOperator> { - /// A value literal right-hand side. - ValueLiteral(ValueLiteral<'a, TOperator>), - - /// A constant right-hand side (the constant must have been matched and - /// bound in the left-hand side's pattern). - Constant(Constant<'a, TOperator>), - - /// A variable right-hand side (the variable must have been matched and - /// bound in the left-hand side's pattern). - Variable(Variable<'a, TOperator>), - - /// An unquote expression that is evaluated while replacing the left-hand - /// side with the right-hand side. The result of the evaluation is used in - /// the replacement. - Unquote(Unquote<'a, TOperator>), - - /// A compound right-hand side consisting of an operation and subsequent - /// right-hand side operands. - Operation(Operation<'a, TOperator, Rhs<'a, TOperator>>), -} - -/// An unquote operation. -/// -/// Rather than replaciong a left-hand side, these are evaluated and then the -/// result of the evaluation replaces the left-hand side. This allows for -/// compile-time computation while replacing a matched left-hand side with a -/// right-hand side. -/// -/// For example, given the unqouted right-hand side `$(log2 $C)`, we replace any -/// instructions that match its left-hand side with the compile-time result of -/// `log2($C)` (the left-hand side must match and bind the constant `$C`). -#[derive(Debug, Ast)] -pub struct Unquote<'a, TOperator> { - /// Where this `Unquote` was defined. - #[peepmatic(skip_child)] - pub span: wast::Span, - - /// The operator for this unquote operation. - #[peepmatic(skip_child)] - pub operator: UnquoteOperator, - - /// The operands for this unquote operation. - #[peepmatic(flatten)] - pub operands: Vec>, - - #[allow(missing_docs)] - #[peepmatic(skip_child)] - pub marker: PhantomData<&'a TOperator>, -} diff --git a/cranelift/peepmatic/src/automatize.rs b/cranelift/peepmatic/src/automatize.rs deleted file mode 100644 index 67197523df..0000000000 --- a/cranelift/peepmatic/src/automatize.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! Compile a set of linear optimizations into an automaton. - -use peepmatic_automata::{Automaton, Builder}; -use peepmatic_runtime::linear; -use std::fmt::Debug; -use std::hash::Hash; - -/// Construct an automaton from a set of linear optimizations. -pub fn automatize( - opts: &linear::Optimizations, -) -> Automaton]>> -where - TOperator: Copy + Debug + Eq + Hash, -{ - debug_assert!(crate::linear_passes::is_sorted_lexicographically(opts)); - - let mut builder = - Builder::]>>::new(); - - for opt in &opts.optimizations { - let mut insertion = builder.insert(); - let mut is_first = true; - for m in &opt.matches { - // Ensure that this state's associated data is this match's - // operation. - if let Some(op) = insertion.get_state_data() { - assert_eq!(*op, m.operation); - } else { - insertion.set_state_data(m.operation); - } - - let actions = if is_first { - is_first = false; - opt.actions.clone().into_boxed_slice() - } else { - vec![].into_boxed_slice() - }; - insertion.next(m.expected, actions); - } - insertion.finish(); - } - - builder.finish() -} diff --git a/cranelift/peepmatic/src/dot_fmt.rs b/cranelift/peepmatic/src/dot_fmt.rs deleted file mode 100644 index 71a9533417..0000000000 --- a/cranelift/peepmatic/src/dot_fmt.rs +++ /dev/null @@ -1,140 +0,0 @@ -//! Formatting a peephole optimizer's automata for GraphViz Dot. -//! -//! See also `crates/automata/src/dot.rs`. - -use peepmatic_automata::dot::DotFmt; -use peepmatic_runtime::{ - cc::ConditionCode, - integer_interner::{IntegerId, IntegerInterner}, - linear, -}; -use std::convert::{TryFrom, TryInto}; -use std::fmt::Debug; -use std::io::{self, Write}; -use std::num::{NonZeroU16, NonZeroU32}; - -#[derive(Debug)] -pub(crate) struct PeepholeDotFmt<'a>(pub(crate) &'a IntegerInterner); - -impl DotFmt]>> - for PeepholeDotFmt<'_> -where - TOperator: Debug + TryFrom, -{ - fn fmt_transition( - &self, - w: &mut impl Write, - from: Option<&linear::MatchOp>, - input: &linear::MatchResult, - _to: Option<&linear::MatchOp>, - ) -> io::Result<()> { - let from = from.expect("we should have match op for every state"); - if let Some(x) = input.ok() { - match from { - linear::MatchOp::Opcode { .. } => { - let opcode = TOperator::try_from(x) - .map_err(|_| ()) - .expect("we shouldn't generate non-opcode edges"); - write!(w, "{:?}", opcode) - } - linear::MatchOp::ConditionCode { .. } => { - let cc = ConditionCode::try_from(x.get()) - .expect("we shouldn't generate non-CC edges"); - write!(w, "{}", cc) - } - linear::MatchOp::IntegerValue { .. } => { - let x = self.0.lookup(IntegerId( - NonZeroU16::new(x.get().try_into().unwrap()).unwrap(), - )); - write!(w, "{}", x) - } - _ => write!(w, "Ok({})", x), - } - } else { - write!(w, "(else)") - } - } - - fn fmt_state(&self, w: &mut impl Write, op: &linear::MatchOp) -> io::Result<()> { - use linear::MatchOp::*; - - write!(w, r#""#)?; - - match op { - Opcode(id) => write!(w, "opcode $lhs{}", id.0)?, - IsConst(id) => write!(w, "is-const? $lhs{}", id.0)?, - IsPowerOfTwo(id) => write!(w, "is-power-of-two? $lhs{}", id.0)?, - BitWidth(id) => write!(w, "bit-width $lhs{}", id.0)?, - FitsInNativeWord(id) => write!(w, "fits-in-native-word $lhs{}", id.0)?, - Eq(a, b) => write!(w, "$lhs{} == $lhs{}", a.0, b.0)?, - IntegerValue(id) => write!(w, "integer-value $lhs{}", id.0)?, - BooleanValue(id) => write!(w, "boolean-value $lhs{}", id.0)?, - ConditionCode(id) => write!(w, "condition-code $lhs{}", id.0)?, - Nop => write!(w, "nop")?, - } - - writeln!(w, "") - } - - fn fmt_output( - &self, - w: &mut impl Write, - actions: &Box<[linear::Action]>, - ) -> io::Result<()> { - use linear::Action::*; - - if actions.is_empty() { - return writeln!(w, "(no output)"); - } - - write!(w, r#""#)?; - - for a in actions.iter() { - match a { - GetLhs { lhs } => write!(w, "get-lhs $lhs{}
", lhs.0)?, - UnaryUnquote { operator, operand } => { - write!(w, "eval {:?} $rhs{}
", operator, operand.0)? - } - BinaryUnquote { operator, operands } => write!( - w, - "eval {:?} $rhs{}, $rhs{}
", - operator, operands[0].0, operands[1].0, - )?, - MakeIntegerConst { - value, - bit_width: _, - } => write!(w, "make {}
", self.0.lookup(*value))?, - MakeBooleanConst { - value, - bit_width: _, - } => write!(w, "make {}
", value)?, - MakeConditionCode { cc } => write!(w, "{}
", cc)?, - MakeUnaryInst { - operand, - operator, - r#type: _, - } => write!(w, "make {:?} $rhs{}
", operator, operand.0,)?, - MakeBinaryInst { - operator, - operands, - r#type: _, - } => write!( - w, - "make {:?} $rhs{}, $rhs{}
", - operator, operands[0].0, operands[1].0, - )?, - MakeTernaryInst { - operator, - operands, - r#type: _, - } => write!( - w, - "make {:?} $rhs{}, $rhs{}, $rhs{}
", - operator, operands[0].0, operands[1].0, operands[2].0, - )?, - } - } - - writeln!(w, "
") - } -} diff --git a/cranelift/peepmatic/src/lib.rs b/cranelift/peepmatic/src/lib.rs deleted file mode 100644 index 9b32e06f44..0000000000 --- a/cranelift/peepmatic/src/lib.rs +++ /dev/null @@ -1,189 +0,0 @@ -/*! - -`peepmatic` is a DSL and compiler for generating peephole optimizers. - -The user writes a set of optimizations in the DSL, and then `peepmatic` compiles -the set of optimizations into an efficient peephole optimizer. - - */ - -#![deny(missing_docs)] -#![deny(missing_debug_implementations)] - -mod ast; -mod automatize; -mod dot_fmt; -mod linear_passes; -mod linearize; -mod parser; -mod traversals; -mod verify; -pub use self::{ - ast::*, automatize::*, linear_passes::*, linearize::*, parser::*, traversals::*, verify::*, -}; - -use peepmatic_runtime::PeepholeOptimizations; -use peepmatic_traits::TypingRules; -use std::convert::TryFrom; -use std::fmt::Debug; -use std::fs; -use std::hash::Hash; -use std::num::NonZeroU32; -use std::path::Path; - -/// Compile the given DSL file into a compact peephole optimizations automaton! -/// -/// ## Example -/// -/// ```ignore -/// # fn main() -> anyhow::Result<()> { -/// use std::path::Path; -/// -/// let peep_opts = peepmatic::compile_file::( -/// Path::new("path/to/optimizations.peepmatic") -/// )?; -/// -/// // Use the peephole optimizations or serialize them into bytes here... -/// # Ok(()) -/// # } -/// ``` -/// -/// ## Visualizing the Peephole Optimizer's Automaton -/// -/// To visualize (or debug) the peephole optimizer's automaton, set the -/// `PEEPMATIC_DOT` environment variable to a file path. A [GraphViz -/// Dot]((https://graphviz.gitlab.io/_pages/pdf/dotguide.pdf)) file showing the -/// peephole optimizer's automaton will be written to that file path. -pub fn compile_file(filename: &Path) -> anyhow::Result> -where - TOperator: Copy - + Debug - + Eq - + Hash - + for<'a> wast::parser::Parse<'a> - + TypingRules - + Into - + TryFrom, -{ - let source = fs::read_to_string(filename)?; - compile_str::(&source, filename) -} - -/// Compile the given DSL source text down into a compact peephole optimizations -/// automaton. -/// -/// This is like [compile_file][crate::compile_file] but you bring your own file -/// I/O. -/// -/// The `filename` parameter is used to provide better error messages. -/// -/// ## Example -/// -/// ```ignore -/// # fn main() -> anyhow::Result<()> { -/// use std::path::Path; -/// -/// let peep_opts = peepmatic::compile_str::( -/// " -/// (=> (iadd $x 0) $x) -/// (=> (imul $x 0) 0) -/// (=> (imul $x 1) $x) -/// ", -/// Path::new("my-optimizations"), -/// )?; -/// -/// // Use the peephole optimizations or serialize them into bytes here... -/// # Ok(()) -/// # } -/// ``` -/// -/// ## Visualizing the Peephole Optimizer's Automaton -/// -/// To visualize (or debug) the peephole optimizer's automaton, set the -/// `PEEPMATIC_DOT` environment variable to a file path. A [GraphViz -/// Dot]((https://graphviz.gitlab.io/_pages/pdf/dotguide.pdf)) file showing the -/// peephole optimizer's automaton will be written to that file path. -pub fn compile_str( - source: &str, - filename: &Path, -) -> anyhow::Result> -where - TOperator: Copy - + Debug - + Eq - + Hash - + for<'a> wast::parser::Parse<'a> - + TypingRules - + Into - + TryFrom, -{ - let buf = wast::parser::ParseBuffer::new(source).map_err(|mut e| { - e.set_path(filename); - e.set_text(source); - e - })?; - - let opts = wast::parser::parse::>(&buf).map_err(|mut e| { - e.set_path(filename); - e.set_text(source); - e - })?; - - verify(&opts).map_err(|mut e| { - e.set_path(filename); - e.set_text(source); - e - })?; - - let mut opts = crate::linearize(&opts); - sort_least_to_most_general(&mut opts); - remove_unnecessary_nops(&mut opts); - match_in_same_order(&mut opts); - sort_lexicographically(&mut opts); - - let automata = automatize(&opts); - let integers = opts.integers; - - if let Ok(path) = std::env::var("PEEPMATIC_DOT") { - let f = dot_fmt::PeepholeDotFmt(&integers); - if let Err(e) = automata.write_dot_file(&f, &path) { - panic!( - "failed to write GraphViz Dot file to PEEPMATIC_DOT={}; error: {}", - path, e - ); - } - } - - Ok(PeepholeOptimizations { integers, automata }) -} - -#[cfg(test)] -mod tests { - use super::*; - use peepmatic_test_operator::TestOperator; - - fn assert_compiles(path: &str) { - match compile_file::(Path::new(path)) { - Ok(_) => return, - Err(e) => { - eprintln!("error: {}", e); - panic!("error: {}", e); - } - } - } - - #[test] - fn compile_redundant_bor() { - assert_compiles("examples/redundant-bor.peepmatic"); - } - - #[test] - fn mul_by_pow2() { - assert_compiles("examples/mul-by-pow2.peepmatic"); - } - - #[test] - fn compile_preopt() { - assert_compiles("../codegen/src/preopt.peepmatic"); - } -} diff --git a/cranelift/peepmatic/src/linear_passes.rs b/cranelift/peepmatic/src/linear_passes.rs deleted file mode 100644 index 143e1a19c1..0000000000 --- a/cranelift/peepmatic/src/linear_passes.rs +++ /dev/null @@ -1,594 +0,0 @@ -//! Passes over the linear IR. - -use peepmatic_runtime::linear; -use std::cmp::Ordering; -use std::fmt::Debug; -use std::hash::Hash; - -/// Sort a set of optimizations from least to most general. -/// -/// This helps us ensure that we always match the least-general (aka -/// most-specific) optimization that we can for a particular instruction -/// sequence. -/// -/// For example, if we have both of these optimizations: -/// -/// ```lisp -/// (=> (imul $C $x) -/// (imul_imm $C $x)) -/// -/// (=> (when (imul $C $x)) -/// (is-power-of-two $C)) -/// (ishl $x $C)) -/// ``` -/// -/// and we are matching `(imul 4 (..))`, then we want to apply the second -/// optimization, because it is more specific than the first. -pub fn sort_least_to_most_general(opts: &mut linear::Optimizations) -where - TOperator: Copy + Debug + Eq + Hash, -{ - let linear::Optimizations { - ref mut optimizations, - .. - } = opts; - - // NB: we *cannot* use an unstable sort here, because we want deterministic - // compilation of optimizations to automata. - optimizations.sort_by(compare_optimization_generality); - debug_assert!(is_sorted_by_generality(opts)); -} - -/// Sort the linear optimizations lexicographically. -/// -/// This sort order is required for automata construction. -pub fn sort_lexicographically(opts: &mut linear::Optimizations) -where - TOperator: Copy + Debug + Eq + Hash, -{ - let linear::Optimizations { - ref mut optimizations, - .. - } = opts; - - // NB: we *cannot* use an unstable sort here, same as above. - optimizations.sort_by(|a, b| compare_optimizations(a, b, |a_len, b_len| a_len.cmp(&b_len))); -} - -fn compare_optimizations( - a: &linear::Optimization, - b: &linear::Optimization, - compare_lengths: impl Fn(usize, usize) -> Ordering, -) -> Ordering -where - TOperator: Copy + Debug + Eq + Hash, -{ - for (a, b) in a.matches.iter().zip(b.matches.iter()) { - let c = compare_match_op_generality(a.operation, b.operation); - if c != Ordering::Equal { - return c; - } - - let c = match (a.expected, b.expected) { - (Ok(a), Ok(b)) => a.cmp(&b).reverse(), - (Err(_), Ok(_)) => Ordering::Greater, - (Ok(_), Err(_)) => Ordering::Less, - (Err(linear::Else), Err(linear::Else)) => Ordering::Equal, - }; - if c != Ordering::Equal { - return c; - } - } - - compare_lengths(a.matches.len(), b.matches.len()) -} - -fn compare_optimization_generality( - a: &linear::Optimization, - b: &linear::Optimization, -) -> Ordering -where - TOperator: Copy + Debug + Eq + Hash, -{ - compare_optimizations(a, b, |a_len, b_len| { - // If they shared equivalent prefixes, then compare lengths and invert the - // result because longer patterns are less general than shorter patterns. - a_len.cmp(&b_len).reverse() - }) -} - -fn compare_match_op_generality(a: linear::MatchOp, b: linear::MatchOp) -> Ordering { - use linear::MatchOp::*; - match (a, b) { - (Opcode(a), Opcode(b)) => a.cmp(&b), - (Opcode(_), _) => Ordering::Less, - (_, Opcode(_)) => Ordering::Greater, - - (IntegerValue(a), IntegerValue(b)) => a.cmp(&b), - (IntegerValue(_), _) => Ordering::Less, - (_, IntegerValue(_)) => Ordering::Greater, - - (BooleanValue(a), BooleanValue(b)) => a.cmp(&b), - (BooleanValue(_), _) => Ordering::Less, - (_, BooleanValue(_)) => Ordering::Greater, - - (ConditionCode(a), ConditionCode(b)) => a.cmp(&b), - (ConditionCode(_), _) => Ordering::Less, - (_, ConditionCode(_)) => Ordering::Greater, - - (IsConst(a), IsConst(b)) => a.cmp(&b), - (IsConst(_), _) => Ordering::Less, - (_, IsConst(_)) => Ordering::Greater, - - (Eq(a1, b1), Eq(a2, b2)) => a1.cmp(&a2).then(b1.cmp(&b2)), - (Eq(..), _) => Ordering::Less, - (_, Eq(..)) => Ordering::Greater, - - (IsPowerOfTwo(a), IsPowerOfTwo(b)) => a.cmp(&b), - (IsPowerOfTwo(_), _) => Ordering::Less, - (_, IsPowerOfTwo(_)) => Ordering::Greater, - - (BitWidth(a), BitWidth(b)) => a.cmp(&b), - (BitWidth(_), _) => Ordering::Less, - (_, BitWidth(_)) => Ordering::Greater, - - (FitsInNativeWord(a), FitsInNativeWord(b)) => a.cmp(&b), - (FitsInNativeWord(_), _) => Ordering::Less, - (_, FitsInNativeWord(_)) => Ordering::Greater, - - (Nop, Nop) => Ordering::Equal, - } -} - -/// Are the given optimizations sorted from least to most general? -pub(crate) fn is_sorted_by_generality(opts: &linear::Optimizations) -> bool -where - TOperator: Copy + Debug + Eq + Hash, -{ - opts.optimizations - .windows(2) - .all(|w| compare_optimization_generality(&w[0], &w[1]) <= Ordering::Equal) -} - -/// Are the given optimizations sorted lexicographically? -pub(crate) fn is_sorted_lexicographically( - opts: &linear::Optimizations, -) -> bool -where - TOperator: Copy + Debug + Eq + Hash, -{ - opts.optimizations.windows(2).all(|w| { - compare_optimizations(&w[0], &w[1], |a_len, b_len| a_len.cmp(&b_len)) <= Ordering::Equal - }) -} - -/// Ensure that we emit match operations in a consistent order. -/// -/// There are many linear optimizations, each of which have their own sequence -/// of match operations that need to be tested. But when interpreting the -/// automata against some instructions, we only perform a single sequence of -/// match operations, and at any given moment, we only want one match operation -/// to interpret next. This means that two optimizations that are next to each -/// other in the sorting must have their shared prefixes diverge on an -/// **expected result edge**, not on which match operation to preform next. And -/// if they have zero shared prefix, then we need to create one, that -/// immediately divereges on the expected result. -/// -/// For example, consider these two patterns that don't have any shared prefix: -/// -/// ```lisp -/// (=> (iadd $x $y) ...) -/// (=> $C ...) -/// ``` -/// -/// These produce the following linear match operations and expected results: -/// -/// ```text -/// opcode @ 0 --iadd--> -/// is-const? @ 0 --true--> -/// ``` -/// -/// In order to ensure that we only have one match operation to interpret at any -/// given time when evaluating the automata, this pass transforms the second -/// optimization so that it shares a prefix match operation, but diverges on the -/// expected result: -/// -/// ```text -/// opcode @ 0 --iadd--> -/// opcode @ 0 --(else)--> is-const? @ 0 --true--> -/// ``` -pub fn match_in_same_order(opts: &mut linear::Optimizations) -where - TOperator: Copy + Debug + Eq + Hash, -{ - assert!(!opts.optimizations.is_empty()); - - let mut prefix = vec![]; - - for opt in &mut opts.optimizations { - assert!(!opt.matches.is_empty()); - - let mut old_matches = opt.matches.iter().peekable(); - let mut new_matches = vec![]; - - for (last_op, last_expected) in &prefix { - match old_matches.peek() { - None => { - break; - } - Some(inc) if *last_op == inc.operation => { - let inc = old_matches.next().unwrap(); - new_matches.push(inc.clone()); - if inc.expected != *last_expected { - break; - } - } - Some(_) => { - new_matches.push(linear::Match { - operation: *last_op, - expected: Err(linear::Else), - }); - if last_expected.is_ok() { - break; - } - } - } - } - - new_matches.extend(old_matches.cloned()); - assert!(new_matches.len() >= opt.matches.len()); - opt.matches = new_matches; - - prefix.clear(); - prefix.extend(opt.matches.iter().map(|inc| (inc.operation, inc.expected))); - } - - // Should still be sorted after this pass. - debug_assert!(is_sorted_by_generality(&opts)); -} - -/// 99.99% of nops are unnecessary; remove them. -/// -/// They're only needed for when a LHS pattern is just a variable, and that's -/// it. However, it is easier to have basically unused nop matching operations -/// for the DSL's edge-cases than it is to try and statically eliminate their -/// existence completely. So we just emit nop match operations for all variable -/// patterns, and then in this post-processing pass, we fuse them and their -/// actions with their preceding match. -pub fn remove_unnecessary_nops(opts: &mut linear::Optimizations) -where - TOperator: Copy + Debug + Eq + Hash, -{ - for opt in &mut opts.optimizations { - if opt.matches.len() < 2 { - debug_assert!(!opt.matches.is_empty()); - continue; - } - - for i in (1..opt.matches.len()).rev() { - if let linear::MatchOp::Nop = opt.matches[i].operation { - opt.matches.remove(i); - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::ast::*; - use peepmatic_runtime::linear::{bool_to_match_result, Else, LhsId, MatchOp::*, MatchResult}; - use peepmatic_test_operator::TestOperator; - use std::num::NonZeroU32; - - #[test] - fn ok_non_zero_less_than_err_else() { - assert!(Ok(NonZeroU32::new(1).unwrap()) < Err(Else)); - } - - macro_rules! sorts_to { - ($test_name:ident, $source:expr, $make_expected:expr) => { - #[test] - #[allow(unused_variables)] - fn $test_name() { - let buf = wast::parser::ParseBuffer::new($source).expect("should lex OK"); - - let opts = match wast::parser::parse::>(&buf) { - Ok(opts) => opts, - Err(mut e) => { - e.set_path(std::path::Path::new(stringify!($test_name))); - e.set_text($source); - eprintln!("{}", e); - panic!("should parse OK") - } - }; - - if let Err(mut e) = crate::verify(&opts) { - e.set_path(std::path::Path::new(stringify!($test_name))); - e.set_text($source); - eprintln!("{}", e); - panic!("should verify OK") - } - - let mut opts = crate::linearize(&opts); - - let before = opts - .optimizations - .iter() - .map(|o| { - o.matches - .iter() - .map(|i| format!("{:?} == {:?}", i.operation, i.expected)) - .collect::>() - }) - .collect::>(); - eprintln!("before = {:#?}", before); - - sort_least_to_most_general(&mut opts); - - let after = opts - .optimizations - .iter() - .map(|o| { - o.matches - .iter() - .map(|i| format!("{:?} == {:?}", i.operation, i.expected)) - .collect::>() - }) - .collect::>(); - eprintln!("after = {:#?}", before); - - let linear::Optimizations { - mut integers, - optimizations, - } = opts; - - let actual: Vec> = optimizations - .iter() - .map(|o| { - o.matches - .iter() - .map(|i| (i.operation, i.expected)) - .collect() - }) - .collect(); - - let mut i = |i: u64| Ok(integers.intern(i).into()); - let expected = $make_expected(&mut i); - - assert_eq!(expected, actual); - } - }; - } - - macro_rules! match_in_same_order { - ($test_name:ident, $source:expr, $make_expected:expr) => { - #[test] - #[allow(unused_variables)] - fn $test_name() { - let buf = wast::parser::ParseBuffer::new($source).expect("should lex OK"); - - let opts = match wast::parser::parse::>(&buf) { - Ok(opts) => opts, - Err(mut e) => { - e.set_path(std::path::Path::new(stringify!($test_name))); - e.set_text($source); - eprintln!("{}", e); - panic!("should parse OK") - } - }; - - if let Err(mut e) = crate::verify(&opts) { - e.set_path(std::path::Path::new(stringify!($test_name))); - e.set_text($source); - eprintln!("{}", e); - panic!("should verify OK") - } - - let mut opts = crate::linearize(&opts); - sort_least_to_most_general(&mut opts); - - let before = opts - .optimizations - .iter() - .map(|o| { - o.matches - .iter() - .map(|i| format!("{:?} == {:?}", i.operation, i.expected)) - .collect::>() - }) - .collect::>(); - eprintln!("before = {:#?}", before); - - match_in_same_order(&mut opts); - - let after = opts - .optimizations - .iter() - .map(|o| { - o.matches - .iter() - .map(|i| format!("{:?} == {:?}", i.operation, i.expected)) - .collect::>() - }) - .collect::>(); - eprintln!("after = {:#?}", before); - - let linear::Optimizations { - mut integers, - optimizations, - } = opts; - - let actual: Vec> = optimizations - .iter() - .map(|o| { - o.matches - .iter() - .map(|i| (i.operation, i.expected)) - .collect() - }) - .collect(); - - let mut i = |i: u64| Ok(integers.intern(i).into()); - let expected = $make_expected(&mut i); - - assert_eq!(expected, actual); - } - }; - } - - sorts_to!( - test_sort_least_to_most_general, - " -(=> $x 0) -(=> (iadd $x $y) 0) -(=> (iadd $x $x) 0) -(=> (iadd $x $C) 0) -(=> (when (iadd $x $C) (is-power-of-two $C)) 0) -(=> (when (iadd $x $C) (bit-width $x 32)) 0) -(=> (iadd $x 42) 0) -(=> (iadd $x (iadd $y $z)) 0) -", - |i: &mut dyn FnMut(u64) -> MatchResult| vec![ - vec![ - (Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())), - (Nop, Err(Else)), - (Opcode(LhsId(2)), Ok(TestOperator::Iadd.into())), - (Nop, Err(Else)), - (Nop, Err(Else)), - ], - vec![ - (Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())), - (Nop, Err(Else)), - (IntegerValue(LhsId(2)), i(42)) - ], - vec![ - (Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())), - (Nop, Err(Else)), - (IsConst(LhsId(2)), bool_to_match_result(true)), - (IsPowerOfTwo(LhsId(2)), bool_to_match_result(true)) - ], - vec![ - (Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())), - (Nop, Err(Else)), - (IsConst(LhsId(2)), bool_to_match_result(true)), - (BitWidth(LhsId(1)), Ok(NonZeroU32::new(32).unwrap())) - ], - vec![ - (Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())), - (Nop, Err(Else)), - (IsConst(LhsId(2)), bool_to_match_result(true)) - ], - vec![ - (Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())), - (Nop, Err(Else)), - (Eq(LhsId(2), LhsId(1)), bool_to_match_result(true)) - ], - vec![ - (Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())), - (Nop, Err(Else)), - (Nop, Err(Else)), - ], - vec![(Nop, Err(Else))] - ] - ); - - sorts_to!( - expected_edges_are_sorted, - " -(=> (iadd 0 $x) $x) -(=> (iadd $x 0) $x) -(=> (imul 1 $x) $x) -(=> (imul $x 1) $x) -(=> (imul 2 $x) (ishl $x 1)) -(=> (imul $x 2) (ishl $x 1)) -", - |i: &mut dyn FnMut(u64) -> MatchResult| vec![ - vec![ - (Opcode(LhsId(0)), Ok(TestOperator::Imul.into())), - (IntegerValue(LhsId(1)), i(2)), - (Nop, Err(Else)) - ], - vec![ - (Opcode(LhsId(0)), Ok(TestOperator::Imul.into())), - (IntegerValue(LhsId(1)), i(1)), - (Nop, Err(Else)) - ], - vec![ - (Opcode(LhsId(0)), Ok(TestOperator::Imul.into())), - (Nop, Err(Else)), - (IntegerValue(LhsId(2)), i(2)) - ], - vec![ - (Opcode(LhsId(0)), Ok(TestOperator::Imul.into())), - (Nop, Err(Else)), - (IntegerValue(LhsId(2)), i(1)) - ], - vec![ - (Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())), - (IntegerValue(LhsId(1)), i(0)), - (Nop, Err(Else)) - ], - vec![ - (Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())), - (Nop, Err(Else)), - (IntegerValue(LhsId(2)), i(0)) - ] - ] - ); - - sorts_to!( - sort_redundant_bor, - " - (=> (bor (bor $x $y) $x) - (bor $x $y)) - - (=> (bor (bor $x $y) $y) - (bor $x $y)) - ", - |i: &mut dyn FnMut(u64) -> MatchResult| vec![ - vec![ - (Opcode(LhsId(0)), Ok(TestOperator::Bor.into())), - (Opcode(LhsId(1)), Ok(TestOperator::Bor.into())), - (Nop, Err(Else)), - (Eq(LhsId(3), LhsId(2)), bool_to_match_result(true)), - (Nop, Err(Else)), - ], - vec![ - (Opcode(LhsId(0)), Ok(TestOperator::Bor.into())), - (Opcode(LhsId(1)), Ok(TestOperator::Bor.into())), - (Nop, Err(Else)), - (Nop, Err(Else)), - (Eq(LhsId(4), LhsId(2)), bool_to_match_result(true)), - ], - ] - ); - - match_in_same_order!( - match_in_same_order_redundant_bor, - " - (=> (bor (bor $x $y) $x) - (bor $x $y)) - - (=> (bor (bor $x $y) $y) - (bor $x $y)) - ", - |i: &mut dyn FnMut(u64) -> MatchResult| vec![ - vec![ - (Opcode(LhsId(0)), Ok(TestOperator::Bor.into())), - (Opcode(LhsId(1)), Ok(TestOperator::Bor.into())), - (Nop, Err(Else)), - (Eq(LhsId(3), LhsId(2)), bool_to_match_result(true)), - (Nop, Err(Else)), - ], - vec![ - (Opcode(LhsId(0)), Ok(TestOperator::Bor.into())), - (Opcode(LhsId(1)), Ok(TestOperator::Bor.into())), - (Nop, Err(Else)), - (Eq(LhsId(3), LhsId(2)), Err(Else)), - (Nop, Err(Else)), - (Eq(LhsId(4), LhsId(2)), bool_to_match_result(true)), - ], - ] - ); -} diff --git a/cranelift/peepmatic/src/linearize.rs b/cranelift/peepmatic/src/linearize.rs deleted file mode 100644 index 6d205a7c42..0000000000 --- a/cranelift/peepmatic/src/linearize.rs +++ /dev/null @@ -1,807 +0,0 @@ -//! Convert an AST into its linear equivalent. -//! -//! Convert each optimization's left-hand side into a linear series of match -//! operations. This makes it easy to create an automaton, because automatas -//! typically deal with a linear sequence of inputs. The optimization's -//! right-hand side is built incrementally inside actions that are taken on -//! transitions between match operations. -//! -//! See `crates/runtime/src/linear.rs` for the linear datatype definitions. -//! -//! ## Example -//! -//! As an example, if we linearize this optimization: -//! -//! ```lisp -//! (=> (when (imul $x $C) -//! (is-power-of-two $C)) -//! (ishl $x $(log2 C))) -//! ``` -//! -//! Then the left-hand side becomes the following linear chain of "matches": -//! -//! ```ignore -//! [ -//! // ( Match Operation, Expected Value ) -//! ( Opcode@0, imul ), -//! ( IsConst(C), true ), -//! ( IsPowerOfTwo(C), true ), -//! ] -//! ``` -//! -//! And the right-hand side becomes this linear chain of "actions": -//! -//! ```ignore -//! [ -//! $rhs0 = get lhs @ 0.0 // $x -//! $rhs1 = get lhs @ 0.1 // $C -//! $rhs2 = eval log2 $rhs1 -//! $rhs3 = make ishl $rhs0, $rhs2 -//! ] -//! ``` -//! -//! Each match will essentially become a state and a transition out of that -//! state in the final automata. The actions record the scope of matches from -//! the left-hand side and also incrementally build the right-hand side's -//! instructions. -//! -//! ## General Principles -//! -//! Here are the general principles that linearization should adhere to: -//! -//! * Don't match on a subtree until we know it exists. That is, match on -//! parents before matching on children. -//! -//! * Shorter match chains are better! This means fewer tests when matching -//! left-hand sides, and a more-compact, more-cache-friendly automata, and -//! ultimately, a faster automata. -//! -//! * An match operation should be a switch rather than a predicate that returns -//! a boolean. For example, we switch on an instruction's opcode, rather than -//! ask whether this operation is an `imul`. This allows for more prefix -//! sharing in the automata, which (again) makes it more compact and more -//! cache friendly. -//! -//! ## Implementation Overview -//! -//! We emit match operations for a left-hand side's pattern structure, followed -//! by match operations for its preconditions on that structure. This ensures -//! that anything bound in the pattern is defined before it is used in -//! precondition. -//! -//! Within matching the pattern structure, we emit matching operations in a -//! breadth-first traversal of the pattern. This ensures that we've already -//! matched an operation before we consider its operands, and therefore we -//! already know the operands exist. It also lets us fuse "what opcode does this -//! instruction have?" and "define temporary variables for this instruction's -//! operands" into a single operation. See `PatternBfs` for details. -//! -//! As we define the match operations for a pattern, we remember the path where -//! each LHS id first occurred. These will later be reused when building the RHS -//! actions. See `LhsCanonicalizer` for details. -//! -//! After we've generated the match operations and expected result of those -//! match operations, then we generate the right-hand side actions. The -//! right-hand side is built up a post-order traversal, so that operands are -//! defined before they are used. See `RhsPostOrder` and `RhsBuilder` for -//! details. -//! -//! Finally, see `linearize_optimization` for the the main AST optimization into -//! linear optimization translation function. - -use crate::ast::*; -use crate::traversals::{Bfs, Dfs}; -use peepmatic_runtime::{integer_interner::IntegerInterner, linear}; -use std::collections::BTreeMap; -use std::convert::TryInto; -use std::fmt::Debug; -use std::hash::Hash; -use std::marker::PhantomData; -use std::num::NonZeroU32; -use wast::Id; - -/// Translate the given AST optimizations into linear optimizations. -pub fn linearize(opts: &Optimizations) -> linear::Optimizations -where - TOperator: Copy + Debug + Eq + Hash + Into, -{ - let mut optimizations = vec![]; - let mut integers = IntegerInterner::new(); - for opt in &opts.optimizations { - let lin_opt = linearize_optimization(&mut integers, opt); - optimizations.push(lin_opt); - } - linear::Optimizations { - optimizations, - integers, - } -} - -/// Translate an AST optimization into a linear optimization! -fn linearize_optimization( - integers: &mut IntegerInterner, - opt: &Optimization, -) -> linear::Optimization -where - TOperator: Copy + Debug + Eq + Hash + Into, -{ - let mut matches: Vec = vec![]; - - let mut lhs_canonicalizer = LhsCanonicalizer::new(); - - // We do a breadth-first traversal of the LHS because we don't know whether - // a child actually exists to match on until we've matched its parent, and - // we don't want to emit matching operations on things that might not exist! - for (id, pattern) in PatternBfs::new(&opt.lhs.pattern) { - // Create the matching parts of an `Match` for this part of the - // pattern. - let (operation, expected) = pattern.to_linear_match_op(integers, &lhs_canonicalizer, id); - matches.push(linear::Match { - operation, - expected, - }); - - lhs_canonicalizer.remember_linear_id(pattern, id); - - // Some operations require type ascriptions for us to infer the correct - // bit width of their results: `ireduce`, `sextend`, `uextend`, etc. - // When there is such a type ascription in the pattern, insert another - // match that checks the instruction-being-matched's bit width. - if let Pattern::Operation(Operation { r#type, .. }) = pattern { - if let Some(w) = r#type.get().and_then(|ty| ty.bit_width.fixed_width()) { - debug_assert!(w != 0, "All fixed-width bit widths are non-zero"); - let expected = Ok(unsafe { NonZeroU32::new_unchecked(w as u32) }); - - matches.push(linear::Match { - operation: linear::MatchOp::BitWidth(id), - expected, - }); - } - } - } - - // Now that we've added all the matches for the LHS pattern, add the - // matches for its preconditions. - for pre in &opt.lhs.preconditions { - matches.push(pre.to_linear_match(&lhs_canonicalizer)); - } - - assert!(!matches.is_empty()); - - // Finally, generate the RHS-building actions and attach them to the first match. - let mut rhs_builder = RhsBuilder::new(&opt.rhs); - let mut actions = vec![]; - rhs_builder.add_rhs_build_actions(integers, &lhs_canonicalizer, &mut actions); - - linear::Optimization { matches, actions } -} - -/// A post-order, depth-first traversal of right-hand sides. -/// -/// Does not maintain any extra state about the traversal, such as where in the -/// tree each yielded node comes from. -struct RhsPostOrder<'a, TOperator> { - dfs: Dfs<'a, TOperator>, -} - -impl<'a, TOperator> RhsPostOrder<'a, TOperator> -where - TOperator: Copy + Debug + Eq + Hash, -{ - fn new(rhs: &'a Rhs<'a, TOperator>) -> Self { - Self { dfs: Dfs::new(rhs) } - } -} - -impl<'a, TOperator> Iterator for RhsPostOrder<'a, TOperator> -where - TOperator: Copy, -{ - type Item = &'a Rhs<'a, TOperator>; - - fn next(&mut self) -> Option<&'a Rhs<'a, TOperator>> { - use crate::traversals::TraversalEvent as TE; - loop { - match self.dfs.next()? { - (TE::Exit, DynAstRef::Rhs(rhs)) => return Some(rhs), - _ => continue, - } - } - } -} - -/// A breadth-first traversal of left-hand side patterns. -/// -/// Keeps track of the `LhsId` of each pattern, and yields it along side the -/// pattern AST node. -/// -/// We use a breadth-first traversal because we fuse "which opcode is this?" and -/// "assign operands to temporaries" into a single linear match operation. A -/// breadth-first traversal aligns with "match this opcode, and on success bind -/// all of its operands to temporaries". Fusing these operations into one is -/// important for attaining similar performance as an open-coded Rust `match` -/// expression, which would also fuse these operations via pattern matching. -struct PatternBfs<'a, TOperator> { - next_id: u16, - bfs: Bfs<'a, TOperator>, -} - -impl<'a, TOperator> PatternBfs<'a, TOperator> -where - TOperator: Copy + Debug + Eq + Hash, -{ - fn new(pattern: &'a Pattern<'a, TOperator>) -> Self { - Self { - next_id: 0, - bfs: Bfs::new(pattern), - } - } -} - -impl<'a, TOperator> Iterator for PatternBfs<'a, TOperator> -where - TOperator: 'a + Copy + Debug + Eq + Hash, -{ - type Item = (linear::LhsId, &'a Pattern<'a, TOperator>); - - fn next(&mut self) -> Option { - loop { - if let DynAstRef::Pattern(pattern) = self.bfs.next()? { - let id = linear::LhsId(self.next_id); - self.next_id = self.next_id.checked_add(1).unwrap(); - return Some((id, pattern)); - } - } - } -} - -/// A map from left-hand side identifiers to the path in the left-hand side -/// where they first occurred. -struct LhsCanonicalizer<'a, TOperator> { - id_to_linear: BTreeMap<&'a str, linear::LhsId>, - _marker: PhantomData<&'a TOperator>, -} - -impl<'a, TOperator> LhsCanonicalizer<'a, TOperator> { - /// Construct a new, empty `LhsCanonicalizer`. - fn new() -> Self { - Self { - id_to_linear: Default::default(), - _marker: PhantomData, - } - } - - /// Get the canonical `linear::LhsId` for the given variable, if any. - fn get(&self, id: &Id) -> Option { - self.id_to_linear.get(id.name()).copied() - } - - /// Remember the canonical `linear::LhsId` for any variables or constants - /// used in the given pattern. - fn remember_linear_id( - &mut self, - pattern: &'a Pattern<'a, TOperator>, - linear_id: linear::LhsId, - ) { - match pattern { - // If this is the first time we've seen an identifier defined on the - // left-hand side, remember it. - Pattern::Variable(Variable { id, .. }) | Pattern::Constant(Constant { id, .. }) => { - self.id_to_linear.entry(id.name()).or_insert(linear_id); - } - _ => {} - } - } -} - -/// An `RhsBuilder` emits the actions for building the right-hand side -/// instructions. -struct RhsBuilder<'a, TOperator> { - // We do a post order traversal of the RHS because an RHS instruction cannot - // be created until after all of its operands are created. - rhs_post_order: RhsPostOrder<'a, TOperator>, - - // A map from a right-hand side's span to its `linear::RhsId`. This is used - // by RHS-construction actions to reference operands. In practice the - // `RhsId` is roughly equivalent to its index in the post-order traversal of - // the RHS. - rhs_span_to_id: BTreeMap, -} - -impl<'a, TOperator> RhsBuilder<'a, TOperator> -where - TOperator: Copy + Debug + Eq + Hash, -{ - /// Create a new builder for the given right-hand side. - fn new(rhs: &'a Rhs<'a, TOperator>) -> Self { - let rhs_post_order = RhsPostOrder::new(rhs); - let rhs_span_to_id = Default::default(); - Self { - rhs_post_order, - rhs_span_to_id, - } - } - - /// Get the `linear::RhsId` for the given right-hand side. - /// - /// ## Panics - /// - /// Panics if we haven't already emitted the action for building this RHS's - /// instruction. - fn get_rhs_id(&self, rhs: &Rhs) -> linear::RhsId { - self.rhs_span_to_id[&rhs.span()] - } - - /// Create actions for building up this right-hand side of an optimization. - /// - /// Because we are walking the right-hand side with a post-order traversal, - /// we know that we already created an instruction's operands that are - /// defined in the right-hand side, before we get to the parent instruction. - fn add_rhs_build_actions( - &mut self, - integers: &mut IntegerInterner, - lhs_canonicalizer: &LhsCanonicalizer, - actions: &mut Vec>, - ) { - while let Some(rhs) = self.rhs_post_order.next() { - actions.push(self.rhs_to_linear_action(integers, lhs_canonicalizer, rhs)); - let id = linear::RhsId(self.rhs_span_to_id.len().try_into().unwrap()); - self.rhs_span_to_id.insert(rhs.span(), id); - } - } - - fn rhs_to_linear_action( - &self, - integers: &mut IntegerInterner, - lhs_canonicalizer: &LhsCanonicalizer, - rhs: &Rhs, - ) -> linear::Action { - match rhs { - Rhs::ValueLiteral(ValueLiteral::Integer(i)) => linear::Action::MakeIntegerConst { - value: integers.intern(i.value as u64), - bit_width: i - .bit_width - .get() - .expect("should be initialized after type checking"), - }, - Rhs::ValueLiteral(ValueLiteral::Boolean(b)) => linear::Action::MakeBooleanConst { - value: b.value, - bit_width: b - .bit_width - .get() - .expect("should be initialized after type checking"), - }, - Rhs::ValueLiteral(ValueLiteral::ConditionCode(ConditionCode { cc, .. })) => { - linear::Action::MakeConditionCode { cc: *cc } - } - Rhs::Variable(Variable { id, .. }) | Rhs::Constant(Constant { id, .. }) => { - let lhs = lhs_canonicalizer.get(id).unwrap(); - linear::Action::GetLhs { lhs } - } - Rhs::Unquote(unq) => match unq.operands.len() { - 1 => linear::Action::UnaryUnquote { - operator: unq.operator, - operand: self.get_rhs_id(&unq.operands[0]), - }, - 2 => linear::Action::BinaryUnquote { - operator: unq.operator, - operands: [ - self.get_rhs_id(&unq.operands[0]), - self.get_rhs_id(&unq.operands[1]), - ], - }, - n => unreachable!("no unquote operators of arity {}", n), - }, - Rhs::Operation(op) => match op.operands.len() { - 1 => linear::Action::MakeUnaryInst { - operator: op.operator, - r#type: op - .r#type - .get() - .expect("should be initialized after type checking"), - operand: self.get_rhs_id(&op.operands[0]), - }, - 2 => linear::Action::MakeBinaryInst { - operator: op.operator, - r#type: op - .r#type - .get() - .expect("should be initialized after type checking"), - operands: [ - self.get_rhs_id(&op.operands[0]), - self.get_rhs_id(&op.operands[1]), - ], - }, - 3 => linear::Action::MakeTernaryInst { - operator: op.operator, - r#type: op - .r#type - .get() - .expect("should be initialized after type checking"), - operands: [ - self.get_rhs_id(&op.operands[0]), - self.get_rhs_id(&op.operands[1]), - self.get_rhs_id(&op.operands[2]), - ], - }, - n => unreachable!("no instructions of arity {}", n), - }, - } - } -} - -impl Precondition<'_, TOperator> -where - TOperator: Copy + Debug + Eq + Hash + Into, -{ - /// Convert this precondition into a `linear::Match`. - fn to_linear_match(&self, lhs_canonicalizer: &LhsCanonicalizer) -> linear::Match { - match self.constraint { - Constraint::IsPowerOfTwo => { - let id = match &self.operands[0] { - ConstraintOperand::Constant(Constant { id, .. }) => id, - _ => unreachable!("checked in verification"), - }; - let id = lhs_canonicalizer.get(&id).unwrap(); - linear::Match { - operation: linear::MatchOp::IsPowerOfTwo(id), - expected: linear::bool_to_match_result(true), - } - } - Constraint::BitWidth => { - let id = match &self.operands[0] { - ConstraintOperand::Constant(Constant { id, .. }) - | ConstraintOperand::Variable(Variable { id, .. }) => id, - _ => unreachable!("checked in verification"), - }; - let id = lhs_canonicalizer.get(&id).unwrap(); - - let width = match &self.operands[1] { - ConstraintOperand::ValueLiteral(ValueLiteral::Integer(Integer { - value, - .. - })) => *value, - _ => unreachable!("checked in verification"), - }; - - assert!(0 < width && width <= 128); - assert!((width as u8).is_power_of_two()); - let expected = Ok(unsafe { NonZeroU32::new_unchecked(width as u32) }); - - linear::Match { - operation: linear::MatchOp::BitWidth(id), - expected, - } - } - Constraint::FitsInNativeWord => { - let id = match &self.operands[0] { - ConstraintOperand::Constant(Constant { id, .. }) - | ConstraintOperand::Variable(Variable { id, .. }) => id, - _ => unreachable!("checked in verification"), - }; - let id = lhs_canonicalizer.get(&id).unwrap(); - linear::Match { - operation: linear::MatchOp::FitsInNativeWord(id), - expected: linear::bool_to_match_result(true), - } - } - } - } -} - -impl Pattern<'_, TOperator> -where - TOperator: Copy, -{ - /// Convert this pattern into its linear match operation and the expected - /// result of that operation. - /// - /// NB: these mappings to expected values need to stay sync'd with the - /// runtime! - fn to_linear_match_op( - &self, - integers: &mut IntegerInterner, - lhs_canonicalizer: &LhsCanonicalizer, - linear_lhs_id: linear::LhsId, - ) -> (linear::MatchOp, linear::MatchResult) - where - TOperator: Into, - { - match self { - Pattern::ValueLiteral(ValueLiteral::Integer(Integer { value, .. })) => ( - linear::MatchOp::IntegerValue(linear_lhs_id), - Ok(integers.intern(*value as u64).into()), - ), - Pattern::ValueLiteral(ValueLiteral::Boolean(Boolean { value, .. })) => ( - linear::MatchOp::BooleanValue(linear_lhs_id), - linear::bool_to_match_result(*value), - ), - Pattern::ValueLiteral(ValueLiteral::ConditionCode(ConditionCode { cc, .. })) => { - let cc = *cc as u32; - debug_assert!(cc != 0, "no `ConditionCode` variants are zero"); - let expected = Ok(unsafe { NonZeroU32::new_unchecked(cc) }); - (linear::MatchOp::ConditionCode(linear_lhs_id), expected) - } - Pattern::Constant(Constant { id, .. }) => { - if let Some(linear_lhs_id2) = lhs_canonicalizer.get(id) { - debug_assert!(linear_lhs_id != linear_lhs_id2); - ( - linear::MatchOp::Eq(linear_lhs_id, linear_lhs_id2), - linear::bool_to_match_result(true), - ) - } else { - ( - linear::MatchOp::IsConst(linear_lhs_id), - linear::bool_to_match_result(true), - ) - } - } - Pattern::Variable(Variable { id, .. }) => { - if let Some(linear_lhs_id2) = lhs_canonicalizer.get(id) { - debug_assert!(linear_lhs_id != linear_lhs_id2); - ( - linear::MatchOp::Eq(linear_lhs_id, linear_lhs_id2), - linear::bool_to_match_result(true), - ) - } else { - (linear::MatchOp::Nop, Err(linear::Else)) - } - } - Pattern::Operation(op) => { - let expected = Ok(op.operator.into()); - (linear::MatchOp::Opcode(linear_lhs_id), expected) - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use peepmatic_runtime::{ - integer_interner::IntegerId, - linear::{bool_to_match_result, Action::*, Else, LhsId, MatchOp::*, RhsId}, - r#type::{BitWidth, Kind, Type}, - unquote::UnquoteOperator, - }; - use peepmatic_test_operator::TestOperator; - - macro_rules! linearizes_to { - ($name:ident, $source:expr, $make_expected:expr $(,)* ) => { - #[test] - fn $name() { - let buf = wast::parser::ParseBuffer::new($source).expect("should lex OK"); - - let opts = match wast::parser::parse::>(&buf) { - Ok(opts) => opts, - Err(mut e) => { - e.set_path(std::path::Path::new(stringify!($name))); - e.set_text($source); - eprintln!("{}", e); - panic!("should parse OK") - } - }; - - assert_eq!( - opts.optimizations.len(), - 1, - "`linearizes_to!` only supports a single optimization; split the big test into \ - multiple small tests" - ); - - if let Err(mut e) = crate::verify(&opts) { - e.set_path(std::path::Path::new(stringify!($name))); - e.set_text($source); - eprintln!("{}", e); - panic!("should verify OK") - } - - let mut integers = IntegerInterner::new(); - let mut i = |i: u64| integers.intern(i); - - #[allow(unused_variables)] - let make_expected: fn( - &mut dyn FnMut(u64) -> IntegerId, - ) -> (Vec, Vec>) = $make_expected; - - let expected = make_expected(&mut i); - let actual = linearize_optimization(&mut integers, &opts.optimizations[0]); - assert_eq!(expected.0, actual.matches); - assert_eq!(expected.1, actual.actions); - } - }; - } - - linearizes_to!( - mul_by_pow2_into_shift, - " -(=> (when (imul $x $C) - (is-power-of-two $C)) - (ishl $x $(log2 $C))) - ", - |i| ( - vec![ - linear::Match { - operation: Opcode(LhsId(0)), - expected: Ok(TestOperator::Imul.into()), - }, - linear::Match { - operation: Nop, - expected: Err(Else), - }, - linear::Match { - operation: IsConst(LhsId(2)), - expected: bool_to_match_result(true), - }, - linear::Match { - operation: IsPowerOfTwo(LhsId(2)), - expected: bool_to_match_result(true), - }, - ], - vec![ - GetLhs { lhs: LhsId(1) }, - GetLhs { lhs: LhsId(2) }, - UnaryUnquote { - operator: UnquoteOperator::Log2, - operand: RhsId(1) - }, - MakeBinaryInst { - operator: TestOperator::Ishl, - r#type: Type { - kind: Kind::Int, - bit_width: BitWidth::Polymorphic - }, - operands: [RhsId(0), RhsId(2)] - } - ], - ), - ); - - linearizes_to!(variable_pattern_id_optimization, "(=> $x $x)", |i| ( - vec![linear::Match { - operation: Nop, - expected: Err(Else), - }], - vec![GetLhs { lhs: LhsId(0) }], - )); - - linearizes_to!(constant_pattern_id_optimization, "(=> $C $C)", |i| ( - vec![linear::Match { - operation: IsConst(LhsId(0)), - expected: bool_to_match_result(true), - }], - vec![GetLhs { lhs: LhsId(0) }], - )); - - linearizes_to!(boolean_literal_id_optimization, "(=> true true)", |i| ( - vec![linear::Match { - operation: BooleanValue(LhsId(0)), - expected: bool_to_match_result(true), - }], - vec![MakeBooleanConst { - value: true, - bit_width: BitWidth::Polymorphic, - }], - )); - - linearizes_to!(number_literal_id_optimization, "(=> 5 5)", |i| ( - vec![linear::Match { - operation: IntegerValue(LhsId(0)), - expected: Ok(i(5).into()), - }], - vec![MakeIntegerConst { - value: i(5), - bit_width: BitWidth::Polymorphic, - }], - )); - - linearizes_to!( - operation_id_optimization, - "(=> (iconst $C) (iconst $C))", - |i| ( - vec![ - linear::Match { - operation: Opcode(LhsId(0)), - expected: Ok(TestOperator::Iconst.into()), - }, - linear::Match { - operation: IsConst(LhsId(1)), - expected: bool_to_match_result(true), - }, - ], - vec![ - GetLhs { lhs: LhsId(1) }, - MakeUnaryInst { - operator: TestOperator::Iconst, - r#type: Type { - kind: Kind::Int, - bit_width: BitWidth::Polymorphic, - }, - operand: RhsId(0), - }, - ], - ), - ); - - linearizes_to!( - redundant_bor, - "(=> (bor $x (bor $x $y)) (bor $x $y))", - |i| ( - vec![ - linear::Match { - operation: Opcode(LhsId(0)), - expected: Ok(TestOperator::Bor.into()), - }, - linear::Match { - operation: Nop, - expected: Err(Else), - }, - linear::Match { - operation: Opcode(LhsId(2)), - expected: Ok(TestOperator::Bor.into()), - }, - linear::Match { - operation: Eq(LhsId(3), LhsId(1)), - expected: bool_to_match_result(true), - }, - linear::Match { - operation: Nop, - expected: Err(Else), - }, - ], - vec![ - GetLhs { lhs: LhsId(1) }, - GetLhs { lhs: LhsId(4) }, - MakeBinaryInst { - operator: TestOperator::Bor, - r#type: Type { - kind: Kind::Int, - bit_width: BitWidth::Polymorphic, - }, - operands: [RhsId(0), RhsId(1)], - }, - ], - ), - ); - - linearizes_to!( - large_integers, - // u64::MAX - "(=> 18446744073709551615 0)", - |i| ( - vec![linear::Match { - operation: IntegerValue(LhsId(0)), - expected: Ok(i(std::u64::MAX).into()), - }], - vec![MakeIntegerConst { - value: i(0), - bit_width: BitWidth::Polymorphic, - }], - ), - ); - - linearizes_to!( - ireduce_with_type_ascription, - "(=> (ireduce{i32} $x) 0)", - |i| ( - vec![ - linear::Match { - operation: Opcode(LhsId(0)), - expected: Ok(TestOperator::Ireduce.into()), - }, - linear::Match { - operation: linear::MatchOp::BitWidth(LhsId(0)), - expected: Ok(NonZeroU32::new(32).unwrap()), - }, - linear::Match { - operation: Nop, - expected: Err(Else), - }, - ], - vec![MakeIntegerConst { - value: i(0), - bit_width: BitWidth::ThirtyTwo, - }], - ), - ); -} diff --git a/cranelift/peepmatic/src/parser.rs b/cranelift/peepmatic/src/parser.rs deleted file mode 100644 index fae87f8974..0000000000 --- a/cranelift/peepmatic/src/parser.rs +++ /dev/null @@ -1,981 +0,0 @@ -/*! - -This module implements parsing the DSL text format. It implements the -`wast::Parse` trait for all of our AST types. - -The grammar for the DSL is given below: - -```ebnf - ::= * - - ::= '(' '=>' ')' - - ::= - | '(' 'when' * ')' - - ::= - | - | > - | - - ::= - | - - ::= 'true' | 'false' - -> ::= '(' [] * ')' - - ::= '(' * ')' - - ::= - | - | - - ::= - | - | - | - | > - - ::= '$' '(' * ')' - - ::= - | -``` - - */ - -use crate::ast::*; -use peepmatic_runtime::r#type::Type; -use std::cell::Cell; -use std::marker::PhantomData; -use wast::{ - parser::{Cursor, Parse, Parser, Peek, Result as ParseResult}, - Id, LParen, -}; - -mod tok { - use wast::{custom_keyword, custom_reserved}; - - custom_keyword!(bit_width = "bit-width"); - custom_reserved!(dollar = "$"); - custom_keyword!(r#false = "false"); - custom_keyword!(fits_in_native_word = "fits-in-native-word"); - custom_keyword!(is_power_of_two = "is-power-of-two"); - custom_reserved!(left_curly = "{"); - custom_keyword!(log2); - custom_keyword!(neg); - custom_reserved!(replace = "=>"); - custom_reserved!(right_curly = "}"); - custom_keyword!(r#true = "true"); - custom_keyword!(when); - - custom_keyword!(eq); - custom_keyword!(ne); - custom_keyword!(slt); - custom_keyword!(ult); - custom_keyword!(sge); - custom_keyword!(uge); - custom_keyword!(sgt); - custom_keyword!(ugt); - custom_keyword!(sle); - custom_keyword!(ule); - custom_keyword!(of); - custom_keyword!(nof); -} - -impl<'a, TOperator> Parse<'a> for Optimizations<'a, TOperator> -where - TOperator: Parse<'a>, -{ - fn parse(p: Parser<'a>) -> ParseResult { - let span = p.cur_span(); - let mut optimizations = vec![]; - while !p.is_empty() { - optimizations.push(p.parse()?); - } - Ok(Optimizations { - span, - optimizations, - }) - } -} - -impl<'a, TOperator> Parse<'a> for Optimization<'a, TOperator> -where - TOperator: Parse<'a>, -{ - fn parse(p: Parser<'a>) -> ParseResult { - let span = p.cur_span(); - p.parens(|p| { - p.parse::()?; - let lhs = p.parse()?; - let rhs = p.parse()?; - Ok(Optimization { span, lhs, rhs }) - }) - } -} - -impl<'a, TOperator> Parse<'a> for Lhs<'a, TOperator> -where - TOperator: Parse<'a>, -{ - fn parse(p: Parser<'a>) -> ParseResult { - let span = p.cur_span(); - let mut preconditions = vec![]; - if p.peek::() && p.peek2::() { - p.parens(|p| { - p.parse::()?; - let pattern = p.parse()?; - while p.peek::() { - preconditions.push(p.parse()?); - } - Ok(Lhs { - span, - pattern, - preconditions, - }) - }) - } else { - let span = p.cur_span(); - let pattern = p.parse()?; - Ok(Lhs { - span, - pattern, - preconditions, - }) - } - } -} - -impl<'a, TOperator> Parse<'a> for Pattern<'a, TOperator> -where - TOperator: Parse<'a>, -{ - fn parse(p: Parser<'a>) -> ParseResult { - if p.peek::>() { - return Ok(Pattern::ValueLiteral(p.parse()?)); - } - if p.peek::>() { - return Ok(Pattern::Constant(p.parse()?)); - } - if p.peek::>() { - return Ok(Pattern::Operation(p.parse()?)); - } - if p.peek::>() { - return Ok(Pattern::Variable(p.parse()?)); - } - Err(p.error("expected a left-hand side pattern")) - } -} - -impl<'a, TOperator> Peek for Pattern<'a, TOperator> -where - TOperator: 'a, -{ - fn peek(c: Cursor) -> bool { - ValueLiteral::::peek(c) - || Constant::::peek(c) - || Variable::::peek(c) - || Operation::::peek(c) - } - - fn display() -> &'static str { - "left-hand side pattern" - } -} - -impl<'a, TOperator> Parse<'a> for ValueLiteral<'a, TOperator> { - fn parse(p: Parser<'a>) -> ParseResult { - if let Ok(b) = p.parse::>() { - return Ok(ValueLiteral::Boolean(b)); - } - if let Ok(i) = p.parse::>() { - return Ok(ValueLiteral::Integer(i)); - } - if let Ok(cc) = p.parse::>() { - return Ok(ValueLiteral::ConditionCode(cc)); - } - Err(p.error("expected an integer or boolean or condition code literal")) - } -} - -impl<'a, TOperator> Peek for ValueLiteral<'a, TOperator> { - fn peek(c: Cursor) -> bool { - c.integer().is_some() - || Boolean::::peek(c) - || ConditionCode::::peek(c) - } - - fn display() -> &'static str { - "value literal" - } -} - -impl<'a, TOperator> Parse<'a> for Integer<'a, TOperator> { - fn parse(p: Parser<'a>) -> ParseResult { - let span = p.cur_span(); - p.step(|c| { - if let Some((i, rest)) = c.integer() { - let (s, base) = i.val(); - let val = i64::from_str_radix(s, base) - .or_else(|_| u128::from_str_radix(s, base).map(|i| i as i64)); - return match val { - Ok(value) => Ok(( - Integer { - span, - value, - bit_width: Default::default(), - marker: PhantomData, - }, - rest, - )), - Err(_) => Err(c.error("invalid integer: out of range")), - }; - } - Err(c.error("expected an integer")) - }) - } -} - -impl<'a, TOperator> Parse<'a> for Boolean<'a, TOperator> { - fn parse(p: Parser<'a>) -> ParseResult { - let span = p.cur_span(); - if p.parse::().is_ok() { - return Ok(Boolean { - span, - value: true, - bit_width: Default::default(), - marker: PhantomData, - }); - } - if p.parse::().is_ok() { - return Ok(Boolean { - span, - value: false, - bit_width: Default::default(), - marker: PhantomData, - }); - } - Err(p.error("expected `true` or `false`")) - } -} - -impl<'a, TOperator> Peek for Boolean<'a, TOperator> { - fn peek(c: Cursor) -> bool { - ::peek(c) || ::peek(c) - } - - fn display() -> &'static str { - "boolean `true` or `false`" - } -} - -impl<'a, TOperator> Parse<'a> for ConditionCode<'a, TOperator> { - fn parse(p: Parser<'a>) -> ParseResult { - let span = p.cur_span(); - - macro_rules! parse_cc { - ( $( $token:ident => $cc:ident, )* ) => { - $( - if p.peek::() { - p.parse::()?; - return Ok(Self { - span, - cc: peepmatic_runtime::cc::ConditionCode::$cc, - marker: PhantomData, - }); - } - )* - } - } - - parse_cc! { - eq => Eq, - ne => Ne, - slt => Slt, - ult => Ult, - sge => Sge, - uge => Uge, - sgt => Sgt, - ugt => Ugt, - sle => Sle, - ule => Ule, - of => Of, - nof => Nof, - } - - Err(p.error("expected a condition code")) - } -} - -impl<'a, TOperator> Peek for ConditionCode<'a, TOperator> { - fn peek(c: Cursor) -> bool { - macro_rules! peek_cc { - ( $( $token:ident, )* ) => { - false $( || ::peek(c) )* - } - } - - peek_cc! { - eq, - ne, - slt, - ult, - sge, - uge, - sgt, - ugt, - sle, - ule, - of, - nof, - } - } - - fn display() -> &'static str { - "condition code" - } -} - -impl<'a, TOperator> Parse<'a> for Constant<'a, TOperator> { - fn parse(p: Parser<'a>) -> ParseResult { - let span = p.cur_span(); - let id = Id::parse(p)?; - if id - .name() - .chars() - .all(|c| !c.is_alphabetic() || c.is_uppercase()) - { - Ok(Constant { - span, - id, - marker: PhantomData, - }) - } else { - let upper = id - .name() - .chars() - .flat_map(|c| c.to_uppercase()) - .collect::(); - Err(p.error(format!( - "symbolic constants must start with an upper-case letter like ${}", - upper - ))) - } - } -} - -impl<'a, TOperator> Peek for Constant<'a, TOperator> { - fn peek(c: Cursor) -> bool { - if let Some((id, _rest)) = c.id() { - id.chars().all(|c| !c.is_alphabetic() || c.is_uppercase()) - } else { - false - } - } - - fn display() -> &'static str { - "symbolic constant" - } -} - -impl<'a, TOperator> Parse<'a> for Variable<'a, TOperator> { - fn parse(p: Parser<'a>) -> ParseResult { - let span = p.cur_span(); - let id = Id::parse(p)?; - if id - .name() - .chars() - .all(|c| !c.is_alphabetic() || c.is_lowercase()) - { - Ok(Variable { - span, - id, - marker: PhantomData, - }) - } else { - let lower = id - .name() - .chars() - .flat_map(|c| c.to_lowercase()) - .collect::(); - Err(p.error(format!( - "variables must start with an lower-case letter like ${}", - lower - ))) - } - } -} - -impl<'a, TOperator> Peek for Variable<'a, TOperator> { - fn peek(c: Cursor) -> bool { - if let Some((id, _rest)) = c.id() { - id.chars().all(|c| !c.is_alphabetic() || c.is_lowercase()) - } else { - false - } - } - - fn display() -> &'static str { - "variable" - } -} - -impl<'a, TOperator, TOperand> Parse<'a> for Operation<'a, TOperator, TOperand> -where - TOperator: Parse<'a>, - TOperand: 'a + Ast<'a, TOperator> + Peek + Parse<'a>, - DynAstRef<'a, TOperator>: From<&'a TOperand>, -{ - fn parse(p: Parser<'a>) -> ParseResult { - // Don't blow the stack with this recursive parser. We don't expect - // nesting to ever get very deep, so it isn't worth refactoring this - // code to be non-recursive. - if p.parens_depth() > 25 { - return Err(p.error("module nesting too deep")); - } - - let span = p.cur_span(); - p.parens(|p| { - let operator = p.parse()?; - - let r#type = Cell::new(if p.peek::() { - p.parse::()?; - let ty = p.parse::()?; - p.parse::()?; - Some(ty) - } else { - None - }); - - let mut operands = vec![]; - while p.peek::() { - operands.push(p.parse()?); - } - Ok(Operation { - span, - operator, - r#type, - operands, - marker: PhantomData, - }) - }) - } -} - -impl<'a, TOperator, TOperand> Peek for Operation<'a, TOperator, TOperand> -where - TOperand: 'a + Ast<'a, TOperator>, - DynAstRef<'a, TOperator>: From<&'a TOperand>, -{ - fn peek(c: Cursor) -> bool { - wast::LParen::peek(c) - } - - fn display() -> &'static str { - "operation" - } -} - -impl<'a, TOperator> Parse<'a> for Precondition<'a, TOperator> { - fn parse(p: Parser<'a>) -> ParseResult { - let span = p.cur_span(); - p.parens(|p| { - let constraint = p.parse()?; - let mut operands = vec![]; - while p.peek::>() { - operands.push(p.parse()?); - } - Ok(Precondition { - span, - constraint, - operands, - marker: PhantomData, - }) - }) - } -} - -impl<'a> Parse<'a> for Constraint { - fn parse(p: Parser<'a>) -> ParseResult { - if p.peek::() { - p.parse::()?; - return Ok(Constraint::IsPowerOfTwo); - } - if p.peek::() { - p.parse::()?; - return Ok(Constraint::BitWidth); - } - if p.peek::() { - p.parse::()?; - return Ok(Constraint::FitsInNativeWord); - } - Err(p.error("expected a precondition constraint")) - } -} - -impl<'a, TOperator> Parse<'a> for ConstraintOperand<'a, TOperator> { - fn parse(p: Parser<'a>) -> ParseResult { - if p.peek::>() { - return Ok(ConstraintOperand::ValueLiteral(p.parse()?)); - } - if p.peek::>() { - return Ok(ConstraintOperand::Constant(p.parse()?)); - } - if p.peek::>() { - return Ok(ConstraintOperand::Variable(p.parse()?)); - } - Err(p.error("expected an operand for precondition constraint")) - } -} - -impl<'a, TOperator> Peek for ConstraintOperand<'a, TOperator> { - fn peek(c: Cursor) -> bool { - ValueLiteral::::peek(c) - || Constant::::peek(c) - || Variable::::peek(c) - } - - fn display() -> &'static str { - "operand for a precondition constraint" - } -} - -impl<'a, TOperator> Parse<'a> for Rhs<'a, TOperator> -where - TOperator: Parse<'a>, -{ - fn parse(p: Parser<'a>) -> ParseResult { - if p.peek::>() { - return Ok(Rhs::ValueLiteral(p.parse()?)); - } - if p.peek::>() { - return Ok(Rhs::Constant(p.parse()?)); - } - if p.peek::>() { - return Ok(Rhs::Variable(p.parse()?)); - } - if p.peek::>() { - return Ok(Rhs::Unquote(p.parse()?)); - } - if p.peek::>() { - return Ok(Rhs::Operation(p.parse()?)); - } - Err(p.error("expected a right-hand side replacement")) - } -} - -impl<'a, TOperator> Peek for Rhs<'a, TOperator> -where - TOperator: 'a, -{ - fn peek(c: Cursor) -> bool { - ValueLiteral::::peek(c) - || Constant::::peek(c) - || Variable::::peek(c) - || Unquote::::peek(c) - || Operation::::peek(c) - } - - fn display() -> &'static str { - "right-hand side replacement" - } -} - -impl<'a, TOperator> Parse<'a> for Unquote<'a, TOperator> -where - TOperator: Parse<'a>, -{ - fn parse(p: Parser<'a>) -> ParseResult { - let span = p.cur_span(); - p.parse::()?; - p.parens(|p| { - let operator = p.parse()?; - let mut operands = vec![]; - while p.peek::>() { - operands.push(p.parse()?); - } - Ok(Unquote { - span, - operator, - operands, - marker: PhantomData, - }) - }) - } -} - -impl<'a, TOperator> Peek for Unquote<'a, TOperator> { - fn peek(c: Cursor) -> bool { - tok::dollar::peek(c) - } - - fn display() -> &'static str { - "unquote expression" - } -} - -#[cfg(test)] -mod test { - use super::*; - use peepmatic_test_operator::TestOperator; - - macro_rules! test_parse { - ( - $( - $name:ident < $ast:ty > { - $( ok { $( $ok:expr , )* } )* - $( err { $( $err:expr , )* } )* - } - )* - ) => { - $( - #[test] - #[allow(non_snake_case)] - fn $name() { - $( - $({ - let input = $ok; - let buf = wast::parser::ParseBuffer::new(input).unwrap_or_else(|e| { - panic!("should lex OK, got error:\n\n{}\n\nInput:\n\n{}", e, input) - }); - if let Err(e) = wast::parser::parse::<$ast>(&buf) { - panic!( - "expected to parse OK, got error:\n\n{}\n\nInput:\n\n{}", - e, input - ); - } - })* - )* - - $( - $({ - let input = $err; - let buf = wast::parser::ParseBuffer::new(input).unwrap_or_else(|e| { - panic!("should lex OK, got error:\n\n{}\n\nInput:\n\n{}", e, input) - }); - if let Ok(ast) = wast::parser::parse::<$ast>(&buf) { - panic!( - "expected a parse error, got:\n\n{:?}\n\nInput:\n\n{}", - ast, input - ); - } - })* - )* - } - )* - } - } - - test_parse! { - parse_boolean> { - ok { - "true", - "false", - } - err { - "", - "t", - "tr", - "tru", - "truezzz", - "f", - "fa", - "fal", - "fals", - "falsezzz", - } - } - parse_cc> { - ok { - "eq", - "ne", - "slt", - "ult", - "sge", - "uge", - "sgt", - "ugt", - "sle", - "ule", - "of", - "nof", - } - err { - "", - "neq", - } - } - parse_constant> { - ok { - "$C", - "$C1", - "$C2", - "$X", - "$Y", - "$SOME-CONSTANT", - "$SOME_CONSTANT", - } - err { - "", - "zzz", - "$", - "$variable", - "$Some-Constant", - "$Some_Constant", - "$Some_constant", - } - } - parse_constraint { - ok { - "is-power-of-two", - "bit-width", - "fits-in-native-word", - } - err { - "", - "iadd", - "imul", - } - } - parse_constraint_operand> { - ok { - "1234", - "true", - "$CONSTANT", - "$variable", - } - err { - "", - "is-power-of-two", - "(is-power-of-two $C)", - "(iadd 1 2)", - } - } - parse_integer> { - ok { - "0", - "1", - "12", - "123", - "1234", - "12345", - "123456", - "1234567", - "12345678", - "123456789", - "1234567890", - "0x0", - "0x1", - "0x12", - "0x123", - "0x1234", - "0x12345", - "0x123456", - "0x1234567", - "0x12345678", - "0x123456789", - "0x123456789a", - "0x123456789ab", - "0x123456789abc", - "0x123456789abcd", - "0x123456789abcde", - "0x123456789abcdef", - "0xffff_ffff_ffff_ffff", - } - err { - "", - "abcdef", - "01234567890abcdef", - "0xgggg", - "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", - } - } - parse_lhs> { - ok { - "(when (imul $C1 $C2) (is-power-of-two $C1) (is-power-of-two $C2))", - "(when (imul $x $C) (is-power-of-two $C))", - "(imul $x $y)", - "(imul $x)", - "(imul)", - "$C", - "$x", - } - err { - "", - "()", - "abc", - } - } - parse_operation_pattern>> { - ok { - "(iadd)", - "(iadd 1)", - "(iadd 1 2)", - "(iadd $x $C)", - "(iadd{i32} $x $y)", - "(icmp eq $x $y)", - } - err { - "", - "()", - "$var", - "$CONST", - "(ishl $x $(log2 $C))", - - // Nesting too deep. - "(iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd))))))))))))))))))))))))))))))))))))))))))))))))))", - } - } - parse_operation_rhs>> { - ok { - "(iadd)", - "(iadd 1)", - "(iadd 1 2)", - "(ishl $x $(log2 $C))", - } - err { - "", - "()", - "$var", - "$CONST", - } - } - parse_operator { - ok { - "bor", - "iadd", - "iadd_imm", - "iconst", - "imul", - "imul_imm", - "ishl", - "sdiv", - "sdiv_imm", - "sshr", - } - err { - "", - "iadd.i32", - "iadd{i32}", - } - } - parse_optimization> { - ok { - "(=> (when (iadd $x $C) (is-power-of-two $C) (is-power-of-two $C)) (iadd $C $x))", - "(=> (when (iadd $x $C)) (iadd $C $x))", - "(=> (iadd $x $C) (iadd $C $x))", - } - err { - "", - "()", - "(=>)", - "(=> () ())", - } - } - parse_optimizations> { - ok { - "", - r#" - ;; Canonicalize `a + (b + c)` into `(a + b) + c`. - (=> (iadd $a (iadd $b $c)) - (iadd (iadd $a $b) $c)) - - ;; Combine a `const` and an `iadd` into a `iadd_imm`. - (=> (iadd (iconst $C) $x) - (iadd_imm $C $x)) - - ;; When `C` is a power of two, replace `x * C` with `x << log2(C)`. - (=> (when (imul $x $C) - (is-power-of-two $C)) - (ishl $x $(log2 $C))) - "#, - } - } - parse_pattern> { - ok { - "1234", - "$C", - "$x", - "(iadd $x $y)", - } - err { - "", - "()", - "abc", - } - } - parse_precondition> { - ok { - "(is-power-of-two)", - "(is-power-of-two $C)", - "(is-power-of-two $C1 $C2)", - } - err { - "", - "1234", - "()", - "$var", - "$CONST", - } - } - parse_rhs> { - ok { - "5", - "$C", - "$x", - "$(log2 $C)", - "(iadd $x 1)", - } - err { - "", - "()", - } - } - parse_unquote> { - ok { - "$(log2)", - "$(log2 $C)", - "$(log2 $C1 1)", - "$(neg)", - "$(neg $C)", - "$(neg $C 1)", - } - err { - "", - "(log2 $C)", - "$()", - } - } - parse_value_literal> { - ok { - "12345", - "true", - } - err { - "", - "'c'", - "\"hello\"", - "12.34", - } - } - parse_variable> { - ok { - "$v", - "$v1", - "$v2", - "$x", - "$y", - "$some-var", - "$another_var", - } - err { - "zzz", - "$", - "$CONSTANT", - "$fooBar", - } - } - } -} diff --git a/cranelift/peepmatic/src/traversals.rs b/cranelift/peepmatic/src/traversals.rs deleted file mode 100644 index 1477f6b961..0000000000 --- a/cranelift/peepmatic/src/traversals.rs +++ /dev/null @@ -1,327 +0,0 @@ -//! Traversals over the AST. - -use crate::ast::*; -use std::collections::VecDeque; -use std::fmt::Debug; -use std::hash::Hash; - -/// A low-level DFS traversal event: either entering or exiting the traversal of -/// an AST node. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum TraversalEvent { - /// Entering traversal of an AST node. - /// - /// Processing an AST node upon this event corresponds to a pre-order - /// DFS traversal. - Enter, - - /// Exiting traversal of an AST node. - /// - /// Processing an AST node upon this event corresponds to a post-order DFS - /// traversal. - Exit, -} - -/// A depth-first traversal of an AST. -/// -/// This is a fairly low-level traversal type, and is intended to be used as a -/// building block for making specific pre-order or post-order traversals for -/// whatever problem is at hand. -/// -/// This implementation is not recursive, and exposes an `Iterator` interface -/// that yields pairs of `(TraversalEvent, DynAstRef)` items. -/// -/// The traversal can walk a whole set of `Optimization`s or just a subtree of -/// the AST, because the `new` constructor takes anything that can convert into -/// a `DynAstRef`. -#[derive(Debug, Clone)] -pub struct Dfs<'a, TOperator> { - stack: Vec<(TraversalEvent, DynAstRef<'a, TOperator>)>, -} - -impl<'a, TOperator> Dfs<'a, TOperator> -where - TOperator: Copy + Debug + Eq + Hash, -{ - /// Construct a new `Dfs` traversal starting at the given `start` AST node. - pub fn new(start: impl Into>) -> Self { - let start = start.into(); - Dfs { - stack: vec![ - (TraversalEvent::Exit, start), - (TraversalEvent::Enter, start), - ], - } - } - - /// Peek at the next traversal event and AST node pair, if any. - pub fn peek(&self) -> Option<(TraversalEvent, DynAstRef<'a, TOperator>)> { - self.stack.last().copied() - } -} - -impl<'a, TOperator> Iterator for Dfs<'a, TOperator> -where - TOperator: Copy, -{ - type Item = (TraversalEvent, DynAstRef<'a, TOperator>); - - fn next(&mut self) -> Option<(TraversalEvent, DynAstRef<'a, TOperator>)> { - let (event, node) = self.stack.pop()?; - if let TraversalEvent::Enter = event { - let mut enqueue_children = EnqueueChildren(self); - node.child_nodes(&mut enqueue_children) - } - return Some((event, node)); - - struct EnqueueChildren<'a, 'b, TOperator>(&'b mut Dfs<'a, TOperator>) - where - 'a: 'b; - - impl<'a, 'b, TOperator> Extend> for EnqueueChildren<'a, 'b, TOperator> - where - 'a: 'b, - TOperator: Copy, - { - fn extend>>(&mut self, iter: T) { - let iter = iter.into_iter(); - - let (min, max) = iter.size_hint(); - self.0.stack.reserve(max.unwrap_or(min) * 2); - - let start = self.0.stack.len(); - - for node in iter { - self.0.stack.push((TraversalEvent::Enter, node)); - self.0.stack.push((TraversalEvent::Exit, node)); - } - - // Reverse to make it so that we visit children in order - // (e.g. operands are visited in order). - self.0.stack[start..].reverse(); - } - } - } -} - -/// A breadth-first traversal of an AST -/// -/// This implementation is not recursive, and exposes an `Iterator` interface -/// that yields `DynAstRef` items. -/// -/// The traversal can walk a whole set of `Optimization`s or just a subtree of -/// the AST, because the `new` constructor takes anything that can convert into -/// a `DynAstRef`. -#[derive(Clone, Debug)] -pub struct Bfs<'a, TOperator> { - queue: VecDeque>, -} - -impl<'a, TOperator> Bfs<'a, TOperator> -where - TOperator: Copy + Debug + Eq + Hash, -{ - /// Construct a new `Bfs` traversal starting at the given `start` AST node. - pub fn new(start: impl Into>) -> Self { - let mut queue = VecDeque::with_capacity(16); - queue.push_back(start.into()); - Bfs { queue } - } -} - -impl<'a, TOperator> Iterator for Bfs<'a, TOperator> -where - TOperator: Copy + Debug + Eq + Hash, -{ - type Item = DynAstRef<'a, TOperator>; - - fn next(&mut self) -> Option { - let node = self.queue.pop_front()?; - node.child_nodes(&mut self.queue); - Some(node) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use peepmatic_test_operator::TestOperator; - use DynAstRef::*; - - #[test] - fn test_dfs_traversal() { - let input = " -(=> (when (imul $x $C) - (is-power-of-two $C)) - (ishl $x $(log2 $C))) -"; - let buf = wast::parser::ParseBuffer::new(input).expect("input should lex OK"); - let ast = match wast::parser::parse::>(&buf) { - Ok(ast) => ast, - Err(e) => panic!("expected to parse OK, got error:\n\n{}", e), - }; - - let mut dfs = Dfs::new(&ast); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Enter, Optimizations(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Enter, Optimization(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Enter, Lhs(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Enter, Pattern(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Enter, PatternOperation(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Enter, Pattern(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Enter, Variable(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Exit, Variable(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Exit, Pattern(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Enter, Pattern(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Enter, Constant(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Exit, Constant(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Exit, Pattern(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Exit, PatternOperation(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Exit, Pattern(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Enter, Precondition(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Enter, ConstraintOperand(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Enter, Constant(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Exit, Constant(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Exit, ConstraintOperand(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Exit, Precondition(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Exit, Lhs(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Enter, Rhs(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Enter, RhsOperation(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Enter, Rhs(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Enter, Variable(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Exit, Variable(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Exit, Rhs(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Enter, Rhs(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Enter, Unquote(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Enter, Rhs(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Enter, Constant(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Exit, Constant(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Exit, Rhs(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Exit, Unquote(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Exit, Rhs(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Exit, RhsOperation(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Exit, Rhs(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Exit, Optimization(..))) - )); - assert!(matches!( - dbg!(dfs.next()), - Some((TraversalEvent::Exit, Optimizations(..))) - )); - assert!(dfs.next().is_none()); - } -} diff --git a/cranelift/peepmatic/src/verify.rs b/cranelift/peepmatic/src/verify.rs deleted file mode 100644 index 26bf22f9df..0000000000 --- a/cranelift/peepmatic/src/verify.rs +++ /dev/null @@ -1,1474 +0,0 @@ -//! Verification and type checking of optimizations. -//! -//! For type checking, we compile the AST's type constraints down into Z3 -//! variables and assertions. If Z3 finds the assertions satisfiable, then we're -//! done! If it finds them unsatisfiable, we use the `get_unsat_core` method to -//! get the minimal subset of assertions that are in conflict, and report a -//! best-effort type error message with them. These messages aren't perfect, but -//! they're Good Enough when embedded in the source text via our tracking of -//! `wast::Span`s. -//! -//! Verifying that there aren't any counter-examples (inputs for which the LHS -//! and RHS produce different results) for a particular optimization is not -//! implemented yet. - -use crate::ast::{Span as _, *}; -use crate::traversals::{Dfs, TraversalEvent}; -use peepmatic_runtime::r#type::{BitWidth, Kind, Type}; -use peepmatic_traits::{TypingContext as TypingContextTrait, TypingRules}; -use std::borrow::Cow; -use std::collections::HashMap; -use std::convert::{TryFrom, TryInto}; -use std::fmt; -use std::fmt::Debug; -use std::hash::Hash; -use std::iter; -use std::mem; -use std::ops::{Deref, DerefMut}; -use std::path::Path; -use wast::{Error as WastError, Id, Span}; -use z3::ast::Ast; - -/// A verification or type checking error. -#[derive(Debug)] -pub struct VerifyError { - errors: Vec, -} - -impl fmt::Display for VerifyError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - for e in &self.errors { - writeln!(f, "{}\n", e)?; - } - Ok(()) - } -} - -impl std::error::Error for VerifyError {} - -impl From for VerifyError { - fn from(e: WastError) -> Self { - VerifyError { - errors: vec![e.into()], - } - } -} - -impl From for VerifyError { - fn from(e: anyhow::Error) -> Self { - VerifyError { errors: vec![e] } - } -} - -impl VerifyError { - /// To provide a more useful error this function can be used to extract - /// relevant textual information about this error into the error itself. - /// - /// The `contents` here should be the full text of the original file being - /// parsed, and this will extract a sub-slice as necessary to render in the - /// `Display` implementation later on. - pub fn set_text(&mut self, contents: &str) { - for e in &mut self.errors { - if let Some(e) = e.downcast_mut::() { - e.set_text(contents); - } - } - } - - /// To provide a more useful error this function can be used to set - /// the file name that this error is associated with. - /// - /// The `path` here will be stored in this error and later rendered in the - /// `Display` implementation. - pub fn set_path(&mut self, path: &Path) { - for e in &mut self.errors { - if let Some(e) = e.downcast_mut::() { - e.set_path(path); - } - } - } -} - -/// Either `Ok(T)` or `Err(VerifyError)`. -pub type VerifyResult = Result; - -/// Verify and type check a set of optimizations. -pub fn verify(opts: &Optimizations) -> VerifyResult<()> -where - TOperator: Copy + Debug + Eq + Hash + TypingRules, -{ - if opts.optimizations.is_empty() { - return Err(anyhow::anyhow!("no optimizations").into()); - } - - verify_unique_left_hand_sides(opts)?; - - let z3 = &z3::Context::new(&z3::Config::new()); - for opt in &opts.optimizations { - verify_optimization(z3, opt)?; - } - Ok(()) -} - -/// Check that every LHS in the given optimizations is unique. -/// -/// If there were duplicates, then it would be nondeterministic which one we -/// applied and would make automata construction more difficult. It is better to -/// check for duplicates and reject them if found. -fn verify_unique_left_hand_sides(opts: &Optimizations) -> VerifyResult<()> -where - TOperator: Copy + Eq + Debug + Hash, -{ - let mut lefts = HashMap::new(); - for opt in &opts.optimizations { - let canon_lhs = canonicalized_lhs_key(&opt.lhs); - let existing = lefts.insert(canon_lhs, opt.lhs.span()); - if let Some(span) = existing { - return Err(VerifyError { - errors: vec![ - anyhow::anyhow!("error: two optimizations cannot have the same left-hand side"), - WastError::new(span, "note: first use of this left-hand side".into()).into(), - WastError::new( - opt.lhs.span(), - "note: second use of this left-hand side".into(), - ) - .into(), - ], - }); - } - } - Ok(()) -} - -/// When checking for duplicate left-hand sides, we need to consider patterns -/// that are duplicates up to renaming identifiers. For example, these LHSes -/// should be considered duplicates of each other: -/// -/// ```lisp -/// (=> (iadd $x $y) ...) -/// (=> (iadd $a $b) ...) -/// ``` -/// -/// This function creates an opaque, canonicalized hash key for left-hand sides -/// that sees through identifier renaming. -fn canonicalized_lhs_key(lhs: &Lhs) -> impl Hash + Eq -where - TOperator: Copy + Debug + Eq + Hash, -{ - let mut var_to_canon = HashMap::new(); - let mut const_to_canon = HashMap::new(); - let mut canonicalized = vec![]; - - for (event, ast) in Dfs::new(lhs) { - if event != TraversalEvent::Enter { - continue; - } - use CanonicalBit::*; - canonicalized.push(match ast { - DynAstRef::Lhs(_) => Other("Lhs"), - DynAstRef::Pattern(_) => Other("Pattern"), - DynAstRef::ValueLiteral(_) => Other("ValueLiteral"), - DynAstRef::Integer(i) => Integer(i.value), - DynAstRef::Boolean(b) => Boolean(b.value), - DynAstRef::ConditionCode(cc) => ConditionCode(cc.cc), - DynAstRef::PatternOperation(o) => Operation(o.operator, o.r#type.get()), - DynAstRef::Precondition(p) => Precondition(p.constraint), - DynAstRef::ConstraintOperand(_) => Other("ConstraintOperand"), - DynAstRef::Variable(Variable { id, .. }) => { - let new_id = var_to_canon.len() as u32; - let canon_id = var_to_canon.entry(id).or_insert(new_id); - Var(*canon_id) - } - DynAstRef::Constant(Constant { id, .. }) => { - let new_id = const_to_canon.len() as u32; - let canon_id = const_to_canon.entry(id).or_insert(new_id); - Const(*canon_id) - } - other => unreachable!("unreachable ast node: {:?}", other), - }); - } - - return canonicalized; - - #[derive(Hash, PartialEq, Eq)] - enum CanonicalBit { - Var(u32), - Const(u32), - Integer(i64), - Boolean(bool), - ConditionCode(peepmatic_runtime::cc::ConditionCode), - Operation(TOperator, Option), - Precondition(Constraint), - Other(&'static str), - } -} - -#[derive(Debug)] -struct TypingContext<'a, TOperator> { - z3: &'a z3::Context, - type_kind_sort: z3::DatatypeSort<'a>, - solver: z3::Solver<'a>, - - // The type of the root of the optimization. Initialized when collecting - // type constraints. - root_ty: Option>, - - // See the comments above `enter_operation_scope`. - operation_scope: HashMap<&'static str, TypeVar<'a>>, - - // A map from identifiers to the type variable describing its type. - id_to_type_var: HashMap, TypeVar<'a>>, - - // A list of type constraints, the span of the AST node where the constraint - // originates from, and an optional message to be displayed if the - // constraint is not satisfied. - constraints: Vec<(z3::ast::Bool<'a>, Span, Option>)>, - - // Keep track of AST nodes that need to have their types assigned to - // them. For these AST nodes, we know what bit width to use when - // interpreting peephole optimization actions. - boolean_literals: Vec<(&'a Boolean<'a, TOperator>, TypeVar<'a>)>, - integer_literals: Vec<(&'a Integer<'a, TOperator>, TypeVar<'a>)>, - rhs_operations: Vec<( - &'a Operation<'a, TOperator, Rhs<'a, TOperator>>, - TypeVar<'a>, - )>, -} - -impl<'a, TOperator> TypingContext<'a, TOperator> { - fn new(z3: &'a z3::Context) -> Self { - let type_kind_sort = z3::DatatypeBuilder::new(z3, "TypeKind") - .variant("int", vec![]) - .variant("bool", vec![]) - .variant("cpu_flags", vec![]) - .variant("cc", vec![]) - .variant("void", vec![]) - .finish(); - TypingContext { - z3, - solver: z3::Solver::new(z3), - root_ty: None, - operation_scope: Default::default(), - id_to_type_var: Default::default(), - type_kind_sort, - constraints: vec![], - boolean_literals: Default::default(), - integer_literals: Default::default(), - rhs_operations: Default::default(), - } - } - - fn init_root_type(&mut self, span: Span, root_ty: TypeVar<'a>) { - assert!(self.root_ty.is_none()); - - // Make sure the root is a valid kind, i.e. not a condition code. - let is_int = self.is_int(&root_ty); - let is_bool = self.is_bool(&root_ty); - let is_void = self.is_void(&root_ty); - let is_cpu_flags = self.is_cpu_flags(&root_ty); - self.constraints.push(( - z3::ast::Bool::or(&self.z3, &[&is_int, &is_bool, &is_void, &is_cpu_flags]), - span, - Some( - "the root of an optimization must be an integer, a boolean, void, or CPU flags" - .into(), - ), - )); - - self.root_ty = Some(root_ty); - } - - fn new_type_var(&self) -> TypeVar<'a> { - let kind = - z3::ast::Datatype::fresh_const(self.z3, "type-var-kind", &self.type_kind_sort.sort); - let width = z3::ast::BV::fresh_const(self.z3, "type-var-width", 8); - TypeVar { kind, width } - } - - fn get_or_create_type_var_for_id(&mut self, id: Id<'a>) -> TypeVar<'a> { - if let Some(ty) = self.id_to_type_var.get(&id) { - ty.clone() - } else { - // Note: can't use the entry API because we reborrow `self` here. - let ty = self.new_type_var(); - self.id_to_type_var.insert(id, ty.clone()); - ty - } - } - - fn get_type_var_for_id(&mut self, id: Id<'a>) -> VerifyResult> { - if let Some(ty) = self.id_to_type_var.get(&id) { - Ok(ty.clone()) - } else { - Err(WastError::new(id.span(), format!("unknown identifier: ${}", id.name())).into()) - } - } - - // The `#[peepmatic]` macro for operations allows defining operations' types - // like `(iNN, iNN) -> iNN` where `iNN` all refer to the same integer type - // variable that must have the same bit width. But other operations might - // *also* have that type signature but be instantiated at a different bit - // width. We don't want to mix up which `iNN` variables are and aren't the - // same. We use this method to track scopes within which all uses of `iNN` - // and similar refer to the same type variables. - fn enter_operation_scope<'b>( - &'b mut self, - ) -> impl DerefMut> + Drop + 'b { - assert!(self.operation_scope.is_empty()); - return Scope(self); - - struct Scope<'a, 'b, TOperator>(&'b mut TypingContext<'a, TOperator>) - where - 'a: 'b; - - impl<'a, 'b, TOperator> Deref for Scope<'a, 'b, TOperator> - where - 'a: 'b, - { - type Target = TypingContext<'a, TOperator>; - fn deref(&self) -> &TypingContext<'a, TOperator> { - self.0 - } - } - - impl<'a, 'b, TOperator> DerefMut for Scope<'a, 'b, TOperator> - where - 'a: 'b, - { - fn deref_mut(&mut self) -> &mut TypingContext<'a, TOperator> { - self.0 - } - } - - impl Drop for Scope<'_, '_, TOperator> { - fn drop(&mut self) { - self.0.operation_scope.clear(); - } - } - } - - fn remember_boolean_literal(&mut self, b: &'a Boolean<'a, TOperator>, ty: TypeVar<'a>) { - self.assert_is_bool(b.span, &ty); - self.boolean_literals.push((b, ty)); - } - - fn remember_integer_literal(&mut self, i: &'a Integer<'a, TOperator>, ty: TypeVar<'a>) { - self.assert_is_integer(i.span, &ty); - self.integer_literals.push((i, ty)); - } - - fn remember_rhs_operation( - &mut self, - op: &'a Operation<'a, TOperator, Rhs<'a, TOperator>>, - ty: TypeVar<'a>, - ) { - self.rhs_operations.push((op, ty)); - } - - fn is_int(&self, ty: &TypeVar<'a>) -> z3::ast::Bool<'a> { - self.type_kind_sort.variants[0] - .tester - .apply(&[&ty.kind.clone().into()]) - .as_bool() - .unwrap() - } - - fn is_bool(&self, ty: &TypeVar<'a>) -> z3::ast::Bool<'a> { - self.type_kind_sort.variants[1] - .tester - .apply(&[&ty.kind.clone().into()]) - .as_bool() - .unwrap() - } - - fn is_cpu_flags(&self, ty: &TypeVar<'a>) -> z3::ast::Bool<'a> { - self.type_kind_sort.variants[2] - .tester - .apply(&[&ty.kind.clone().into()]) - .as_bool() - .unwrap() - } - - fn is_condition_code(&self, ty: &TypeVar<'a>) -> z3::ast::Bool<'a> { - self.type_kind_sort.variants[3] - .tester - .apply(&[&ty.kind.clone().into()]) - .as_bool() - .unwrap() - } - - fn is_void(&self, ty: &TypeVar<'a>) -> z3::ast::Bool<'a> { - self.type_kind_sort.variants[4] - .tester - .apply(&[&ty.kind.clone().into()]) - .as_bool() - .unwrap() - } - - fn assert_is_integer(&mut self, span: Span, ty: &TypeVar<'a>) { - self.constraints.push(( - self.is_int(ty), - span, - Some("type error: expected integer".into()), - )); - } - - fn assert_is_bool(&mut self, span: Span, ty: &TypeVar<'a>) { - self.constraints.push(( - self.is_bool(ty), - span, - Some("type error: expected bool".into()), - )); - } - - fn assert_is_cpu_flags(&mut self, span: Span, ty: &TypeVar<'a>) { - self.constraints.push(( - self.is_cpu_flags(ty), - span, - Some("type error: expected CPU flags".into()), - )); - } - - fn assert_is_cc(&mut self, span: Span, ty: &TypeVar<'a>) { - self.constraints.push(( - self.is_condition_code(ty), - span, - Some("type error: expected condition code".into()), - )); - } - - fn assert_is_void(&mut self, span: Span, ty: &TypeVar<'a>) { - self.constraints.push(( - self.is_void(ty), - span, - Some("type error: expected void".into()), - )); - } - - fn assert_bit_width(&mut self, span: Span, ty: &TypeVar<'a>, width: u8) { - debug_assert!(width == 0 || width.is_power_of_two()); - let width_var = z3::ast::BV::from_i64(self.z3, width as i64, 8); - let is_width = width_var._eq(&ty.width); - self.constraints.push(( - is_width, - span, - Some(format!("type error: expected bit width = {}", width).into()), - )); - } - - fn assert_bit_width_lt(&mut self, span: Span, a: &TypeVar<'a>, b: &TypeVar<'a>) { - self.constraints.push(( - a.width.bvult(&b.width), - span, - Some("type error: expected narrower bit width".into()), - )); - } - - fn assert_bit_width_gt(&mut self, span: Span, a: &TypeVar<'a>, b: &TypeVar<'a>) { - self.constraints.push(( - a.width.bvugt(&b.width), - span, - Some("type error: expected wider bit width".into()), - )); - } - - fn assert_type_eq( - &mut self, - span: Span, - lhs: &TypeVar<'a>, - rhs: &TypeVar<'a>, - msg: Option>, - ) { - self.constraints - .push((lhs.kind._eq(&rhs.kind), span, msg.clone())); - self.constraints - .push((lhs.width._eq(&rhs.width), span, msg)); - } - - fn type_check(&self, span: Span) -> VerifyResult<()> { - let trackers = iter::repeat_with(|| z3::ast::Bool::fresh_const(self.z3, "type-constraint")) - .take(self.constraints.len()) - .collect::>(); - - let mut tracker_to_diagnostics = HashMap::with_capacity(self.constraints.len()); - - for (constraint_data, tracker) in self.constraints.iter().zip(trackers) { - let (constraint, span, msg) = constraint_data; - self.solver.assert_and_track(constraint, &tracker); - tracker_to_diagnostics.insert(tracker, (*span, msg.clone())); - } - - match self.solver.check() { - z3::SatResult::Sat => Ok(()), - z3::SatResult::Unsat => { - let core = self.solver.get_unsat_core(); - if core.is_empty() { - return Err(WastError::new( - span, - "z3 determined the type constraints for this optimization are \ - unsatisfiable, meaning there is a type error, but z3 did not give us any \ - additional information" - .into(), - ) - .into()); - } - - let mut errors = core - .iter() - .map(|tracker| { - let (span, msg) = &tracker_to_diagnostics[tracker]; - ( - *span, - WastError::new( - *span, - msg.clone().unwrap_or("type error".into()).into(), - ) - .into(), - ) - }) - .collect::>(); - errors.sort_by_key(|(span, _)| *span); - let errors = errors.into_iter().map(|(_, e)| e).collect(); - - Err(VerifyError { errors }) - } - z3::SatResult::Unknown => Err(anyhow::anyhow!( - "z3 returned 'unknown' when evaluating type constraints: {}", - self.solver - .get_reason_unknown() - .unwrap_or_else(|| "".into()) - ) - .into()), - } - } - - fn assign_types(&mut self) -> VerifyResult<()> { - for (int, ty) in mem::replace(&mut self.integer_literals, vec![]) { - let width = self.ty_var_to_width(&ty)?; - int.bit_width.set(Some(width)); - } - - for (b, ty) in mem::replace(&mut self.boolean_literals, vec![]) { - let width = self.ty_var_to_width(&ty)?; - b.bit_width.set(Some(width)); - } - - for (op, ty) in mem::replace(&mut self.rhs_operations, vec![]) { - let kind = self.op_ty_var_to_kind(&ty); - let bit_width = match kind { - Kind::CpuFlags | Kind::Void => BitWidth::One, - Kind::Int | Kind::Bool => self.ty_var_to_width(&ty)?, - }; - debug_assert!(op.r#type.get().is_none()); - op.r#type.set(Some(Type { kind, bit_width })); - } - - Ok(()) - } - - fn ty_var_to_width(&self, ty_var: &TypeVar<'a>) -> VerifyResult { - // Doing solver push/pops apparently clears out the model, so we have to - // re-check each time to ensure that it exists, and Z3 doesn't helpfully - // abort the process for us. This should be fast, since the solver - // remembers inferences from earlier checks. - assert_eq!(self.solver.check(), z3::SatResult::Sat); - - // Check if there is more than one satisfying assignment to - // `ty_var`'s width variable. If so, then it must be polymorphic. If - // not, then it must have a fixed value. - let model = self.solver.get_model().unwrap(); - let width_var = model.eval(&ty_var.width).unwrap(); - let bit_width: u8 = width_var.as_u64().unwrap().try_into().unwrap(); - - self.solver.push(); - self.solver.assert(&ty_var.width._eq(&width_var).not()); - let is_polymorphic = match self.solver.check() { - z3::SatResult::Sat => true, - z3::SatResult::Unsat => false, - z3::SatResult::Unknown => panic!("Z3 cannot determine bit width of type"), - }; - self.solver.pop(1); - - if is_polymorphic { - // If something is polymorphic over bit widths, it must be - // polymorphic over the same bit width as the whole - // optimization. - // - // TODO: We should have a better model for bit-width - // polymorphism. The current setup works for all the use cases we - // currently care about, and is relatively easy to implement when - // matching and constructing the RHS, but is a bit ad-hoc. Maybe - // allow each LHS variable a polymorphic bit width, augment the AST - // with that info, and later emit match ops as necessary to express - // their relative constraints? *hand waves* - self.solver.push(); - self.solver - .assert(&ty_var.width._eq(&self.root_ty.as_ref().unwrap().width)); - match self.solver.check() { - z3::SatResult::Sat => {} - z3::SatResult::Unsat => { - return Err(anyhow::anyhow!( - "AST node is bit width polymorphic, but not over the optimization's root \ - width" - ) - .into()) - } - z3::SatResult::Unknown => panic!("Z3 cannot determine bit width of type"), - }; - self.solver.pop(1); - - Ok(BitWidth::Polymorphic) - } else { - Ok(BitWidth::try_from(bit_width).unwrap()) - } - } - - fn op_ty_var_to_kind(&self, ty_var: &TypeVar<'a>) -> Kind { - for (predicate, kind) in [ - (Self::is_int as fn(_, _) -> _, Kind::Int), - (Self::is_bool, Kind::Bool), - (Self::is_cpu_flags, Kind::CpuFlags), - (Self::is_void, Kind::Void), - ] - .iter() - { - self.solver.push(); - self.solver.assert(&predicate(self, ty_var)); - match self.solver.check() { - z3::SatResult::Sat => { - self.solver.pop(1); - return *kind; - } - z3::SatResult::Unsat => { - self.solver.pop(1); - continue; - } - z3::SatResult::Unknown => panic!("Z3 cannot determine the type's kind"), - } - } - - // This would only happen if given a `TypeVar` whose kind was a - // condition code, but we only use this function for RHS operations, - // which cannot be condition codes. - panic!("cannot convert type variable's kind to `peepmatic_runtime::type::Kind`") - } -} - -impl<'a, TOperator> TypingContextTrait<'a> for TypingContext<'a, TOperator> { - type Span = Span; - type TypeVariable = TypeVar<'a>; - - fn cc(&mut self, span: Span) -> TypeVar<'a> { - let ty = self.new_type_var(); - self.assert_is_cc(span, &ty); - ty - } - - fn bNN(&mut self, span: Span) -> TypeVar<'a> { - if let Some(ty) = self.operation_scope.get("bNN") { - return ty.clone(); - } - - let ty = self.new_type_var(); - self.assert_is_bool(span, &ty); - self.operation_scope.insert("bNN", ty.clone()); - ty - } - - fn iNN(&mut self, span: Span) -> TypeVar<'a> { - if let Some(ty) = self.operation_scope.get("iNN") { - return ty.clone(); - } - - let ty = self.new_type_var(); - self.assert_is_integer(span, &ty); - self.operation_scope.insert("iNN", ty.clone()); - ty - } - - fn iMM(&mut self, span: Span) -> TypeVar<'a> { - if let Some(ty) = self.operation_scope.get("iMM") { - return ty.clone(); - } - - let ty = self.new_type_var(); - self.assert_is_integer(span, &ty); - self.operation_scope.insert("iMM", ty.clone()); - ty - } - - fn cpu_flags(&mut self, span: Span) -> TypeVar<'a> { - if let Some(ty) = self.operation_scope.get("cpu_flags") { - return ty.clone(); - } - - let ty = self.new_type_var(); - self.assert_is_cpu_flags(span, &ty); - self.assert_bit_width(span, &ty, 1); - self.operation_scope.insert("cpu_flags", ty.clone()); - ty - } - - fn b1(&mut self, span: Span) -> TypeVar<'a> { - let b1 = self.new_type_var(); - self.assert_is_bool(span, &b1); - self.assert_bit_width(span, &b1, 1); - b1 - } - - fn void(&mut self, span: Span) -> TypeVar<'a> { - let void = self.new_type_var(); - self.assert_is_void(span, &void); - self.assert_bit_width(span, &void, 0); - void - } - - fn bool_or_int(&mut self, span: Span) -> TypeVar<'a> { - let ty = self.new_type_var(); - let is_int = self.type_kind_sort.variants[0] - .tester - .apply(&[&ty.kind.clone().into()]) - .as_bool() - .unwrap(); - let is_bool = self.type_kind_sort.variants[1] - .tester - .apply(&[&ty.kind.clone().into()]) - .as_bool() - .unwrap(); - self.constraints.push(( - z3::ast::Bool::or(&self.z3, &[&is_int, &is_bool]), - span, - Some("type error: must be either an int or a bool type".into()), - )); - ty - } - - fn any_t(&mut self, _span: Span) -> TypeVar<'a> { - if let Some(ty) = self.operation_scope.get("any_t") { - return ty.clone(); - } - - let ty = self.new_type_var(); - self.operation_scope.insert("any_t", ty.clone()); - ty - } -} - -#[derive(Clone, Debug)] -struct TypeVar<'a> { - kind: z3::ast::Datatype<'a>, - width: z3::ast::BV<'a>, -} - -fn verify_optimization( - z3: &z3::Context, - opt: &Optimization, -) -> VerifyResult<()> -where - TOperator: Copy + Debug + Eq + Hash + TypingRules, -{ - let mut context = TypingContext::new(z3); - collect_type_constraints(&mut context, opt)?; - context.type_check(opt.span)?; - context.assign_types()?; - - // TODO: add another pass here to check for counter-examples to this - // optimization, i.e. inputs where the LHS and RHS are not equivalent. - - Ok(()) -} - -fn collect_type_constraints<'a, TOperator>( - context: &mut TypingContext<'a, TOperator>, - opt: &'a Optimization<'a, TOperator>, -) -> VerifyResult<()> -where - TOperator: Copy + Debug + Eq + Hash + TypingRules, -{ - use crate::traversals::TraversalEvent as TE; - - let lhs_ty = context.new_type_var(); - context.init_root_type(opt.lhs.span, lhs_ty.clone()); - - let rhs_ty = context.new_type_var(); - context.assert_type_eq( - opt.span, - &lhs_ty, - &rhs_ty, - Some("type error: the left-hand side and right-hand side must have the same type".into()), - ); - - // A stack of type variables that we are constraining as we traverse the - // AST. Operations push new type variables for their operands' expected - // types, and exiting a `Pattern` in the traversal pops them off. - let mut expected_types = vec![lhs_ty]; - - // Build up the type constraints for the left-hand side. - for (event, node) in Dfs::new(&opt.lhs) { - match (event, node) { - ( - TE::Enter, - DynAstRef::Pattern(Pattern::Constant(Constant { - id, - span, - marker: _, - })), - ) - | ( - TE::Enter, - DynAstRef::Pattern(Pattern::Variable(Variable { - id, - span, - marker: _, - })), - ) => { - let id = context.get_or_create_type_var_for_id(*id); - context.assert_type_eq(*span, expected_types.last().unwrap(), &id, None); - } - (TE::Enter, DynAstRef::Pattern(Pattern::ValueLiteral(ValueLiteral::Integer(i)))) => { - let ty = expected_types.last().unwrap(); - context.remember_integer_literal(i, ty.clone()); - } - (TE::Enter, DynAstRef::Pattern(Pattern::ValueLiteral(ValueLiteral::Boolean(b)))) => { - let ty = expected_types.last().unwrap(); - context.remember_boolean_literal(b, ty.clone()); - } - ( - TE::Enter, - DynAstRef::Pattern(Pattern::ValueLiteral(ValueLiteral::ConditionCode(cc))), - ) => { - let ty = expected_types.last().unwrap(); - context.assert_is_cc(cc.span, ty); - } - (TE::Enter, DynAstRef::PatternOperation(op)) => { - let result_ty; - let mut operand_types = vec![]; - { - let mut scope = context.enter_operation_scope(); - result_ty = op.operator.result_type(op.span, &mut *scope); - op.operator - .immediate_types(op.span, &mut *scope, &mut operand_types); - op.operator - .parameter_types(op.span, &mut *scope, &mut operand_types); - } - - if op.operands.len() != operand_types.len() { - return Err(WastError::new( - op.span, - format!( - "Expected {} operands but found {}", - operand_types.len(), - op.operands.len() - ), - ) - .into()); - } - - for imm in op - .operands - .iter() - .take(op.operator.immediates_arity() as usize) - { - match imm { - Pattern::ValueLiteral(_) | - Pattern::Constant(_) | - Pattern::Variable(_) => continue, - Pattern::Operation(op) => return Err(WastError::new( - op.span, - "operations are invalid immediates; must be a value literal, constant, \ - or variable".into() - ).into()), - } - } - - if (op.operator.is_reduce() || op.operator.is_extend()) && op.r#type.get().is_none() - { - return Err(WastError::new( - op.span, - "`ireduce`, `sextend`, and `uextend` require an ascribed type, \ - like `(sextend{i64} ...)`" - .into(), - ) - .into()); - } - - if op.operator.is_extend() { - context.assert_bit_width_gt(op.span, &result_ty, &operand_types[0]); - } - if op.operator.is_reduce() { - context.assert_bit_width_lt(op.span, &result_ty, &operand_types[0]); - } - - if let Some(ty) = op.r#type.get() { - match ty.kind { - Kind::Bool => context.assert_is_bool(op.span, &result_ty), - Kind::Int => context.assert_is_integer(op.span, &result_ty), - Kind::Void => context.assert_is_void(op.span, &result_ty), - Kind::CpuFlags => { - unreachable!("no syntax for ascribing CPU flags types right now") - } - } - if let Some(w) = ty.bit_width.fixed_width() { - context.assert_bit_width(op.span, &result_ty, w); - } - } - - context.assert_type_eq(op.span, expected_types.last().unwrap(), &result_ty, None); - - operand_types.reverse(); - expected_types.extend(operand_types); - } - (TE::Exit, DynAstRef::Pattern(..)) => { - expected_types.pop().unwrap(); - } - (TE::Enter, DynAstRef::Precondition(pre)) => { - type_constrain_precondition(context, pre)?; - } - _ => continue, - } - } - - // We should have exited exactly as many patterns as we entered: one for the - // root pattern and the initial `lhs_ty`, and then the rest for the operands - // of pattern operations. - assert!(expected_types.is_empty()); - - // Collect the type constraints for the right-hand side. - expected_types.push(rhs_ty); - for (event, node) in Dfs::new(&opt.rhs) { - match (event, node) { - (TE::Enter, DynAstRef::Rhs(Rhs::ValueLiteral(ValueLiteral::Integer(i)))) => { - let ty = expected_types.last().unwrap(); - context.remember_integer_literal(i, ty.clone()); - } - (TE::Enter, DynAstRef::Rhs(Rhs::ValueLiteral(ValueLiteral::Boolean(b)))) => { - let ty = expected_types.last().unwrap(); - context.remember_boolean_literal(b, ty.clone()); - } - (TE::Enter, DynAstRef::Rhs(Rhs::ValueLiteral(ValueLiteral::ConditionCode(cc)))) => { - let ty = expected_types.last().unwrap(); - context.assert_is_cc(cc.span, ty); - } - ( - TE::Enter, - DynAstRef::Rhs(Rhs::Constant(Constant { - span, - id, - marker: _, - })), - ) - | ( - TE::Enter, - DynAstRef::Rhs(Rhs::Variable(Variable { - span, - id, - marker: _, - })), - ) => { - let id_ty = context.get_type_var_for_id(*id)?; - context.assert_type_eq(*span, expected_types.last().unwrap(), &id_ty, None); - } - (TE::Enter, DynAstRef::RhsOperation(op)) => { - let result_ty; - let mut operand_types = vec![]; - { - let mut scope = context.enter_operation_scope(); - result_ty = op.operator.result_type(op.span, &mut *scope); - op.operator - .immediate_types(op.span, &mut *scope, &mut operand_types); - op.operator - .parameter_types(op.span, &mut *scope, &mut operand_types); - } - - if op.operands.len() != operand_types.len() { - return Err(WastError::new( - op.span, - format!( - "Expected {} operands but found {}", - operand_types.len(), - op.operands.len() - ), - ) - .into()); - } - - for imm in op - .operands - .iter() - .take(op.operator.immediates_arity() as usize) - { - match imm { - Rhs::ValueLiteral(_) - | Rhs::Constant(_) - | Rhs::Variable(_) - | Rhs::Unquote(_) => continue, - Rhs::Operation(op) => return Err(WastError::new( - op.span, - "operations are invalid immediates; must be a value literal, unquote, \ - constant, or variable" - .into(), - ) - .into()), - } - } - - if (op.operator.is_reduce() || op.operator.is_extend()) && op.r#type.get().is_none() - { - return Err(WastError::new( - op.span, - "`ireduce`, `sextend`, and `uextend` require an ascribed type, \ - like `(sextend{i64} ...)`" - .into(), - ) - .into()); - } - - if op.operator.is_extend() { - context.assert_bit_width_gt(op.span, &result_ty, &operand_types[0]); - } - if op.operator.is_reduce() { - context.assert_bit_width_lt(op.span, &result_ty, &operand_types[0]); - } - - if let Some(ty) = op.r#type.get() { - match ty.kind { - Kind::Bool => context.assert_is_bool(op.span, &result_ty), - Kind::Int => context.assert_is_integer(op.span, &result_ty), - Kind::Void => context.assert_is_void(op.span, &result_ty), - Kind::CpuFlags => { - unreachable!("no syntax for ascribing CPU flags types right now") - } - } - if let Some(w) = ty.bit_width.fixed_width() { - context.assert_bit_width(op.span, &result_ty, w); - } - } - - context.assert_type_eq(op.span, expected_types.last().unwrap(), &result_ty, None); - if op.r#type.get().is_none() { - context.remember_rhs_operation(op, result_ty); - } - - operand_types.reverse(); - expected_types.extend(operand_types); - } - (TE::Enter, DynAstRef::Unquote(unq)) => { - let result_ty; - let mut operand_types = vec![]; - { - let mut scope = context.enter_operation_scope(); - result_ty = unq.operator.result_type(unq.span, &mut *scope); - unq.operator - .immediate_types(unq.span, &mut *scope, &mut operand_types); - unq.operator - .parameter_types(unq.span, &mut *scope, &mut operand_types); - } - - if unq.operands.len() != operand_types.len() { - return Err(WastError::new( - unq.span, - format!( - "Expected {} unquote operands but found {}", - operand_types.len(), - unq.operands.len() - ), - ) - .into()); - } - - for operand in &unq.operands { - match operand { - Rhs::ValueLiteral(_) | Rhs::Constant(_) => continue, - Rhs::Variable(_) | Rhs::Unquote(_) | Rhs::Operation(_) => { - return Err(WastError::new( - operand.span(), - "unquote operands must be value literals or constants".into(), - ) - .into()); - } - } - } - - context.assert_type_eq(unq.span, expected_types.last().unwrap(), &result_ty, None); - - operand_types.reverse(); - expected_types.extend(operand_types); - } - (TE::Exit, DynAstRef::Rhs(..)) => { - expected_types.pop().unwrap(); - } - _ => continue, - } - } - - // Again, we should have popped off all the expected types when exiting - // `Rhs` nodes in the traversal. - assert!(expected_types.is_empty()); - - Ok(()) -} - -fn type_constrain_precondition<'a, TOperator>( - context: &mut TypingContext<'a, TOperator>, - pre: &Precondition<'a, TOperator>, -) -> VerifyResult<()> { - match pre.constraint { - Constraint::BitWidth => { - if pre.operands.len() != 2 { - return Err(WastError::new( - pre.span, - format!( - "the `bit-width` precondition requires exactly 2 operands, found \ - {} operands", - pre.operands.len(), - ), - ) - .into()); - } - - let id = match pre.operands[0] { - ConstraintOperand::ValueLiteral(_) => { - return Err(anyhow::anyhow!( - "the `bit-width` precondition requires a constant or variable as \ - its first operand" - ) - .into()) - } - ConstraintOperand::Constant(Constant { id, .. }) - | ConstraintOperand::Variable(Variable { id, .. }) => id, - }; - - let width = match pre.operands[1] { - ConstraintOperand::ValueLiteral(ValueLiteral::Integer(Integer { - value, .. - })) if value == 1 - || value == 8 - || value == 16 - || value == 32 - || value == 64 - || value == 128 => - { - value as u8 - } - ref op => return Err(WastError::new( - op.span(), - "the `bit-width` precondition requires a bit width of 1, 8, 16, 32, 64, or \ - 128" - .into(), - ) - .into()), - }; - - let ty = context.get_type_var_for_id(id)?; - context.assert_bit_width(pre.span, &ty, width); - Ok(()) - } - Constraint::IsPowerOfTwo => { - if pre.operands.len() != 1 { - return Err(WastError::new( - pre.span, - format!( - "the `is-power-of-two` precondition requires exactly 1 operand, found \ - {} operands", - pre.operands.len(), - ), - ) - .into()); - } - match &pre.operands[0] { - ConstraintOperand::Constant(Constant { id, .. }) => { - let ty = context.get_type_var_for_id(*id)?; - context.assert_is_integer(pre.span(), &ty); - Ok(()) - } - op => Err(WastError::new( - op.span(), - "`is-power-of-two` operands must be constant bindings".into(), - ) - .into()), - } - } - Constraint::FitsInNativeWord => { - if pre.operands.len() != 1 { - return Err(WastError::new( - pre.span, - format!( - "the `fits-in-native-word` precondition requires exactly 1 operand, found \ - {} operands", - pre.operands.len(), - ), - ) - .into()); - } - - match pre.operands[0] { - ConstraintOperand::ValueLiteral(_) => { - return Err(anyhow::anyhow!( - "the `fits-in-native-word` precondition requires a constant or variable as \ - its first operand" - ) - .into()) - } - ConstraintOperand::Constant(Constant { id, .. }) - | ConstraintOperand::Variable(Variable { id, .. }) => { - context.get_type_var_for_id(id)?; - Ok(()) - } - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use peepmatic_test_operator::TestOperator; - - macro_rules! verify_ok { - ($name:ident, $src:expr) => { - #[test] - fn $name() { - let buf = wast::parser::ParseBuffer::new($src).expect("should lex OK"); - let opts = match wast::parser::parse::>(&buf) { - Ok(opts) => opts, - Err(mut e) => { - e.set_path(Path::new(stringify!($name))); - e.set_text($src); - eprintln!("{}", e); - panic!("should parse OK") - } - }; - match verify(&opts) { - Ok(_) => return, - Err(mut e) => { - e.set_path(Path::new(stringify!($name))); - e.set_text($src); - eprintln!("{}", e); - panic!("should verify OK") - } - } - } - }; - } - - macro_rules! verify_err { - ($name:ident, $src:expr) => { - #[test] - fn $name() { - let buf = wast::parser::ParseBuffer::new($src).expect("should lex OK"); - let opts = match wast::parser::parse::>(&buf) { - Ok(opts) => opts, - Err(mut e) => { - e.set_path(Path::new(stringify!($name))); - e.set_text($src); - eprintln!("{}", e); - panic!("should parse OK") - } - }; - match verify(&opts) { - Ok(_) => panic!("expected a verification error, but it verified OK"), - Err(mut e) => { - e.set_path(Path::new(stringify!($name))); - e.set_text($src); - eprintln!("{}", e); - return; - } - } - } - }; - } - - verify_ok!(bool_0, "(=> true true)"); - verify_ok!(bool_1, "(=> false false)"); - verify_ok!(bool_2, "(=> true false)"); - verify_ok!(bool_3, "(=> false true)"); - - verify_err!(bool_is_not_int_0, "(=> true 42)"); - verify_err!(bool_is_not_int_1, "(=> 42 true)"); - - verify_ok!( - bit_width_0, - " -(=> (when (iadd $x $y) - (bit-width $x 32) - (bit-width $y 32)) - (iadd $x $y)) -" - ); - verify_err!( - bit_width_1, - " -(=> (when (iadd $x $y) - (bit-width $x 32) - (bit-width $y 64)) - (iadd $x $y)) -" - ); - verify_err!( - bit_width_2, - " -(=> (when (iconst $C) - (bit-width $C)) - 5) -" - ); - verify_err!( - bit_width_3, - " -(=> (when (iconst $C) - (bit-width 32 32)) - 5) -" - ); - verify_err!( - bit_width_4, - " -(=> (when (iconst $C) - (bit-width $C $C)) - 5) -" - ); - verify_err!( - bit_width_5, - " -(=> (when (iconst $C) - (bit-width $C2 32)) - 5) -" - ); - verify_err!( - bit_width_6, - " -(=> (when (iconst $C) - (bit-width $C2 33)) - 5) -" - ); - - verify_ok!( - is_power_of_two_0, - " -(=> (when (imul $x $C) - (is-power-of-two $C)) - (ishl $x $(log2 $C))) -" - ); - verify_err!( - is_power_of_two_1, - " -(=> (when (imul $x $C) - (is-power-of-two)) - 5) -" - ); - verify_err!( - is_power_of_two_2, - " -(=> (when (imul $x $C) - (is-power-of-two $C $C)) - 5) -" - ); - - verify_ok!(pattern_ops_0, "(=> (iadd $x $C) 5)"); - verify_err!(pattern_ops_1, "(=> (iadd $x) 5)"); - verify_err!(pattern_ops_2, "(=> (iadd $x $y $z) 5)"); - - verify_ok!(unquote_0, "(=> $C $(log2 $C))"); - verify_err!(unquote_1, "(=> (iadd $C $D) $(log2 $C $D))"); - verify_err!(unquote_2, "(=> $x $(log2))"); - verify_ok!(unquote_3, "(=> $C $(neg $C))"); - verify_err!(unquote_4, "(=> $x $(neg))"); - verify_err!(unquote_5, "(=> (iadd $x $y) $(neg $x $y))"); - verify_err!(unquote_6, "(=> $x $(neg $x))"); - - verify_ok!(rhs_0, "(=> $x (iadd $x (iconst 0)))"); - verify_err!(rhs_1, "(=> $x (iadd $x))"); - verify_err!(rhs_2, "(=> $x (iadd $x 0 0))"); - - verify_err!(no_optimizations, ""); - - verify_err!( - duplicate_left_hand_sides, - " -(=> (iadd $x $y) 0) -(=> (iadd $x $y) 1) -" - ); - verify_err!( - canonically_duplicate_left_hand_sides_0, - " -(=> (iadd $x $y) 0) -(=> (iadd $y $x) 1) -" - ); - verify_err!( - canonically_duplicate_left_hand_sides_1, - " -(=> (iadd $X $Y) 0) -(=> (iadd $Y $X) 1) -" - ); - verify_err!( - canonically_duplicate_left_hand_sides_2, - " -(=> (iadd $x $x) 0) -(=> (iadd $y $y) 1) -" - ); - - verify_ok!( - canonically_different_left_hand_sides_0, - " -(=> (iadd $x $C) 0) -(=> (iadd $C $x) 1) -" - ); - verify_ok!( - canonically_different_left_hand_sides_1, - " -(=> (iadd $x $x) 0) -(=> (iadd $x $y) 1) -" - ); - - verify_ok!( - fits_in_native_word_0, - "(=> (when (iadd $x $y) (fits-in-native-word $x)) 0)" - ); - verify_err!( - fits_in_native_word_1, - "(=> (when (iadd $x $y) (fits-in-native-word)) 0)" - ); - verify_err!( - fits_in_native_word_2, - "(=> (when (iadd $x $y) (fits-in-native-word $x $y)) 0)" - ); - verify_err!( - fits_in_native_word_3, - "(=> (when (iadd $x $y) (fits-in-native-word true)) 0)" - ); - - verify_err!(reduce_extend_0, "(=> (sextend (ireduce -1)) 0)"); - verify_err!(reduce_extend_1, "(=> (uextend (ireduce -1)) 0)"); - verify_ok!(reduce_extend_2, "(=> (sextend{i64} (ireduce{i32} -1)) 0)"); - verify_ok!(reduce_extend_3, "(=> (uextend{i64} (ireduce{i32} -1)) 0)"); - verify_err!(reduce_extend_4, "(=> (sextend{i64} (ireduce{i64} -1)) 0)"); - verify_err!(reduce_extend_5, "(=> (uextend{i64} (ireduce{i64} -1)) 0)"); - verify_err!(reduce_extend_6, "(=> (sextend{i32} (ireduce{i64} -1)) 0)"); - verify_err!(reduce_extend_7, "(=> (uextend{i32} (ireduce{i64} -1)) 0)"); - - verify_err!( - using_an_operation_as_an_immediate_in_lhs, - "(=> (iadd_imm (imul $x $y) $z) 0)" - ); - verify_err!( - using_an_operation_as_an_immediate_in_rhs, - "(=> (iadd (imul $x $y) $z) (iadd_imm (imul $x $y) $z))" - ); - - verify_err!( - using_a_condition_code_as_the_root_of_an_optimization, - "(=> eq eq)" - ); -} diff --git a/cranelift/src/clif-util.rs b/cranelift/src/clif-util.rs old mode 100644 new mode 100755 index 481401614c..ba9a32d327 --- a/cranelift/src/clif-util.rs +++ b/cranelift/src/clif-util.rs @@ -28,9 +28,6 @@ mod utils; #[cfg(feature = "souper-harvest")] mod souper_harvest; -#[cfg(feature = "peepmatic-souper")] -mod souper_to_peepmatic; - #[cfg(feature = "wasm")] mod wasm; @@ -59,11 +56,6 @@ enum Commands { #[cfg(not(feature = "wasm"))] Wasm(CompiledWithoutSupportOptions), - #[cfg(feature = "peepmatic-souper")] - SouperToPeepmatic(souper_to_peepmatic::Options), - #[cfg(not(feature = "peepmatic-souper"))] - SouperToPeepmatic(CompiledWithoutSupportOptions), - #[cfg(feature = "souper-harvest")] SouperHarvest(souper_harvest::Options), #[cfg(not(feature = "souper-harvest"))] @@ -135,14 +127,6 @@ fn main() -> anyhow::Result<()> { #[cfg(not(feature = "wasm"))] Commands::Wasm(_) => anyhow::bail!("Error: clif-util was compiled without wasm support."), - #[cfg(feature = "peepmatic-souper")] - Commands::SouperToPeepmatic(s) => souper_to_peepmatic::run(&s)?, - #[cfg(not(feature = "peepmatic-souper"))] - Commands::SouperToPeepmatic(_) => anyhow::bail!( - "Error: clif-util was compiled without support for the `souper-to-peepmatic` \ - subcommand", - ), - #[cfg(feature = "souper-harvest")] Commands::SouperHarvest(s) => souper_harvest::run(&s)?, #[cfg(not(feature = "souper-harvest"))] diff --git a/cranelift/src/souper_to_peepmatic.rs b/cranelift/src/souper_to_peepmatic.rs deleted file mode 100644 index cad181289a..0000000000 --- a/cranelift/src/souper_to_peepmatic.rs +++ /dev/null @@ -1,44 +0,0 @@ -use anyhow::{Context, Result}; -use std::io::{Read, Write}; -use std::path::{Path, PathBuf}; -use structopt::StructOpt; - -/// Convert Souper optimizations into Peepmatic DSL. -#[derive(StructOpt)] -pub struct Options { - /// Specify an input file to be used. Use '-' for stdin. - #[structopt(parse(from_os_str))] - input: PathBuf, - - /// Specify the output file to be used. Use '-' for stdout. - #[structopt(short("o"), long("output"), default_value("-"), parse(from_os_str))] - output: PathBuf, -} - -pub fn run(options: &Options) -> Result<()> { - let peepmatic_dsl = if options.input == Path::new("-") { - let stdin = std::io::stdin(); - let mut stdin = stdin.lock(); - let mut souper_dsl = vec![]; - stdin - .read_to_end(&mut souper_dsl) - .context("failed to read from stdin")?; - let souper_dsl = String::from_utf8(souper_dsl).context("stdin is not UTF-8: {}")?; - peepmatic_souper::convert_str(&souper_dsl, Some(Path::new("stdin")))? - } else { - peepmatic_souper::convert_file(&options.input)? - }; - - if options.output == Path::new("-") { - let stdout = std::io::stdout(); - let mut stdout = stdout.lock(); - stdout - .write_all(peepmatic_dsl.as_bytes()) - .context("error writing to stdout")?; - } else { - std::fs::write(&options.output, peepmatic_dsl.as_bytes()) - .with_context(|| format!("error writing to {}", options.output.display()))?; - } - - Ok(()) -} diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index b673450ba3..9c2b3d1ee9 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -17,7 +17,6 @@ cranelift-interpreter = { path = "../cranelift/interpreter" } cranelift-fuzzgen = { path = "../cranelift/fuzzgen" } libfuzzer-sys = "0.4.0" target-lexicon = "0.12" -peepmatic-fuzzing = { path = "../cranelift/peepmatic/crates/fuzzing", optional = true } wasmtime = { path = "../crates/wasmtime" } wasmtime-fuzzing = { path = "../crates/fuzzing" } wasm-smith = "0.7.0" diff --git a/scripts/publish.rs b/scripts/publish.rs index 1b8083eb4a..165a8e0b3b 100644 --- a/scripts/publish.rs +++ b/scripts/publish.rs @@ -17,14 +17,6 @@ use std::time::Duration; // note that this list must be topologically sorted by dependencies const CRATES_TO_PUBLISH: &[&str] = &[ - // peepmatic - "peepmatic-traits", - "peepmatic-macro", - "peepmatic-automata", - "peepmatic-test-operator", - "peepmatic-runtime", - "peepmatic", - "peepmatic-souper", // cranelift "isle", "cranelift-entity", @@ -461,7 +453,6 @@ fn verify(crates: &[Crate]) { .env("CARGO_TARGET_DIR", "./target"); if krate.name == "witx" || krate.name.contains("wasi-nn") - || krate.name.contains("peepmatic") { cmd.arg("--no-verify"); }