From de9fc63009fe39a45623fa767ad10d906a7495aa Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Fri, 1 May 2020 15:41:06 -0700 Subject: [PATCH] peepmatic: Introduce the main `peepmatic` crate 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. [Souper]: https://github.com/google/souper [Alive]: https://github.com/AliveToolkit/alive2 --- cranelift/peepmatic/Cargo.toml | 15 + cranelift/peepmatic/README.md | 423 +++++ .../peepmatic/examples/mul-by-pow2.peepmatic | 3 + cranelift/peepmatic/examples/preopt.peepmatic | 193 +++ .../examples/redundant-bor.peepmatic | 13 + .../peepmatic/examples/redundant-bor.png | Bin 0 -> 225727 bytes cranelift/peepmatic/examples/simple.peepmatic | 11 + cranelift/peepmatic/src/ast.rs | 508 ++++++ cranelift/peepmatic/src/automatize.rs | 31 + cranelift/peepmatic/src/dot_fmt.rs | 142 ++ cranelift/peepmatic/src/lib.rs | 165 ++ cranelift/peepmatic/src/linear_passes.rs | 444 +++++ cranelift/peepmatic/src/linearize.rs | 831 ++++++++++ cranelift/peepmatic/src/parser.rs | 932 +++++++++++ cranelift/peepmatic/src/traversals.rs | 278 ++++ cranelift/peepmatic/src/verify.rs | 1433 +++++++++++++++++ 16 files changed, 5422 insertions(+) create mode 100644 cranelift/peepmatic/Cargo.toml create mode 100644 cranelift/peepmatic/README.md create mode 100644 cranelift/peepmatic/examples/mul-by-pow2.peepmatic create mode 100644 cranelift/peepmatic/examples/preopt.peepmatic create mode 100644 cranelift/peepmatic/examples/redundant-bor.peepmatic create mode 100644 cranelift/peepmatic/examples/redundant-bor.png create mode 100644 cranelift/peepmatic/examples/simple.peepmatic create mode 100644 cranelift/peepmatic/src/ast.rs create mode 100644 cranelift/peepmatic/src/automatize.rs create mode 100644 cranelift/peepmatic/src/dot_fmt.rs create mode 100755 cranelift/peepmatic/src/lib.rs create mode 100644 cranelift/peepmatic/src/linear_passes.rs create mode 100644 cranelift/peepmatic/src/linearize.rs create mode 100644 cranelift/peepmatic/src/parser.rs create mode 100644 cranelift/peepmatic/src/traversals.rs create mode 100644 cranelift/peepmatic/src/verify.rs diff --git a/cranelift/peepmatic/Cargo.toml b/cranelift/peepmatic/Cargo.toml new file mode 100644 index 0000000000..843b06a1a2 --- /dev/null +++ b/cranelift/peepmatic/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "peepmatic" +version = "0.1.0" +authors = ["Nick Fitzgerald "] +edition = "2018" + +# 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.1.0", path = "crates/automata", features = ["dot"] } +peepmatic-macro = { version = "0.1.0", path = "crates/macro" } +peepmatic-runtime = { version = "0.1.0", path = "crates/runtime", features = ["construct"] } +wast = "13.0.0" +z3 = { version = "0.5.0", features = ["static-link-z3"] } diff --git a/cranelift/peepmatic/README.md b/cranelift/peepmatic/README.md new file mode 100644 index 0000000000..550f6d310d --- /dev/null +++ b/cranelift/peepmatic/README.md @@ -0,0 +1,423 @@ +
+

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. + +Currently, `peepmatic` is targeting peephole optimizers that operate on +Cranelift's clif intermediate representation. The intended next target is +Cranelift's new backend's "vcode" intermediate representation. Supporting +non-Cranelift targets is not a goal. + +[Cranelift]: https://github.com/bytecodealliance/wasmtime/tree/master/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 othe 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/examples/mul-by-pow2.peepmatic b/cranelift/peepmatic/examples/mul-by-pow2.peepmatic new file mode 100644 index 0000000000..cca78a372f --- /dev/null +++ b/cranelift/peepmatic/examples/mul-by-pow2.peepmatic @@ -0,0 +1,3 @@ +(=> (when (imul $x $C) + (is-power-of-two $C)) + (ishl $x $(log2 $C))) diff --git a/cranelift/peepmatic/examples/preopt.peepmatic b/cranelift/peepmatic/examples/preopt.peepmatic new file mode 100644 index 0000000000..22523f0a01 --- /dev/null +++ b/cranelift/peepmatic/examples/preopt.peepmatic @@ -0,0 +1,193 @@ +;; Apply basic simplifications. +;; +;; This folds constants with arithmetic to form `_imm` instructions, and other +;; minor simplifications. +;; +;; Doesn't apply some simplifications if the native word width (in bytes) is +;; smaller than the controlling type's width of the instruction. This would +;; result in an illegal instruction that would likely be expanded back into an +;; instruction on smaller types with the same initial opcode, creating +;; unnecessary churn. + +;; Binary instructions whose second argument is constant. +(=> (when (iadd $x $C) + (fits-in-native-word $C)) + (iadd_imm $C $x)) +(=> (when (imul $x $C) + (fits-in-native-word $C)) + (imul_imm $C $x)) +(=> (when (sdiv $x $C) + (fits-in-native-word $C)) + (sdiv_imm $C $x)) +(=> (when (udiv $x $C) + (fits-in-native-word $C)) + (udiv_imm $C $x)) +(=> (when (srem $x $C) + (fits-in-native-word $C)) + (srem_imm $C $x)) +(=> (when (urem $x $C) + (fits-in-native-word $C)) + (urem_imm $C $x)) +(=> (when (band $x $C) + (fits-in-native-word $C)) + (band_imm $C $x)) +(=> (when (bor $x $C) + (fits-in-native-word $C)) + (bor_imm $C $x)) +(=> (when (bxor $x $C) + (fits-in-native-word $C)) + (bxor_imm $C $x)) +(=> (when (rotl $x $C) + (fits-in-native-word $C)) + (rotl_imm $C $x)) +(=> (when (rotr $x $C) + (fits-in-native-word $C)) + (rotr_imm $C $x)) +(=> (when (ishl $x $C) + (fits-in-native-word $C)) + (ishl_imm $C $x)) +(=> (when (ushr $x $C) + (fits-in-native-word $C)) + (ushr_imm $C $x)) +(=> (when (sshr $x $C) + (fits-in-native-word $C)) + (sshr_imm $C $x)) +(=> (when (isub $x $C) + (fits-in-native-word $C)) + (iadd_imm $(neg $C) $x)) +(=> (when (ifcmp $x $C) + (fits-in-native-word $C)) + (ifcmp_imm $C $x)) +(=> (when (icmp $cond $x $C) + (fits-in-native-word $C)) + (icmp_imm $cond $C $x)) + +;; Binary instructions whose first operand is constant. +(=> (when (iadd $C $x) + (fits-in-native-word $C)) + (iadd_imm $C $x)) +(=> (when (imul $C $x) + (fits-in-native-word $C)) + (imul_imm $C $x)) +(=> (when (band $C $x) + (fits-in-native-word $C)) + (band_imm $C $x)) +(=> (when (bor $C $x) + (fits-in-native-word $C)) + (bor_imm $C $x)) +(=> (when (bxor $C $x) + (fits-in-native-word $C)) + (bxor_imm $C $x)) +(=> (when (isub $C $x) + (fits-in-native-word $C)) + (irsub_imm $C $x)) + +;; Unary instructions whose operand is constant. +(=> (adjust_sp_down $C) (adjust_sp_down_imm $C)) + +;; Fold `(binop_imm $C1 (binop_imm $C2 $x))` into `(binop_imm $(binop $C2 $C1) $x)`. +(=> (iadd_imm $C1 (iadd_imm $C2 $x)) (iadd_imm $(iadd $C1 $C2) $x)) +(=> (imul_imm $C1 (imul_imm $C2 $x)) (imul_imm $(imul $C1 $C2) $x)) +(=> (bor_imm $C1 (bor_imm $C2 $x)) (bor_imm $(bor $C1 $C2) $x)) +(=> (band_imm $C1 (band_imm $C2 $x)) (band_imm $(band $C1 $C2) $x)) +(=> (bxor_imm $C1 (bxor_imm $C2 $x)) (bxor_imm $(bxor $C1 $C2) $x)) + +;; Remove operations that are no-ops. +(=> (iadd_imm 0 $x) $x) +(=> (imul_imm 1 $x) $x) +(=> (sdiv_imm 1 $x) $x) +(=> (udiv_imm 1 $x) $x) +(=> (bor_imm 0 $x) $x) +(=> (band_imm -1 $x) $x) +(=> (bxor_imm 0 $x) $x) +(=> (rotl_imm 0 $x) $x) +(=> (rotr_imm 0 $x) $x) +(=> (ishl_imm 0 $x) $x) +(=> (ushr_imm 0 $x) $x) +(=> (sshr_imm 0 $x) $x) + +;; Replace with zero. +(=> (imul_imm 0 $x) 0) +(=> (band_imm 0 $x) 0) + +;; Replace with negative 1. +(=> (bor_imm -1 $x) -1) + +;; Transform `[(x << N) >> N]` into a (un)signed-extending move. +;; +;; i16 -> i8 -> i16 +(=> (when (ushr_imm 8 (ishl_imm 8 $x)) + (bit-width $x 16)) + (uextend{i16} (ireduce{i8} $x))) +(=> (when (sshr_imm 8 (ishl_imm 8 $x)) + (bit-width $x 16)) + (sextend{i16} (ireduce{i8} $x))) +;; i32 -> i8 -> i32 +(=> (when (ushr_imm 24 (ishl_imm 24 $x)) + (bit-width $x 32)) + (uextend{i32} (ireduce{i8} $x))) +(=> (when (sshr_imm 24 (ishl_imm 24 $x)) + (bit-width $x 32)) + (sextend{i32} (ireduce{i8} $x))) +;; i32 -> i16 -> i32 +(=> (when (ushr_imm 16 (ishl_imm 16 $x)) + (bit-width $x 32)) + (uextend{i32} (ireduce{i16} $x))) +(=> (when (sshr_imm 16 (ishl_imm 16 $x)) + (bit-width $x 32)) + (sextend{i32} (ireduce{i16} $x))) +;; i64 -> i8 -> i64 +(=> (when (ushr_imm 56 (ishl_imm 56 $x)) + (bit-width $x 64)) + (uextend{i64} (ireduce{i8} $x))) +(=> (when (sshr_imm 56 (ishl_imm 56 $x)) + (bit-width $x 64)) + (sextend{i64} (ireduce{i8} $x))) +;; i64 -> i16 -> i64 +(=> (when (ushr_imm 48 (ishl_imm 48 $x)) + (bit-width $x 64)) + (uextend{i64} (ireduce{i16} $x))) +(=> (when (sshr_imm 48 (ishl_imm 48 $x)) + (bit-width $x 64)) + (sextend{i64} (ireduce{i16} $x))) +;; i64 -> i32 -> i64 +(=> (when (ushr_imm 32 (ishl_imm 32 $x)) + (bit-width $x 64)) + (uextend{i64} (ireduce{i32} $x))) +(=> (when (sshr_imm 32 (ishl_imm 32 $x)) + (bit-width $x 64)) + (sextend{i64} (ireduce{i32} $x))) + +;; Fold away redundant `bint` instructions that accept both integer and boolean +;; arguments. +(=> (select (bint $x) $y $z) (select $x $y $z)) +(=> (brz (bint $x)) (brz $x)) +(=> (brnz (bint $x)) (brnz $x)) +(=> (trapz (bint $x)) (trapz $x)) +(=> (trapnz (bint $x)) (trapnz $x)) + +;; Fold comparisons into branch operations when possible. +;; +;; This matches against operations which compare against zero, then use the +;; result in a `brz` or `brnz` branch. It folds those two operations into a +;; single `brz` or `brnz`. +(=> (brnz (icmp_imm ne 0 $x)) (brnz $x)) +(=> (brz (icmp_imm ne 0 $x)) (brz $x)) +(=> (brnz (icmp_imm eq 0 $x)) (brz $x)) +(=> (brz (icmp_imm eq 0 $x)) (brnz $x)) + +;; Division and remainder by constants. +;; +;; Note that this section is incomplete, and a bunch of related optimizations +;; are still hand-coded in `simple_preopt.rs`. + +;; (Division by one is handled above.) + +;; Remainder by one is zero. +(=> (urem_imm 1 $x) 0) +(=> (srem_imm 1 $x) 0) + +;; Division by a power of two -> shift right. +(=> (when (udiv_imm $C $x) + (is-power-of-two $C)) + (ushr_imm $(log2 $C) $x)) diff --git a/cranelift/peepmatic/examples/redundant-bor.peepmatic b/cranelift/peepmatic/examples/redundant-bor.peepmatic new file mode 100644 index 0000000000..d8d6f4a144 --- /dev/null +++ b/cranelift/peepmatic/examples/redundant-bor.peepmatic @@ -0,0 +1,13 @@ +;; 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 new file mode 100644 index 0000000000000000000000000000000000000000..dab873e6cfec627af608c2d74acd0af76d80fde0 GIT binary patch literal 225727 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¥h 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%0AHVd!V>- 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≧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 z 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 gJO2MUbK9 literal 0 HcmV?d00001 diff --git a/cranelift/peepmatic/examples/simple.peepmatic b/cranelift/peepmatic/examples/simple.peepmatic new file mode 100644 index 0000000000..a90e0c1a69 --- /dev/null +++ b/cranelift/peepmatic/examples/simple.peepmatic @@ -0,0 +1,11 @@ +(=> (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 new file mode 100644 index 0000000000..daac871553 --- /dev/null +++ b/cranelift/peepmatic/src/ast.rs @@ -0,0 +1,508 @@ +//! 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::{ + operator::{Operator, UnquoteOperator}, + r#type::{BitWidth, Type}, +}; +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> { + /// A reference to an `Optimizations`. + Optimizations(&'a Optimizations<'a>), + + /// A reference to an `Optimization`. + Optimization(&'a Optimization<'a>), + + /// A reference to an `Lhs`. + Lhs(&'a Lhs<'a>), + + /// A reference to an `Rhs`. + Rhs(&'a Rhs<'a>), + + /// A reference to a `Pattern`. + Pattern(&'a Pattern<'a>), + + /// A reference to a `Precondition`. + Precondition(&'a Precondition<'a>), + + /// A reference to a `ConstraintOperand`. + ConstraintOperand(&'a ConstraintOperand<'a>), + + /// A reference to a `ValueLiteral`. + ValueLiteral(&'a ValueLiteral<'a>), + + /// A reference to a `Constant`. + Constant(&'a Constant<'a>), + + /// A reference to a `PatternOperation`. + PatternOperation(&'a Operation<'a, Pattern<'a>>), + + /// A reference to a `Variable`. + Variable(&'a Variable<'a>), + + /// A reference to an `Integer`. + Integer(&'a Integer<'a>), + + /// A reference to a `Boolean`. + Boolean(&'a Boolean<'a>), + + /// A reference to a `ConditionCode`. + ConditionCode(&'a ConditionCode<'a>), + + /// A reference to an `Unquote`. + Unquote(&'a Unquote<'a>), + + /// A reference to an `RhsOperation`. + RhsOperation(&'a Operation<'a, Rhs<'a>>), +} + +impl<'a, 'b> ChildNodes<'a, 'b> for DynAstRef<'a> { + 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>: 'a + ChildNodes<'a, 'a> + Span +where + DynAstRef<'a>: From<&'a Self>, +{ +} + +impl<'a, T> Ast<'a> for T +where + T: 'a + ?Sized + ChildNodes<'a, 'a> + Span, + DynAstRef<'a>: From<&'a Self>, +{ +} + +/// Enumerate the child AST nodes of a given node. +pub trait ChildNodes<'a, 'b> { + /// 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> { + /// 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> { + /// 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>, + + /// The new sequence of instructions to replace an old sequence that matches + /// the left-hand side with. + pub rhs: Rhs<'a>, +} + +/// 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> { + /// 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>, + + /// 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> { + /// A specific value. These are written as `1234` or `0x1234` or `true` or + /// `false`. + ValueLiteral(ValueLiteral<'a>), + + /// A constant that matches any constant value. This subsumes value + /// patterns. These are upper-case identifiers like `$C`. + Constant(Constant<'a>), + + /// An operation pattern with zero or more operand patterns. These are + /// s-expressions like `(iadd $x $y)`. + Operation(Operation<'a, Pattern<'a>>), + + /// A variable that matches any kind of subexpression. This subsumes all + /// other patterns. These are lower-case identifiers like `$x`. + Variable(Variable<'a>), +} + +/// An integer or boolean value literal. +#[derive(Debug, Ast)] +pub enum ValueLiteral<'a> { + /// An integer value. + Integer(Integer<'a>), + + /// A boolean value: `true` or `false`. + Boolean(Boolean<'a>), + + /// A condition code: `eq`, `ne`, etc... + ConditionCode(ConditionCode<'a>), +} + +/// An integer literal. +#[derive(Debug, PartialEq, Eq, Ast)] +pub struct Integer<'a> { + /// 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 ()>, +} + +impl Hash for Integer<'_> { + 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> { + /// 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 ()>, +} + +impl Hash for Boolean<'_> { + 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> { + /// 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 ()>, +} + +/// A symbolic constant. +/// +/// These are identifiers containing uppercase letters: `$C`, `$MY-CONST`, +/// `$CONSTANT1`. +#[derive(Debug, Ast)] +pub struct Constant<'a> { + /// Where this `Constant` was defined. + #[peepmatic(skip_child)] + pub span: wast::Span, + + /// This constant's identifier. + #[peepmatic(skip_child)] + pub id: Id<'a>, +} + +/// 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> { + /// Where this `Variable` was defined. + #[peepmatic(skip_child)] + pub span: wast::Span, + + /// This variable's identifier. + #[peepmatic(skip_child)] + pub id: Id<'a>, +} + +/// An operation with an operator, and operands of type `T`. +#[derive(Debug, Ast)] +#[peepmatic(no_into_dyn_node)] +pub struct Operation<'a, T> +where + T: 'a + Ast<'a>, + DynAstRef<'a>: From<&'a T>, +{ + /// 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: Operator, + + /// 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> From<&'a Operation<'a, Pattern<'a>>> for DynAstRef<'a> { + #[inline] + fn from(o: &'a Operation<'a, Pattern<'a>>) -> DynAstRef<'a> { + DynAstRef::PatternOperation(o) + } +} + +impl<'a> From<&'a Operation<'a, Rhs<'a>>> for DynAstRef<'a> { + #[inline] + fn from(o: &'a Operation<'a, Rhs<'a>>) -> DynAstRef<'a> { + 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> { + /// 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>, +} + +/// 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> { + /// A value literal operand. + ValueLiteral(ValueLiteral<'a>), + + /// A constant operand. + Constant(Constant<'a>), + + /// A variable operand. + Variable(Variable<'a>), +} + +/// 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> { + /// A value literal right-hand side. + ValueLiteral(ValueLiteral<'a>), + + /// A constant right-hand side (the constant must have been matched and + /// bound in the left-hand side's pattern). + Constant(Constant<'a>), + + /// A variable right-hand side (the variable must have been matched and + /// bound in the left-hand side's pattern). + Variable(Variable<'a>), + + /// 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>), + + /// A compound right-hand side consisting of an operation and subsequent + /// right-hand side operands. + Operation(Operation<'a, Rhs<'a>>), +} + +/// 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> { + /// 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>, +} diff --git a/cranelift/peepmatic/src/automatize.rs b/cranelift/peepmatic/src/automatize.rs new file mode 100644 index 0000000000..3310bef118 --- /dev/null +++ b/cranelift/peepmatic/src/automatize.rs @@ -0,0 +1,31 @@ +//! Compile a set of linear optimizations into an automaton. + +use peepmatic_automata::{Automaton, Builder}; +use peepmatic_runtime::linear; + +/// Construct an automaton from a set of linear optimizations. +pub fn automatize( + opts: &linear::Optimizations, +) -> Automaton, linear::MatchOp, Vec> { + debug_assert!(crate::linear_passes::is_sorted_lexicographically(opts)); + + let mut builder = Builder::, linear::MatchOp, Vec>::new(); + + for opt in &opts.optimizations { + let mut insertion = builder.insert(); + for inc in &opt.increments { + // Ensure that this state's associated data is this increment's + // match operation. + if let Some(op) = insertion.get_state_data() { + assert_eq!(*op, inc.operation); + } else { + insertion.set_state_data(inc.operation); + } + + insertion.next(inc.expected, inc.actions.clone()); + } + insertion.finish(); + } + + builder.finish() +} diff --git a/cranelift/peepmatic/src/dot_fmt.rs b/cranelift/peepmatic/src/dot_fmt.rs new file mode 100644 index 0000000000..a2c75de02c --- /dev/null +++ b/cranelift/peepmatic/src/dot_fmt.rs @@ -0,0 +1,142 @@ +//! 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, + operator::Operator, + paths::{PathId, PathInterner}, +}; +use std::convert::TryFrom; +use std::io::{self, Write}; + +#[derive(Debug)] +pub(crate) struct PeepholeDotFmt<'a>(pub(crate) &'a PathInterner, pub(crate) &'a IntegerInterner); + +impl DotFmt, linear::MatchOp, Vec> for PeepholeDotFmt<'_> { + fn fmt_transition( + &self, + w: &mut impl Write, + from: Option<&linear::MatchOp>, + input: &Option, + _to: Option<&linear::MatchOp>, + ) -> io::Result<()> { + let from = from.expect("we should have match op for every state"); + if let Some(x) = input { + match from { + linear::MatchOp::Opcode { .. } => { + let opcode = + Operator::try_from(*x).expect("we shouldn't generate non-opcode edges"); + write!(w, "{}", opcode) + } + linear::MatchOp::ConditionCode { .. } => { + let cc = + ConditionCode::try_from(*x).expect("we shouldn't generate non-CC edges"); + write!(w, "{}", cc) + } + linear::MatchOp::IntegerValue { .. } => { + let x = self.1.lookup(IntegerId(*x)); + write!(w, "{}", x) + } + _ => write!(w, "{}", x), + } + } else { + write!(w, "(else)") + } + } + + fn fmt_state(&self, w: &mut impl Write, op: &linear::MatchOp) -> io::Result<()> { + use linear::MatchOp::*; + + write!(w, r#""#)?; + + let p = p(self.0); + match op { + Opcode { path } => write!(w, "opcode @ {}", p(path))?, + IsConst { path } => write!(w, "is-const? @ {}", p(path))?, + IsPowerOfTwo { path } => write!(w, "is-power-of-two? @ {}", p(path))?, + BitWidth { path } => write!(w, "bit-width @ {}", p(path))?, + FitsInNativeWord { path } => write!(w, "fits-in-native-word @ {}", p(path))?, + Eq { path_a, path_b } => write!(w, "{} == {}", p(path_a), p(path_b))?, + IntegerValue { path } => write!(w, "integer-value @ {}", p(path))?, + BooleanValue { path } => write!(w, "boolean-value @ {}", p(path))?, + ConditionCode { path } => write!(w, "condition-code @ {}", p(path))?, + Nop => write!(w, "nop")?, + } + + writeln!(w, "") + } + + fn fmt_output(&self, w: &mut impl Write, actions: &Vec) -> io::Result<()> { + use linear::Action::*; + + if actions.is_empty() { + return writeln!(w, "(no output)"); + } + + write!(w, r#""#)?; + + let p = p(self.0); + + for a in actions { + match a { + GetLhs { path } => write!(w, "get-lhs @ {}
", p(path))?, + 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.1.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, "
") + } +} + +fn p<'a>(paths: &'a PathInterner) -> impl Fn(&PathId) -> String + 'a { + move |path: &PathId| { + let mut s = vec![]; + for b in paths.lookup(*path).0 { + s.push(b.to_string()); + } + s.join(".") + } +} diff --git a/cranelift/peepmatic/src/lib.rs b/cranelift/peepmatic/src/lib.rs new file mode 100755 index 0000000000..0cf4147db8 --- /dev/null +++ b/cranelift/peepmatic/src/lib.rs @@ -0,0 +1,165 @@ +/*! + +`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 std::fs; +use std::path::Path; + +/// Compile the given DSL file into a compact peephole optimizations automaton! +/// +/// ## Example +/// +/// ```no_run +/// # 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 { + 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 +/// +/// ```no_run +/// # 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 { + 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 paths = opts.paths; + let integers = opts.integers; + + if let Ok(path) = std::env::var("PEEPMATIC_DOT") { + let f = dot_fmt::PeepholeDotFmt(&paths, &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 { + paths, + integers, + automata, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + 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("examples/preopt.peepmatic"); + } +} diff --git a/cranelift/peepmatic/src/linear_passes.rs b/cranelift/peepmatic/src/linear_passes.rs new file mode 100644 index 0000000000..cac1ca3d19 --- /dev/null +++ b/cranelift/peepmatic/src/linear_passes.rs @@ -0,0 +1,444 @@ +//! Passes over the linear IR. + +use peepmatic_runtime::{ + linear, + paths::{PathId, PathInterner}, +}; +use std::cmp::Ordering; + +/// 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) { + let linear::Optimizations { + ref mut optimizations, + ref paths, + .. + } = opts; + + // NB: we *cannot* use an unstable sort here, because we want deterministic + // compilation of optimizations to automata. + optimizations.sort_by(|a, b| compare_optimization_generality(paths, a, b)); + 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) { + let linear::Optimizations { + ref mut optimizations, + ref paths, + .. + } = opts; + + // NB: we *cannot* use an unstable sort here, same as above. + optimizations + .sort_by(|a, b| compare_optimizations(paths, a, b, |a_len, b_len| a_len.cmp(&b_len))); +} + +fn compare_optimizations( + paths: &PathInterner, + a: &linear::Optimization, + b: &linear::Optimization, + compare_lengths: impl Fn(usize, usize) -> Ordering, +) -> Ordering { + for (a, b) in a.increments.iter().zip(b.increments.iter()) { + let c = compare_match_op_generality(paths, a.operation, b.operation); + if c != Ordering::Equal { + return c; + } + + let c = a.expected.cmp(&b.expected).reverse(); + if c != Ordering::Equal { + return c; + } + } + + compare_lengths(a.increments.len(), b.increments.len()) +} + +fn compare_optimization_generality( + paths: &PathInterner, + a: &linear::Optimization, + b: &linear::Optimization, +) -> Ordering { + compare_optimizations(paths, 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( + paths: &PathInterner, + a: linear::MatchOp, + b: linear::MatchOp, +) -> Ordering { + use linear::MatchOp::*; + match (a, b) { + (Opcode { path: a }, Opcode { path: b }) => compare_paths(paths, a, b), + (Opcode { .. }, _) => Ordering::Less, + (_, Opcode { .. }) => Ordering::Greater, + + (IntegerValue { path: a }, IntegerValue { path: b }) => compare_paths(paths, a, b), + (IntegerValue { .. }, _) => Ordering::Less, + (_, IntegerValue { .. }) => Ordering::Greater, + + (BooleanValue { path: a }, BooleanValue { path: b }) => compare_paths(paths, a, b), + (BooleanValue { .. }, _) => Ordering::Less, + (_, BooleanValue { .. }) => Ordering::Greater, + + (ConditionCode { path: a }, ConditionCode { path: b }) => compare_paths(paths, a, b), + (ConditionCode { .. }, _) => Ordering::Less, + (_, ConditionCode { .. }) => Ordering::Greater, + + (IsConst { path: a }, IsConst { path: b }) => compare_paths(paths, a, b), + (IsConst { .. }, _) => Ordering::Less, + (_, IsConst { .. }) => Ordering::Greater, + + ( + Eq { + path_a: pa1, + path_b: pb1, + }, + Eq { + path_a: pa2, + path_b: pb2, + }, + ) => compare_paths(paths, pa1, pa2).then(compare_paths(paths, pb1, pb2)), + (Eq { .. }, _) => Ordering::Less, + (_, Eq { .. }) => Ordering::Greater, + + (IsPowerOfTwo { path: a }, IsPowerOfTwo { path: b }) => compare_paths(paths, a, b), + (IsPowerOfTwo { .. }, _) => Ordering::Less, + (_, IsPowerOfTwo { .. }) => Ordering::Greater, + + (BitWidth { path: a }, BitWidth { path: b }) => compare_paths(paths, a, b), + (BitWidth { .. }, _) => Ordering::Less, + (_, BitWidth { .. }) => Ordering::Greater, + + (FitsInNativeWord { path: a }, FitsInNativeWord { path: b }) => compare_paths(paths, a, b), + (FitsInNativeWord { .. }, _) => Ordering::Less, + (_, FitsInNativeWord { .. }) => Ordering::Greater, + + (Nop, Nop) => Ordering::Equal, + } +} + +fn compare_paths(paths: &PathInterner, a: PathId, b: PathId) -> Ordering { + if a == b { + Ordering::Equal + } else { + let a = paths.lookup(a); + let b = paths.lookup(b); + a.0.cmp(&b.0) + } +} + +/// Are the given optimizations sorted from least to most general? +pub(crate) fn is_sorted_by_generality(opts: &linear::Optimizations) -> bool { + opts.optimizations + .windows(2) + .all(|w| compare_optimization_generality(&opts.paths, &w[0], &w[1]) <= Ordering::Equal) +} + +/// Are the given optimizations sorted lexicographically? +pub(crate) fn is_sorted_lexicographically(opts: &linear::Optimizations) -> bool { + opts.optimizations.windows(2).all(|w| { + compare_optimizations(&opts.paths, &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) { + assert!(!opts.optimizations.is_empty()); + + let mut prefix = vec![]; + + for opt in &mut opts.optimizations { + assert!(!opt.increments.is_empty()); + + let mut old_increments = opt.increments.iter().peekable(); + let mut new_increments = vec![]; + + for (last_op, last_expected) in &prefix { + match old_increments.peek() { + None => { + break; + } + Some(inc) if *last_op == inc.operation => { + let inc = old_increments.next().unwrap(); + new_increments.push(inc.clone()); + if inc.expected != *last_expected { + break; + } + } + Some(_) => { + new_increments.push(linear::Increment { + operation: *last_op, + expected: None, + actions: vec![], + }); + if last_expected.is_some() { + break; + } + } + } + } + + new_increments.extend(old_increments.cloned()); + assert!(new_increments.len() >= opt.increments.len()); + opt.increments = new_increments; + + prefix.clear(); + prefix.extend( + opt.increments + .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 increment. +pub fn remove_unnecessary_nops(opts: &mut linear::Optimizations) { + for opt in &mut opts.optimizations { + if opt.increments.len() < 2 { + debug_assert!(!opt.increments.is_empty()); + continue; + } + + for i in (1..opt.increments.len()).rev() { + if let linear::MatchOp::Nop = opt.increments[i].operation { + let nop = opt.increments.remove(i); + opt.increments[i - 1].actions.extend(nop.actions); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ast::*; + use linear::MatchOp::*; + use peepmatic_runtime::{operator::Operator, paths::*}; + + macro_rules! sorts_to { + ($test_name:ident, $source:expr, $make_expected:expr) => { + #[test] + 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 linear::Optimizations { + mut paths, + mut integers, + optimizations, + } = opts; + + let actual: Vec> = optimizations + .iter() + .map(|o| { + o.increments + .iter() + .map(|i| (i.operation, i.expected)) + .collect() + }) + .collect(); + + let mut p = |p: &[u8]| paths.intern(Path::new(&p)); + let mut i = |i: u64| Some(integers.intern(i).into()); + let expected = $make_expected(&mut p, &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) +", + |p: &mut dyn FnMut(&[u8]) -> PathId, i: &mut dyn FnMut(u64) -> Option| vec![ + vec![ + (Opcode { path: p(&[0]) }, Some(Operator::Iadd as _)), + (Nop, None), + (Opcode { path: p(&[0, 1]) }, Some(Operator::Iadd as _)), + (Nop, None), + (Nop, None), + ], + vec![ + (Opcode { path: p(&[0]) }, Some(Operator::Iadd as _)), + (Nop, None), + (IntegerValue { path: p(&[0, 1]) }, i(42)) + ], + vec![ + (Opcode { path: p(&[0]) }, Some(Operator::Iadd as _)), + (Nop, None), + (IsConst { path: p(&[0, 1]) }, Some(1)), + (IsPowerOfTwo { path: p(&[0, 1]) }, Some(1)) + ], + vec![ + (Opcode { path: p(&[0]) }, Some(Operator::Iadd as _)), + (Nop, None), + (IsConst { path: p(&[0, 1]) }, Some(1)), + (BitWidth { path: p(&[0, 0]) }, Some(32)) + ], + vec![ + (Opcode { path: p(&[0]) }, Some(Operator::Iadd as _)), + (Nop, None), + (IsConst { path: p(&[0, 1]) }, Some(1)) + ], + vec![ + (Opcode { path: p(&[0]) }, Some(Operator::Iadd as _)), + (Nop, None), + ( + Eq { + path_a: p(&[0, 1]), + path_b: p(&[0, 0]), + }, + Some(1) + ) + ], + vec![ + (Opcode { path: p(&[0]) }, Some(Operator::Iadd as _)), + (Nop, None), + (Nop, None), + ], + vec![(Nop, None)] + ] + ); + + 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)) +", + |p: &mut dyn FnMut(&[u8]) -> PathId, i: &mut dyn FnMut(u64) -> Option| vec![ + vec![ + (Opcode { path: p(&[0]) }, Some(Operator::Imul as _)), + (IntegerValue { path: p(&[0, 0]) }, i(2)), + (Nop, None) + ], + vec![ + (Opcode { path: p(&[0]) }, Some(Operator::Imul as _)), + (IntegerValue { path: p(&[0, 0]) }, i(1)), + (Nop, None) + ], + vec![ + (Opcode { path: p(&[0]) }, Some(Operator::Imul as _)), + (Nop, None), + (IntegerValue { path: p(&[0, 1]) }, i(2)) + ], + vec![ + (Opcode { path: p(&[0]) }, Some(Operator::Imul as _)), + (Nop, None), + (IntegerValue { path: p(&[0, 1]) }, i(1)) + ], + vec![ + (Opcode { path: p(&[0]) }, Some(Operator::Iadd as _)), + (IntegerValue { path: p(&[0, 0]) }, i(0)), + (Nop, None) + ], + vec![ + (Opcode { path: p(&[0]) }, Some(Operator::Iadd as _)), + (Nop, None), + (IntegerValue { path: p(&[0, 1]) }, i(0)) + ] + ] + ); +} diff --git a/cranelift/peepmatic/src/linearize.rs b/cranelift/peepmatic/src/linearize.rs new file mode 100644 index 0000000000..75205c93b9 --- /dev/null +++ b/cranelift/peepmatic/src/linearize.rs @@ -0,0 +1,831 @@ +//! 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 we should get the following linear chain of "increments": +//! +//! ```ignore +//! [ +//! // ( Match Operation, Expected Value, Actions ) +//! ( Opcode@0, imul, [$x = GetLhs@0.0, $C = GetLhs@0.1, ...] ), +//! ( IsConst(C), true, [] ), +//! ( IsPowerOfTwo(C), true, [] ), +//! ] +//! ``` +//! +//! Each increment will essentially become a state and a transition out of that +//! state in the final automata, along with the actions to perform when taking +//! that transition. The actions record the scope of matches from the left-hand +//! side and also incrementally build the right-hand side's instructions. (Note +//! that we've elided the actions that build up the optimization's right-hand +//! side in this example.) +//! +//! ## General Principles +//! +//! Here are the general principles that linearization should adhere to: +//! +//! * Actions should be pushed as early in the optimization's increment chain as +//! they can be. This means the tail has fewer side effects, and is therefore +//! more likely to be share-able with other optimizations in the automata that +//! we build. +//! +//! * RHS actions cannot reference matches from the LHS until they've been +//! defined. And finally, an RHS operation's operands must be defined before +//! the RHS operation itself. In general, definitions must come before uses! +//! +//! * Shorter increment 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 increment's 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 +//! pre-order 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. See `PatternPreOrder` 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 `LhsIdToPath` 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::Dfs; +use peepmatic_runtime::{ + integer_interner::IntegerInterner, + linear, + paths::{Path, PathId, PathInterner}, +}; +use std::collections::BTreeMap; +use wast::Id; + +/// Translate the given AST optimizations into linear optimizations. +pub fn linearize(opts: &Optimizations) -> linear::Optimizations { + let mut optimizations = vec![]; + let mut paths = PathInterner::new(); + let mut integers = IntegerInterner::new(); + for opt in &opts.optimizations { + let lin_opt = linearize_optimization(&mut paths, &mut integers, opt); + optimizations.push(lin_opt); + } + linear::Optimizations { + optimizations, + paths, + integers, + } +} + +/// Translate an AST optimization into a linear optimization! +fn linearize_optimization( + paths: &mut PathInterner, + integers: &mut IntegerInterner, + opt: &Optimization, +) -> linear::Optimization { + let mut increments: Vec = vec![]; + + let mut lhs_id_to_path = LhsIdToPath::new(); + + // We do a pre-order 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! + let mut patterns = PatternPreOrder::new(&opt.lhs.pattern); + while let Some((path, pattern)) = patterns.next(paths) { + // Create the matching parts of an `Increment` for this part of the + // pattern, without any actions yet. + let (operation, expected) = pattern.to_linear_match_op(integers, &lhs_id_to_path, path); + increments.push(linear::Increment { + operation, + expected, + actions: vec![], + }); + + lhs_id_to_path.remember_path_to_pattern_ids(pattern, path); + + // 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 + // increment 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()) { + increments.push(linear::Increment { + operation: linear::MatchOp::BitWidth { path }, + expected: Some(w as u32), + actions: vec![], + }); + } + } + } + + // Now that we've added all the increments for the LHS pattern, add the + // increments for its preconditions. + for pre in &opt.lhs.preconditions { + increments.push(pre.to_linear_increment(&lhs_id_to_path)); + } + + assert!(!increments.is_empty()); + + // Finally, generate the RHS-building actions and attach them to the first increment. + let mut rhs_builder = RhsBuilder::new(&opt.rhs); + rhs_builder.add_rhs_build_actions(integers, &lhs_id_to_path, &mut increments[0].actions); + + linear::Optimization { increments } +} + +/// 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> { + dfs: Dfs<'a>, +} + +impl<'a> RhsPostOrder<'a> { + fn new(rhs: &'a Rhs<'a>) -> Self { + Self { dfs: Dfs::new(rhs) } + } +} + +impl<'a> Iterator for RhsPostOrder<'a> { + type Item = &'a Rhs<'a>; + + fn next(&mut self) -> Option<&'a Rhs<'a>> { + use crate::traversals::TraversalEvent as TE; + loop { + match self.dfs.next()? { + (TE::Exit, DynAstRef::Rhs(rhs)) => return Some(rhs), + _ => continue, + } + } + } +} + +/// A pre-order, depth-first traversal of left-hand side patterns. +/// +/// Keeps track of the path to each pattern, and yields it along side the +/// pattern AST node. +struct PatternPreOrder<'a> { + last_child: Option, + path: Vec, + dfs: Dfs<'a>, +} + +impl<'a> PatternPreOrder<'a> { + fn new(pattern: &'a Pattern<'a>) -> Self { + Self { + last_child: None, + path: vec![], + dfs: Dfs::new(pattern), + } + } + + fn next(&mut self, paths: &mut PathInterner) -> Option<(PathId, &'a Pattern<'a>)> { + use crate::traversals::TraversalEvent as TE; + loop { + match self.dfs.next()? { + (TE::Enter, DynAstRef::Pattern(pattern)) => { + let last_child = self.last_child.take(); + self.path.push(match last_child { + None => 0, + Some(c) => { + assert!( + c < std::u8::MAX, + "operators must have less than or equal u8::MAX arity" + ); + c + 1 + } + }); + let path = paths.intern(Path(&self.path)); + return Some((path, pattern)); + } + (TE::Exit, DynAstRef::Pattern(_)) => { + self.last_child = Some( + self.path + .pop() + .expect("should always have a non-empty path during traversal"), + ); + } + _ => {} + } + } + } +} + +/// A map from left-hand side identifiers to the path in the left-hand side +/// where they first occurred. +struct LhsIdToPath<'a> { + id_to_path: BTreeMap<&'a str, PathId>, +} + +impl<'a> LhsIdToPath<'a> { + /// Construct a new, empty `LhsIdToPath`. + fn new() -> Self { + Self { + id_to_path: Default::default(), + } + } + + /// Have we already seen the given identifier? + fn get_first_occurrence(&self, id: &Id) -> Option { + self.id_to_path.get(id.name()).copied() + } + + /// Get the path within the left-hand side pattern where we first saw the + /// given AST id. + /// + /// ## Panics + /// + /// Panics if the given AST id has not already been canonicalized. + fn unwrap_first_occurrence(&self, id: &Id) -> PathId { + self.id_to_path[id.name()] + } + + /// Remember the path to any LHS ids used in the given pattern. + fn remember_path_to_pattern_ids(&mut self, pattern: &'a Pattern<'a>, path: PathId) { + 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_path.entry(id.name()).or_insert(path); + } + _ => {} + } + } +} + +/// An `RhsBuilder` emits the actions for building the right-hand side +/// instructions. +struct RhsBuilder<'a> { + // 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>, + + // 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> RhsBuilder<'a> { + /// Create a new builder for the given right-hand side. + fn new(rhs: &'a Rhs<'a>) -> 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_id_to_path: &LhsIdToPath, + actions: &mut Vec, + ) { + while let Some(rhs) = self.rhs_post_order.next() { + actions.push(self.rhs_to_linear_action(integers, lhs_id_to_path, rhs)); + let id = linear::RhsId(self.rhs_span_to_id.len() as u32); + self.rhs_span_to_id.insert(rhs.span(), id); + } + } + + fn rhs_to_linear_action( + &self, + integers: &mut IntegerInterner, + lhs_id_to_path: &LhsIdToPath, + 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 path = lhs_id_to_path.unwrap_first_occurrence(id); + linear::Action::GetLhs { path } + } + 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<'_> { + /// Convert this precondition into a `linear::Increment`. + fn to_linear_increment(&self, lhs_id_to_path: &LhsIdToPath) -> linear::Increment { + match self.constraint { + Constraint::IsPowerOfTwo => { + let id = match &self.operands[0] { + ConstraintOperand::Constant(Constant { id, .. }) => id, + _ => unreachable!("checked in verification"), + }; + let path = lhs_id_to_path.unwrap_first_occurrence(&id); + linear::Increment { + operation: linear::MatchOp::IsPowerOfTwo { path }, + expected: Some(1), + actions: vec![], + } + } + Constraint::BitWidth => { + let id = match &self.operands[0] { + ConstraintOperand::Constant(Constant { id, .. }) + | ConstraintOperand::Variable(Variable { id, .. }) => id, + _ => unreachable!("checked in verification"), + }; + let path = lhs_id_to_path.unwrap_first_occurrence(&id); + + let width = match &self.operands[1] { + ConstraintOperand::ValueLiteral(ValueLiteral::Integer(Integer { + value, + .. + })) => *value, + _ => unreachable!("checked in verification"), + }; + debug_assert!(width <= 128); + debug_assert!((width as u8).is_power_of_two()); + + linear::Increment { + operation: linear::MatchOp::BitWidth { path }, + expected: Some(width as u32), + actions: vec![], + } + } + Constraint::FitsInNativeWord => { + let id = match &self.operands[0] { + ConstraintOperand::Constant(Constant { id, .. }) + | ConstraintOperand::Variable(Variable { id, .. }) => id, + _ => unreachable!("checked in verification"), + }; + let path = lhs_id_to_path.unwrap_first_occurrence(&id); + linear::Increment { + operation: linear::MatchOp::FitsInNativeWord { path }, + expected: Some(1), + actions: vec![], + } + } + } + } +} + +impl Pattern<'_> { + /// 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_id_to_path: &LhsIdToPath, + path: PathId, + ) -> (linear::MatchOp, Option) { + match self { + Pattern::ValueLiteral(ValueLiteral::Integer(Integer { value, .. })) => ( + linear::MatchOp::IntegerValue { path }, + Some(integers.intern(*value as u64).into()), + ), + Pattern::ValueLiteral(ValueLiteral::Boolean(Boolean { value, .. })) => { + (linear::MatchOp::BooleanValue { path }, Some(*value as u32)) + } + Pattern::ValueLiteral(ValueLiteral::ConditionCode(ConditionCode { cc, .. })) => { + (linear::MatchOp::ConditionCode { path }, Some(*cc as u32)) + } + Pattern::Constant(Constant { id, .. }) => { + if let Some(path_b) = lhs_id_to_path.get_first_occurrence(id) { + debug_assert!(path != path_b); + ( + linear::MatchOp::Eq { + path_a: path, + path_b, + }, + Some(1), + ) + } else { + (linear::MatchOp::IsConst { path }, Some(1)) + } + } + Pattern::Variable(Variable { id, .. }) => { + if let Some(path_b) = lhs_id_to_path.get_first_occurrence(id) { + debug_assert!(path != path_b); + ( + linear::MatchOp::Eq { + path_a: path, + path_b, + }, + Some(1), + ) + } else { + (linear::MatchOp::Nop, None) + } + } + Pattern::Operation(op) => (linear::MatchOp::Opcode { path }, Some(op.operator as u32)), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use peepmatic_runtime::{ + integer_interner::IntegerId, + linear::{Action::*, MatchOp::*}, + operator::Operator, + r#type::{BitWidth, Kind, Type}, + }; + + 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 paths = PathInterner::new(); + let mut p = |p: &[u8]| paths.intern(Path::new(&p)); + + let mut integers = IntegerInterner::new(); + let mut i = |i: u64| integers.intern(i); + + #[allow(unused_variables)] + let expected = $make_expected(&mut p, &mut i); + dbg!(&expected); + + let actual = linearize_optimization(&mut paths, &mut integers, &opts.optimizations[0]); + dbg!(&actual); + + assert_eq!(expected, actual); + } + }; + } + + linearizes_to!( + mul_by_pow2_into_shift, + " +(=> (when (imul $x $C) + (is-power-of-two $C)) + (ishl $x $C)) + ", + |p: &mut dyn FnMut(&[u8]) -> PathId, i: &mut dyn FnMut(u64) -> IntegerId| { + linear::Optimization { + increments: vec![ + linear::Increment { + operation: Opcode { path: p(&[0]) }, + expected: Some(Operator::Imul as _), + actions: vec![ + GetLhs { path: p(&[0, 0]) }, + GetLhs { path: p(&[0, 1]) }, + MakeBinaryInst { + operator: Operator::Ishl, + r#type: Type { + kind: Kind::Int, + bit_width: BitWidth::Polymorphic, + }, + operands: [linear::RhsId(0), linear::RhsId(1)], + }, + ], + }, + linear::Increment { + operation: Nop, + expected: None, + actions: vec![], + }, + linear::Increment { + operation: IsConst { path: p(&[0, 1]) }, + expected: Some(1), + actions: vec![], + }, + linear::Increment { + operation: IsPowerOfTwo { path: p(&[0, 1]) }, + expected: Some(1), + actions: vec![], + }, + ], + } + }, + ); + + linearizes_to!( + variable_pattern_id_optimization, + "(=> $x $x)", + |p: &mut dyn FnMut(&[u8]) -> PathId, i: &mut dyn FnMut(u64) -> IntegerId| { + linear::Optimization { + increments: vec![linear::Increment { + operation: Nop, + expected: None, + actions: vec![GetLhs { path: p(&[0]) }], + }], + } + }, + ); + + linearizes_to!( + constant_pattern_id_optimization, + "(=> $C $C)", + |p: &mut dyn FnMut(&[u8]) -> PathId, i: &mut dyn FnMut(u64) -> IntegerId| { + linear::Optimization { + increments: vec![linear::Increment { + operation: IsConst { path: p(&[0]) }, + expected: Some(1), + actions: vec![GetLhs { path: p(&[0]) }], + }], + } + }, + ); + + linearizes_to!( + boolean_literal_id_optimization, + "(=> true true)", + |p: &mut dyn FnMut(&[u8]) -> PathId, i: &mut dyn FnMut(u64) -> IntegerId| { + linear::Optimization { + increments: vec![linear::Increment { + operation: BooleanValue { path: p(&[0]) }, + expected: Some(1), + actions: vec![MakeBooleanConst { + value: true, + bit_width: BitWidth::Polymorphic, + }], + }], + } + }, + ); + + linearizes_to!( + number_literal_id_optimization, + "(=> 5 5)", + |p: &mut dyn FnMut(&[u8]) -> PathId, i: &mut dyn FnMut(u64) -> IntegerId| { + linear::Optimization { + increments: vec![linear::Increment { + operation: IntegerValue { path: p(&[0]) }, + expected: Some(i(5).into()), + actions: vec![MakeIntegerConst { + value: i(5), + bit_width: BitWidth::Polymorphic, + }], + }], + } + }, + ); + + linearizes_to!( + operation_id_optimization, + "(=> (iconst $C) (iconst $C))", + |p: &mut dyn FnMut(&[u8]) -> PathId, i: &mut dyn FnMut(u64) -> IntegerId| { + linear::Optimization { + increments: vec![ + linear::Increment { + operation: Opcode { path: p(&[0]) }, + expected: Some(Operator::Iconst as _), + actions: vec![ + GetLhs { path: p(&[0, 0]) }, + MakeUnaryInst { + operator: Operator::Iconst, + r#type: Type { + kind: Kind::Int, + bit_width: BitWidth::Polymorphic, + }, + operand: linear::RhsId(0), + }, + ], + }, + linear::Increment { + operation: IsConst { path: p(&[0, 0]) }, + expected: Some(1), + actions: vec![], + }, + ], + } + }, + ); + + linearizes_to!( + redundant_bor, + "(=> (bor $x (bor $x $y)) (bor $x $y))", + |p: &mut dyn FnMut(&[u8]) -> PathId, i: &mut dyn FnMut(u64) -> IntegerId| { + linear::Optimization { + increments: vec![ + linear::Increment { + operation: Opcode { path: p(&[0]) }, + expected: Some(Operator::Bor as _), + actions: vec![ + GetLhs { path: p(&[0, 0]) }, + GetLhs { + path: p(&[0, 1, 1]), + }, + MakeBinaryInst { + operator: Operator::Bor, + r#type: Type { + kind: Kind::Int, + bit_width: BitWidth::Polymorphic, + }, + operands: [linear::RhsId(0), linear::RhsId(1)], + }, + ], + }, + linear::Increment { + operation: Nop, + expected: None, + actions: vec![], + }, + linear::Increment { + operation: Opcode { path: p(&[0, 1]) }, + expected: Some(Operator::Bor as _), + actions: vec![], + }, + linear::Increment { + operation: Eq { + path_a: p(&[0, 1, 0]), + path_b: p(&[0, 0]), + }, + expected: Some(1), + actions: vec![], + }, + linear::Increment { + operation: Nop, + expected: None, + actions: vec![], + }, + ], + } + }, + ); + + linearizes_to!( + large_integers, + // u64::MAX + "(=> 18446744073709551615 0)", + |p: &mut dyn FnMut(&[u8]) -> PathId, i: &mut dyn FnMut(u64) -> IntegerId| { + linear::Optimization { + increments: vec![linear::Increment { + operation: IntegerValue { path: p(&[0]) }, + expected: Some(i(std::u64::MAX).into()), + actions: vec![MakeIntegerConst { + value: i(0), + bit_width: BitWidth::Polymorphic, + }], + }], + } + } + ); + + linearizes_to!( + ireduce_with_type_ascription, + "(=> (ireduce{i32} $x) 0)", + |p: &mut dyn FnMut(&[u8]) -> PathId, i: &mut dyn FnMut(u64) -> IntegerId| { + linear::Optimization { + increments: vec![ + linear::Increment { + operation: Opcode { path: p(&[0]) }, + expected: Some(Operator::Ireduce as _), + actions: vec![MakeIntegerConst { + value: i(0), + bit_width: BitWidth::ThirtyTwo, + }], + }, + linear::Increment { + operation: linear::MatchOp::BitWidth { path: p(&[0]) }, + expected: Some(32), + actions: vec![], + }, + linear::Increment { + operation: Nop, + expected: None, + actions: vec![], + }, + ], + } + } + ); +} diff --git a/cranelift/peepmatic/src/parser.rs b/cranelift/peepmatic/src/parser.rs new file mode 100644 index 0000000000..19ad49017c --- /dev/null +++ b/cranelift/peepmatic/src/parser.rs @@ -0,0 +1,932 @@ +/*! + +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> Parse<'a> for Optimizations<'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> Parse<'a> for Optimization<'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> Parse<'a> for Lhs<'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> Parse<'a> for Pattern<'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> Peek for Pattern<'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> Parse<'a> for ValueLiteral<'a> { + 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> Peek for ValueLiteral<'a> { + fn peek(c: Cursor) -> bool { + c.integer().is_some() || Boolean::peek(c) || ConditionCode::peek(c) + } + + fn display() -> &'static str { + "value literal" + } +} + +impl<'a> Parse<'a> for Integer<'a> { + 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> Parse<'a> for Boolean<'a> { + 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> Peek for Boolean<'a> { + fn peek(c: Cursor) -> bool { + ::peek(c) || ::peek(c) + } + + fn display() -> &'static str { + "boolean `true` or `false`" + } +} + +impl<'a> Parse<'a> for ConditionCode<'a> { + 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> Peek for ConditionCode<'a> { + 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> Parse<'a> for Constant<'a> { + 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 }) + } 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> Peek for Constant<'a> { + 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> Parse<'a> for Variable<'a> { + 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 }) + } 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> Peek for Variable<'a> { + 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, T> Parse<'a> for Operation<'a, T> +where + T: 'a + Ast<'a> + Peek + Parse<'a>, + DynAstRef<'a>: From<&'a T>, +{ + fn parse(p: Parser<'a>) -> ParseResult { + 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, T> Peek for Operation<'a, T> +where + T: 'a + Ast<'a>, + DynAstRef<'a>: From<&'a T>, +{ + fn peek(c: Cursor) -> bool { + wast::LParen::peek(c) + } + + fn display() -> &'static str { + "operation" + } +} + +impl<'a> Parse<'a> for Precondition<'a> { + 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, + }) + }) + } +} + +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> Parse<'a> for ConstraintOperand<'a> { + 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> Peek for ConstraintOperand<'a> { + 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> Parse<'a> for Rhs<'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> Peek for Rhs<'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> Parse<'a> for Unquote<'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, + }) + }) + } +} + +impl<'a> Peek for Unquote<'a> { + fn peek(c: Cursor) -> bool { + tok::dollar::peek(c) + } + + fn display() -> &'static str { + "unquote expression" + } +} + +#[cfg(test)] +mod test { + use super::*; + use peepmatic_runtime::operator::Operator; + + 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))", + } + } + 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 new file mode 100644 index 0000000000..5eb4101c37 --- /dev/null +++ b/cranelift/peepmatic/src/traversals.rs @@ -0,0 +1,278 @@ +//! Traversals over the AST. + +use crate::ast::*; + +/// 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> { + stack: Vec<(TraversalEvent, DynAstRef<'a>)>, +} + +impl<'a> Dfs<'a> { + /// 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>)> { + self.stack.last().cloned() + } +} + +impl<'a> Iterator for Dfs<'a> { + type Item = (TraversalEvent, DynAstRef<'a>); + + fn next(&mut self) -> Option<(TraversalEvent, DynAstRef<'a>)> { + 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>(&'b mut Dfs<'a>) + where + 'a: 'b; + + impl<'a, 'b> Extend> for EnqueueChildren<'a, 'b> + where + 'a: 'b, + { + 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(); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + 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 new file mode 100644 index 0000000000..03865458ca --- /dev/null +++ b/cranelift/peepmatic/src/verify.rs @@ -0,0 +1,1433 @@ +//! 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::{ + operator::{Operator, TypingContext as TypingContextTrait}, + r#type::{BitWidth, Kind, Type}, +}; +use std::borrow::Cow; +use std::collections::HashMap; +use std::convert::{TryFrom, TryInto}; +use std::fmt; +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<()> { + 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<()> { + 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 { + 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(Operator, Option), + Precondition(Constraint), + Other(&'static str), + } +} + +pub(crate) struct TypingContext<'a> { + 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>, TypeVar<'a>)>, + integer_literals: Vec<(&'a Integer<'a>, TypeVar<'a>)>, + rhs_operations: Vec<(&'a Operation<'a, Rhs<'a>>, TypeVar<'a>)>, +} + +impl<'a> TypingContext<'a> { + fn new(z3: &'a z3::Context) -> Self { + let type_kind_sort = z3::DatatypeBuilder::new(z3) + .variant("int", &[]) + .variant("bool", &[]) + .variant("cpu_flags", &[]) + .variant("cc", &[]) + .variant("void", &[]) + .finish("TypeKind"); + 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(( + is_int.or(&[&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>(&'b mut TypingContext<'a>) + where + 'a: 'b; + + impl<'a, 'b> Deref for Scope<'a, 'b> + where + 'a: 'b, + { + type Target = TypingContext<'a>; + fn deref(&self) -> &TypingContext<'a> { + self.0 + } + } + + impl<'a, 'b> DerefMut for Scope<'a, 'b> + where + 'a: 'b, + { + fn deref_mut(&mut self) -> &mut TypingContext<'a> { + self.0 + } + } + + impl Drop for Scope<'_, '_> { + fn drop(&mut self) { + self.0.operation_scope.clear(); + } + } + } + + fn remember_boolean_literal(&mut self, b: &'a Boolean<'a>, 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>, 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, Rhs<'a>>, 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(); + 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> TypingContextTrait<'a> for TypingContext<'a> { + 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(( + is_int.or(&[&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)] +pub(crate) struct TypeVar<'a> { + kind: z3::ast::Datatype<'a>, + width: z3::ast::BV<'a>, +} + +fn verify_optimization(z3: &z3::Context, opt: &Optimization) -> VerifyResult<()> { + 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>( + context: &mut TypingContext<'a>, + opt: &'a Optimization<'a>, +) -> VerifyResult<()> { + 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 }))) + | (TE::Enter, DynAstRef::Pattern(Pattern::Variable(Variable { id, span }))) => { + 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(&mut *scope, op.span); + op.operator + .immediate_types(&mut *scope, op.span, &mut operand_types); + op.operator + .param_types(&mut *scope, op.span, &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()), + } + } + + match op.operator { + Operator::Ireduce | Operator::Uextend | Operator::Sextend => { + if 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()); + } + } + _ => {} + } + + match op.operator { + Operator::Uextend | Operator::Sextend => { + context.assert_bit_width_gt(op.span, &result_ty, &operand_types[0]); + } + Operator::Ireduce => { + 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 }))) + | (TE::Enter, DynAstRef::Rhs(Rhs::Variable(Variable { span, id }))) => { + 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(&mut *scope, op.span); + op.operator + .immediate_types(&mut *scope, op.span, &mut operand_types); + op.operator + .param_types(&mut *scope, op.span, &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()), + } + } + + match op.operator { + Operator::Ireduce | Operator::Uextend | Operator::Sextend => { + if 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()); + } + } + _ => {} + } + + match op.operator { + Operator::Uextend | Operator::Sextend => { + context.assert_bit_width_gt(op.span, &result_ty, &operand_types[0]); + } + Operator::Ireduce => { + 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(&mut *scope, unq.span); + unq.operator + .immediate_types(&mut *scope, unq.span, &mut operand_types); + unq.operator + .param_types(&mut *scope, unq.span, &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>( + context: &mut TypingContext<'a>, + pre: &Precondition<'a>, +) -> 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::*; + + 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)" + ); +}