Remove Peepmatic!!!
Peepmatic was an early attempt at a DSL for peephole optimizations, with the idea that maybe sometime in the future we could user it for instruction selection as well. It didn't really pan out, however: * Peepmatic wasn't quite flexible enough, and adding new operators or snippets of code implemented externally in Rust was a bit of a pain. * The performance was never competitive with the hand-written peephole optimizers. It was *very* size efficient, but that came at the cost of run-time efficiency. Everything was table-based and interpreted, rather than generating any Rust code. Ultimately, because of these reasons, we never turned Peepmatic on by default. These days, we just landed the ISLE domain-specific language, and it is better suited than Peepmatic for all the things that Peepmatic was originally designed to do. It is more flexible and easy to integrate with external Rust code. It is has better time efficiency, meeting or even beating hand-written code. I think a small part of the reason why ISLE excels in these things is because its design was informed by Peepmatic's failures. I still plan on continuing Peepmatic's mission to make Cranelift's peephole optimizer passes generated from DSL rewrite rules, but using ISLE instead of Peepmatic. Thank you Peepmatic, rest in peace!
This commit is contained in:
4
.github/labeler.yml
vendored
4
.github/labeler.yml
vendored
@@ -29,10 +29,6 @@
|
||||
"cranelift:area:x64":
|
||||
- "cranelift/codegen/src/isa/x64/**"
|
||||
|
||||
"cranelift:area:peepmatic":
|
||||
- "cranelift/peepmatic/**"
|
||||
- "cranelift/codegen/src/peepmatic.rs"
|
||||
|
||||
"cranelift:docs":
|
||||
- "cranelift/docs/**"
|
||||
|
||||
|
||||
2
.github/subscribe-to-label.json
vendored
2
.github/subscribe-to-label.json
vendored
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"cfallin": ["isle"],
|
||||
"fitzgen": ["cranelift:area:peepmatic", "fuzzing", "isle", "wasmtime:ref-types"],
|
||||
"fitzgen": ["fuzzing", "isle", "wasmtime:ref-types"],
|
||||
"peterhuene": ["wasmtime:api", "wasmtime:c-api"],
|
||||
"kubkon": ["wasi"]
|
||||
}
|
||||
|
||||
30
.github/workflows/main.yml
vendored
30
.github/workflows/main.yml
vendored
@@ -88,8 +88,7 @@ jobs:
|
||||
cargo doc --no-deps --workspace \
|
||||
--exclude wasmtime-cli \
|
||||
--exclude test-programs \
|
||||
--exclude cranelift-codegen-meta \
|
||||
--exclude 'peepmatic*'
|
||||
--exclude cranelift-codegen-meta
|
||||
- run: cargo doc --package cranelift-codegen-meta --document-private-items
|
||||
|
||||
# Assemble the documentation, and always upload it as an artifact for
|
||||
@@ -204,33 +203,6 @@ jobs:
|
||||
- name: Check that the ISLE DSL files are up-to-date
|
||||
run: git diff --exit-code
|
||||
|
||||
rebuild_peephole_optimizers:
|
||||
name: Rebuild Peephole Optimizers
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
- run: rustup update stable && rustup default stable
|
||||
- name: Test `peepmatic`
|
||||
run: cargo test --package 'peepmatic*'
|
||||
- name: Rebuild Peepmatic-based peephole optimizers
|
||||
run: |
|
||||
cargo test \
|
||||
--features 'enable-peepmatic rebuild-peephole-optimizers' \
|
||||
peepmatic
|
||||
working-directory: ./cranelift/codegen
|
||||
- name: Upload rebuilt peephole optimizers
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: peephole-optimizers
|
||||
path: cranelift/codegen/src/preopt.serialized
|
||||
- name: Check that built peephole optimizers are up to date
|
||||
run: git diff --exit-code
|
||||
- name: Test with Peepmatic-based peephole optimizers
|
||||
run: cargo test --features 'enable-peepmatic'
|
||||
working-directory: ./cranelift
|
||||
|
||||
# Perform all tests (debug mode) for `wasmtime`. This runs stable/beta/nightly
|
||||
# channels of Rust as well as macOS/Linux/Windows.
|
||||
test:
|
||||
|
||||
144
Cargo.lock
generated
144
Cargo.lock
generated
@@ -545,16 +545,12 @@ dependencies = [
|
||||
"isle",
|
||||
"log",
|
||||
"miette",
|
||||
"peepmatic",
|
||||
"peepmatic-runtime",
|
||||
"peepmatic-traits",
|
||||
"regalloc",
|
||||
"serde",
|
||||
"sha2",
|
||||
"smallvec",
|
||||
"souper-ir",
|
||||
"target-lexicon",
|
||||
"wast 38.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -730,7 +726,6 @@ dependencies = [
|
||||
"filecheck",
|
||||
"indicatif",
|
||||
"log",
|
||||
"peepmatic-souper",
|
||||
"pretty_env_logger",
|
||||
"rayon",
|
||||
"structopt",
|
||||
@@ -1204,12 +1199,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fst"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e398fae362f4124bbe630d99519fb2d68a03e2e3a23b441028cdcdc4f4895687"
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "1.1.0"
|
||||
@@ -1994,109 +1983,6 @@ version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
|
||||
|
||||
[[package]]
|
||||
name = "peepmatic"
|
||||
version = "0.78.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"peepmatic-automata",
|
||||
"peepmatic-macro",
|
||||
"peepmatic-runtime",
|
||||
"peepmatic-test-operator",
|
||||
"peepmatic-traits",
|
||||
"serde",
|
||||
"wast 38.0.1",
|
||||
"z3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peepmatic-automata"
|
||||
version = "0.78.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peepmatic-fuzzing"
|
||||
version = "0.66.0"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"bincode",
|
||||
"env_logger 0.8.3",
|
||||
"fst",
|
||||
"log",
|
||||
"peepmatic",
|
||||
"peepmatic-automata",
|
||||
"peepmatic-runtime",
|
||||
"peepmatic-test",
|
||||
"peepmatic-test-operator",
|
||||
"peepmatic-traits",
|
||||
"rand 0.8.3",
|
||||
"serde",
|
||||
"wast 38.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peepmatic-macro"
|
||||
version = "0.78.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peepmatic-runtime"
|
||||
version = "0.78.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"log",
|
||||
"peepmatic-automata",
|
||||
"peepmatic-test-operator",
|
||||
"peepmatic-traits",
|
||||
"serde",
|
||||
"serde_test",
|
||||
"thiserror",
|
||||
"wast 38.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peepmatic-souper"
|
||||
version = "0.78.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"log",
|
||||
"peepmatic",
|
||||
"peepmatic-test-operator",
|
||||
"souper-ir",
|
||||
"wast 38.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peepmatic-test"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"env_logger 0.8.3",
|
||||
"log",
|
||||
"peepmatic",
|
||||
"peepmatic-runtime",
|
||||
"peepmatic-test-operator",
|
||||
"peepmatic-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peepmatic-test-operator"
|
||||
version = "0.78.0"
|
||||
dependencies = [
|
||||
"peepmatic-traits",
|
||||
"serde",
|
||||
"wast 38.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "peepmatic-traits"
|
||||
version = "0.78.0"
|
||||
|
||||
[[package]]
|
||||
name = "pem-rfc7468"
|
||||
version = "0.2.2"
|
||||
@@ -2675,15 +2561,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_test"
|
||||
version = "1.0.126"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd1055d1c20532080b9da5040ec8e27425f4d4573d8e29eb19ba4ff1e4b9da2d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.9.8"
|
||||
@@ -3664,7 +3541,6 @@ dependencies = [
|
||||
"cranelift-reader",
|
||||
"cranelift-wasm",
|
||||
"libfuzzer-sys",
|
||||
"peepmatic-fuzzing",
|
||||
"target-lexicon",
|
||||
"wasm-smith",
|
||||
"wasmtime",
|
||||
@@ -3961,26 +3837,6 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "z3"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa17be9852c6c2a8de2ea0875350dc5f8626f3eaa6b683148753b827f1b39ce5"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"log",
|
||||
"z3-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "z3-sys"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afa18ba5fbd4933e41ffb440c3fd91f91fe9cdb7310cce3ddfb6648563811de0"
|
||||
dependencies = [
|
||||
"cmake",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.4.2"
|
||||
|
||||
@@ -4,6 +4,5 @@ cargo test \
|
||||
--features "test-programs/test_programs" \
|
||||
--workspace \
|
||||
--exclude 'wasmtime-wasi-*' \
|
||||
--exclude 'peepmatic*' \
|
||||
--exclude wasi-crypto \
|
||||
$@
|
||||
|
||||
@@ -34,7 +34,6 @@ termcolor = "1.1.2"
|
||||
capstone = { version = "0.9.0", optional = true }
|
||||
wat = { version = "1.0.36", optional = true }
|
||||
target-lexicon = { version = "0.12", features = ["std"] }
|
||||
peepmatic-souper = { path = "./peepmatic/crates/souper", version = "0.78.0", optional = true }
|
||||
pretty_env_logger = "0.4.0"
|
||||
rayon = { version = "1", optional = true }
|
||||
file-per-thread-logger = "0.1.2"
|
||||
@@ -45,9 +44,8 @@ anyhow = "1.0.32"
|
||||
structopt = "0.3.17"
|
||||
|
||||
[features]
|
||||
default = ["disas", "wasm", "cranelift-codegen/all-arch", "peepmatic-souper", "souper-harvest"]
|
||||
default = ["disas", "wasm", "cranelift-codegen/all-arch", "souper-harvest"]
|
||||
disas = ["capstone"]
|
||||
enable-peepmatic = ["cranelift-codegen/enable-peepmatic", "cranelift-filetests/enable-peepmatic"]
|
||||
wasm = ["wat", "cranelift-wasm"]
|
||||
experimental_arm32 = ["cranelift-codegen/arm32", "cranelift-filetests/experimental_arm32"]
|
||||
souper-harvest = ["cranelift-codegen/souper-harvest", "rayon"]
|
||||
|
||||
@@ -23,12 +23,8 @@ serde = { version = "1.0.94", features = ["derive"], optional = true }
|
||||
bincode = { version = "1.2.1", optional = true }
|
||||
gimli = { version = "0.25.0", default-features = false, features = ["write"], optional = true }
|
||||
smallvec = { version = "1.6.1" }
|
||||
peepmatic = { path = "../peepmatic", optional = true, version = "=0.78.0" }
|
||||
peepmatic-traits = { path = "../peepmatic/crates/traits", optional = true, version = "=0.78.0" }
|
||||
peepmatic-runtime = { path = "../peepmatic/crates/runtime", optional = true, version = "=0.78.0" }
|
||||
regalloc = { version = "0.0.32" }
|
||||
souper-ir = { version = "2.1.0", optional = true }
|
||||
wast = { version = "38.0.0", optional = true }
|
||||
# It is a goal of the cranelift-codegen crate to have minimal external dependencies.
|
||||
# Please don't add any unless they are essential to the task of creating binary
|
||||
# machine code. Integration tests that need external dependencies can be
|
||||
@@ -91,13 +87,6 @@ enable-serde = [
|
||||
# allocation failures, or for regalloc.rs developers.
|
||||
regalloc-snapshot = ["bincode", "regalloc/enable-serde"]
|
||||
|
||||
# Recompile our optimizations that are written in the `peepmatic` DSL into a
|
||||
# compact finite-state transducer automaton.
|
||||
rebuild-peephole-optimizers = ["peepmatic", "peepmatic-traits", "wast"]
|
||||
|
||||
# Enable the use of `peepmatic`-generated peephole optimizers.
|
||||
enable-peepmatic = ["peepmatic-runtime", "peepmatic-traits", "serde"]
|
||||
|
||||
# Enable support for the Souper harvester.
|
||||
souper-harvest = ["souper-ir", "souper-ir/stringify"]
|
||||
|
||||
|
||||
@@ -68,16 +68,6 @@ fn main() {
|
||||
println!("cargo:warning=Generated files are in {}", out_dir);
|
||||
}
|
||||
|
||||
#[cfg(feature = "rebuild-peephole-optimizers")]
|
||||
{
|
||||
let cur_dir = env::current_dir().expect("Can't access current working directory");
|
||||
std::fs::write(
|
||||
std::path::Path::new(&out_dir).join("CRANELIFT_CODEGEN_PATH"),
|
||||
cur_dir.to_str().unwrap(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
// The "Meta deterministic check" CI job runs this build script N
|
||||
// times to ensure it produces the same output
|
||||
// consistently. However, it runs the script in a fresh directory,
|
||||
|
||||
@@ -410,7 +410,7 @@ fn gen_opcodes(all_inst: &AllInstructions, fmt: &mut Formatter) {
|
||||
fmt.line("#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]");
|
||||
fmt.line(
|
||||
r#"#[cfg_attr(
|
||||
any(feature = "enable-peepmatic", feature = "enable-serde"),
|
||||
feature = "enable-serde",
|
||||
derive(serde::Serialize, serde::Deserialize)
|
||||
)]"#,
|
||||
);
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
//! for this specific case, as of 24 Aug 2020:
|
||||
//!
|
||||
//! cd to the top of your wasmtime tree, then:
|
||||
//!
|
||||
//! RUST_BACKTRACE=1 cargo test --features test-programs/test_programs \
|
||||
//! --features experimental_x64 --all --exclude peepmatic \
|
||||
//! --exclude peepmatic-automata --exclude peepmatic-fuzzing \
|
||||
//! --exclude peepmatic-macro --exclude wasmtime-wasi-nn -- isa::x64::inst::emit_tests::test_x64_emit
|
||||
//! --features experimental_x64 --all --exclude wasmtime-wasi-nn \
|
||||
//! -- isa::x64::inst::emit_tests::test_x64_emit
|
||||
|
||||
use super::*;
|
||||
use crate::isa::test_utils;
|
||||
|
||||
@@ -109,9 +109,6 @@ mod simple_preopt;
|
||||
mod unreachable_code;
|
||||
mod value_label;
|
||||
|
||||
#[cfg(feature = "enable-peepmatic")]
|
||||
mod peepmatic;
|
||||
|
||||
#[cfg(feature = "souper-harvest")]
|
||||
mod souper_harvest;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -608,48 +608,6 @@ fn branch_order(pos: &mut FuncCursor, cfg: &mut ControlFlowGraph, block: Block,
|
||||
cfg.recompute_block(pos.func, block);
|
||||
}
|
||||
|
||||
#[cfg(feature = "enable-peepmatic")]
|
||||
mod simplify {
|
||||
use super::*;
|
||||
use crate::peepmatic::ValueOrInst;
|
||||
|
||||
pub type PeepholeOptimizer<'a, 'b> =
|
||||
peepmatic_runtime::optimizer::PeepholeOptimizer<'static, 'a, &'b dyn TargetIsa>;
|
||||
|
||||
pub fn peephole_optimizer<'a, 'b>(isa: &'b dyn TargetIsa) -> PeepholeOptimizer<'a, 'b> {
|
||||
crate::peepmatic::preopt(isa)
|
||||
}
|
||||
|
||||
pub fn apply_all<'a, 'b>(
|
||||
optimizer: &mut PeepholeOptimizer<'a, 'b>,
|
||||
pos: &mut FuncCursor<'a>,
|
||||
inst: Inst,
|
||||
_native_word_width: u32,
|
||||
) {
|
||||
// After we apply one optimization, that might make another
|
||||
// optimization applicable. Keep running the peephole optimizer
|
||||
// until either:
|
||||
//
|
||||
// * No optimization applied, and therefore it doesn't make sense to
|
||||
// try again, because no optimization will apply again.
|
||||
//
|
||||
// * Or when we replaced an instruction with an alias to an existing
|
||||
// value, because we already ran the peephole optimizer over the
|
||||
// aliased value's instruction in an early part of the traversal
|
||||
// over the function.
|
||||
while let Some(ValueOrInst::Inst(new_inst)) =
|
||||
optimizer.apply_one(pos, ValueOrInst::Inst(inst))
|
||||
{
|
||||
// We transplanted a new instruction into the current
|
||||
// instruction, so the "new" instruction is actually the same
|
||||
// one, just with different data.
|
||||
debug_assert_eq!(new_inst, inst);
|
||||
}
|
||||
debug_assert_eq!(pos.current_inst(), Some(inst));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "enable-peepmatic"))]
|
||||
mod simplify {
|
||||
use super::*;
|
||||
use crate::ir::{
|
||||
|
||||
@@ -27,5 +27,4 @@ thiserror = "1.0.15"
|
||||
anyhow = "1.0.32"
|
||||
|
||||
[features]
|
||||
enable-peepmatic = []
|
||||
experimental_arm32 = []
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
test peepmatic
|
||||
target aarch64
|
||||
target x86_64
|
||||
|
||||
function %icmp_to_brz_fold(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = icmp_imm eq v0, 0
|
||||
brnz v1, block1
|
||||
jump block2
|
||||
block1:
|
||||
v3 = iconst.i32 1
|
||||
return v3
|
||||
block2:
|
||||
v4 = iconst.i32 2
|
||||
return v4
|
||||
}
|
||||
; sameln: function %icmp_to_brz_fold
|
||||
; nextln: block0(v0: i32):
|
||||
; nextln: v1 = icmp_imm eq v0, 0
|
||||
; nextln: brnz v0, block2
|
||||
; nextln: jump block1
|
||||
; nextln:
|
||||
; nextln: block1:
|
||||
; nextln: v3 = iconst.i32 1
|
||||
; nextln: return v3
|
||||
; nextln:
|
||||
; nextln: block2:
|
||||
; nextln: v4 = iconst.i32 2
|
||||
; nextln: return v4
|
||||
; nextln: }
|
||||
|
||||
function %icmp_to_brz_inverted_fold(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = icmp_imm ne v0, 0
|
||||
brz v1, block1
|
||||
jump block2
|
||||
block1:
|
||||
v3 = iconst.i32 1
|
||||
return v3
|
||||
block2:
|
||||
v4 = iconst.i32 2
|
||||
return v4
|
||||
}
|
||||
; sameln: function %icmp_to_brz_inve
|
||||
; nextln: block0(v0: i32):
|
||||
; nextln: v1 = icmp_imm ne v0, 0
|
||||
; nextln: brnz v0, block2
|
||||
; nextln: jump block1
|
||||
; nextln:
|
||||
; nextln: block1:
|
||||
; nextln: v3 = iconst.i32 1
|
||||
; nextln: return v3
|
||||
; nextln:
|
||||
; nextln: block2:
|
||||
; nextln: v4 = iconst.i32 2
|
||||
; nextln: return v4
|
||||
; nextln: }
|
||||
|
||||
function %br_icmp_inversion(i32, i32) -> i32 {
|
||||
block0(v0: i32, v1: i32):
|
||||
br_icmp ugt v0, v1, block1
|
||||
jump block2
|
||||
block1:
|
||||
v2 = iconst.i32 1
|
||||
return v2
|
||||
block2:
|
||||
v3 = iconst.i32 2
|
||||
return v3
|
||||
}
|
||||
; sameln: function %br_icmp_inversio
|
||||
; nextln: block0(v0: i32, v1: i32):
|
||||
; nextln: br_icmp ule v0, v1, block2
|
||||
; nextln: jump block1
|
||||
; nextln:
|
||||
; nextln: block1:
|
||||
; nextln: v2 = iconst.i32 1
|
||||
; nextln: return v2
|
||||
; nextln:
|
||||
; nextln: block2:
|
||||
; nextln: v3 = iconst.i32 2
|
||||
; nextln: return v3
|
||||
; nextln: }
|
||||
@@ -1,56 +0,0 @@
|
||||
test peepmatic
|
||||
target aarch64
|
||||
target x86_64 baseline
|
||||
|
||||
; Cases where the denominator is created by an iconst
|
||||
|
||||
function %indir_udiv32(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = iconst.i32 7
|
||||
v2 = udiv v0, v1
|
||||
; check: v4 = iconst.i32 0x2492_4925
|
||||
; nextln: v5 = umulhi v0, v4
|
||||
; nextln: v6 = isub v0, v5
|
||||
; nextln: v7 = ushr_imm v6, 1
|
||||
; nextln: v8 = iadd v7, v5
|
||||
; nextln: v9 = ushr_imm v8, 2
|
||||
; nextln: v2 -> v9
|
||||
return v2
|
||||
}
|
||||
|
||||
function %indir_sdiv32(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = iconst.i32 -17
|
||||
v2 = sdiv v0, v1
|
||||
; check: v4 = iconst.i32 0xffff_ffff_8787_8787
|
||||
; nextln: v5 = smulhi v0, v4
|
||||
; nextln: v6 = sshr_imm v5, 3
|
||||
; nextln: v7 = ushr_imm v6, 31
|
||||
; nextln: v8 = iadd v6, v7
|
||||
; nextln: v2 -> v8
|
||||
return v2
|
||||
}
|
||||
|
||||
function %indir_udiv64(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = iconst.i64 1337
|
||||
v2 = udiv v0, v1
|
||||
; check: v4 = iconst.i64 0xc411_9d95_2866_a139
|
||||
; nextln: v5 = umulhi v0, v4
|
||||
; nextln: v6 = ushr_imm v5, 10
|
||||
; nextln: v2 -> v6
|
||||
return v2
|
||||
}
|
||||
|
||||
function %indir_sdiv64(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = iconst.i64 -90210
|
||||
v2 = sdiv v0, v1
|
||||
; check: v4 = iconst.i64 0xd181_4ee8_939c_b8bb
|
||||
; nextln: v5 = smulhi v0, v4
|
||||
; nextln: v6 = sshr_imm v5, 14
|
||||
; nextln: v7 = ushr_imm v6, 63
|
||||
; nextln: v8 = iadd v6, v7
|
||||
; nextln: v2 -> v8
|
||||
return v2
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
test peepmatic
|
||||
target aarch64
|
||||
target i686 baseline
|
||||
|
||||
; -------- U32 --------
|
||||
|
||||
; complex case (mul, sub, shift, add, shift)
|
||||
function %t_udiv32_p7(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = udiv_imm v0, 7
|
||||
; check: iconst.i32 0x2492_4925
|
||||
; check: umulhi v0, v2
|
||||
; check: isub v0, v3
|
||||
; check: ushr_imm v4, 1
|
||||
; check: iadd v5, v3
|
||||
; check: v7 = ushr_imm v6, 2
|
||||
; check: v1 -> v7
|
||||
return v1
|
||||
}
|
||||
|
||||
; simple case (mul, shift)
|
||||
function %t_udiv32_p125(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = udiv_imm v0, 125
|
||||
; check: iconst.i32 0x1062_4dd3
|
||||
; check: umulhi v0, v2
|
||||
; check: v4 = ushr_imm v3, 3
|
||||
; check: v1 -> v4
|
||||
return v1
|
||||
}
|
||||
|
||||
; simple case w/ shift by zero (mul)
|
||||
function %t_udiv32_p641(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = udiv_imm v0, 641
|
||||
; check: iconst.i32 0x0066_3d81
|
||||
; check: v3 = umulhi v0, v2
|
||||
; check: v1 -> v3
|
||||
return v1
|
||||
}
|
||||
|
||||
|
||||
; -------- S32 --------
|
||||
|
||||
; simple case w/ shift by zero (mul, add-sign-bit)
|
||||
function %t_sdiv32_n6(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = sdiv_imm v0, -6
|
||||
; check: iconst.i32 0xffff_ffff_d555_5555
|
||||
; check: smulhi v0, v2
|
||||
; check: ushr_imm v3, 31
|
||||
; check: v5 = iadd v3, v4
|
||||
; check: v1 -> v5
|
||||
return v1
|
||||
}
|
||||
|
||||
; simple case (mul, shift, add-sign-bit)
|
||||
function %t_sdiv32_n5(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = sdiv_imm v0, -5
|
||||
; check: iconst.i32 0xffff_ffff_9999_9999
|
||||
; check: smulhi v0, v2
|
||||
; check: sshr_imm v3, 1
|
||||
; check: ushr_imm v4, 31
|
||||
; check: v6 = iadd v4, v5
|
||||
; check: v1 -> v6
|
||||
return v1
|
||||
}
|
||||
|
||||
; case d < 0 && M > 0 (mul, sub, shift, add-sign-bit)
|
||||
function %t_sdiv32_n3(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = sdiv_imm v0, -3
|
||||
; check: iconst.i32 0x5555_5555
|
||||
; check: smulhi v0, v2
|
||||
; check: isub v3, v0
|
||||
; check: sshr_imm v4, 1
|
||||
; check: ushr_imm v5, 31
|
||||
; check: v7 = iadd v5, v6
|
||||
; check: v1 -> v7
|
||||
return v1
|
||||
}
|
||||
|
||||
; simple case w/ shift by zero (mul, add-sign-bit)
|
||||
function %t_sdiv32_p6(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = sdiv_imm v0, 6
|
||||
; check: iconst.i32 0x2aaa_aaab
|
||||
; check: smulhi v0, v2
|
||||
; check: ushr_imm v3, 31
|
||||
; check: v5 = iadd v3, v4
|
||||
; check: v1 -> v5
|
||||
return v1
|
||||
}
|
||||
|
||||
; case d > 0 && M < 0 (mull, add, shift, add-sign-bit)
|
||||
function %t_sdiv32_p7(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = sdiv_imm v0, 7
|
||||
; check: iconst.i32 0xffff_ffff_9249_2493
|
||||
; check: smulhi v0, v2
|
||||
; check: iadd v3, v0
|
||||
; check: sshr_imm v4, 2
|
||||
; check: ushr_imm v5, 31
|
||||
; check: v7 = iadd v5, v6
|
||||
; check: v1 -> v7
|
||||
return v1
|
||||
}
|
||||
|
||||
; simple case (mul, shift, add-sign-bit)
|
||||
function %t_sdiv32_p625(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = sdiv_imm v0, 625
|
||||
; check: iconst.i32 0x68db_8bad
|
||||
; check: smulhi v0, v2
|
||||
; check: sshr_imm v3, 8
|
||||
; check: ushr_imm v4, 31
|
||||
; check: v6 = iadd v4, v5
|
||||
; check: v1 -> v6
|
||||
return v1
|
||||
}
|
||||
|
||||
|
||||
; -------- U64 --------
|
||||
|
||||
; complex case (mul, sub, shift, add, shift)
|
||||
function %t_udiv64_p7(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = udiv_imm v0, 7
|
||||
; check: iconst.i64 0x2492_4924_9249_2493
|
||||
; check: umulhi v0, v2
|
||||
; check: isub v0, v3
|
||||
; check: ushr_imm v4, 1
|
||||
; check: iadd v5, v3
|
||||
; check: v7 = ushr_imm v6, 2
|
||||
; check: v1 -> v7
|
||||
return v1
|
||||
}
|
||||
|
||||
; simple case (mul, shift)
|
||||
function %t_udiv64_p9(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = udiv_imm v0, 9
|
||||
; check: iconst.i64 0xe38e_38e3_8e38_e38f
|
||||
; check: umulhi v0, v2
|
||||
; check: v4 = ushr_imm v3, 3
|
||||
; check: v1 -> v4
|
||||
return v1
|
||||
}
|
||||
|
||||
; complex case (mul, sub, shift, add, shift)
|
||||
function %t_udiv64_p125(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = udiv_imm v0, 125
|
||||
; check: iconst.i64 0x0624_dd2f_1a9f_be77
|
||||
; check: umulhi v0, v2
|
||||
; check: isub v0, v3
|
||||
; check: ushr_imm v4, 1
|
||||
; check: iadd v5, v3
|
||||
; check: v7 = ushr_imm v6, 6
|
||||
; check: v1 -> v7
|
||||
return v1
|
||||
}
|
||||
|
||||
; simple case w/ shift by zero (mul)
|
||||
function %t_udiv64_p274177(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = udiv_imm v0, 274177
|
||||
; check: iconst.i64 0x3d30_f19c_d101
|
||||
; check: v3 = umulhi v0, v2
|
||||
; check: v1 -> v3
|
||||
return v1
|
||||
}
|
||||
|
||||
|
||||
; -------- S64 --------
|
||||
|
||||
; simple case (mul, shift, add-sign-bit)
|
||||
function %t_sdiv64_n625(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = sdiv_imm v0, -625
|
||||
; check: iconst.i64 0xcb92_3a29_c779_a6b5
|
||||
; check: smulhi v0, v2
|
||||
; check: sshr_imm v3, 7
|
||||
; check: ushr_imm v4, 63
|
||||
; check: v6 = iadd v4, v5
|
||||
; check: v1 -> v6
|
||||
return v1
|
||||
}
|
||||
|
||||
; simple case w/ zero shift (mul, add-sign-bit)
|
||||
function %t_sdiv64_n6(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = sdiv_imm v0, -6
|
||||
; check: iconst.i64 0xd555_5555_5555_5555
|
||||
; check: smulhi v0, v2
|
||||
; check: ushr_imm v3, 63
|
||||
; check: v5 = iadd v3, v4
|
||||
; check: v1 -> v5
|
||||
return v1
|
||||
}
|
||||
|
||||
; simple case w/ zero shift (mul, add-sign-bit)
|
||||
function %t_sdiv64_n5(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = sdiv_imm v0, -5
|
||||
; check: iconst.i64 0x9999_9999_9999_9999
|
||||
; check: smulhi v0, v2
|
||||
; check: sshr_imm v3, 1
|
||||
; check: ushr_imm v4, 63
|
||||
; check: v6 = iadd v4, v5
|
||||
; check: v1 -> v6
|
||||
return v1
|
||||
}
|
||||
|
||||
; case d < 0 && M > 0 (mul, sub, shift, add-sign-bit)
|
||||
function %t_sdiv64_n3(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = sdiv_imm v0, -3
|
||||
; check: iconst.i64 0x5555_5555_5555_5555
|
||||
; check: smulhi v0, v2
|
||||
; check: isub v3, v0
|
||||
; check: sshr_imm v4, 1
|
||||
; check: ushr_imm v5, 63
|
||||
; check: v7 = iadd v5, v6
|
||||
; check: v1 -> v7
|
||||
return v1
|
||||
}
|
||||
|
||||
; simple case w/ zero shift (mul, add-sign-bit)
|
||||
function %t_sdiv64_p6(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = sdiv_imm v0, 6
|
||||
; check: iconst.i64 0x2aaa_aaaa_aaaa_aaab
|
||||
; check: smulhi v0, v2
|
||||
; check: ushr_imm v3, 63
|
||||
; check: v5 = iadd v3, v4
|
||||
; check: v1 -> v5
|
||||
return v1
|
||||
}
|
||||
|
||||
; case d > 0 && M < 0 (mul, add, shift, add-sign-bit)
|
||||
function %t_sdiv64_p15(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = sdiv_imm v0, 15
|
||||
; check: iconst.i64 0x8888_8888_8888_8889
|
||||
; check: smulhi v0, v2
|
||||
; check: iadd v3, v0
|
||||
; check: sshr_imm v4, 3
|
||||
; check: ushr_imm v5, 63
|
||||
; check: v7 = iadd v5, v6
|
||||
; check: v1 -> v7
|
||||
return v1
|
||||
}
|
||||
|
||||
; simple case (mul, shift, add-sign-bit)
|
||||
function %t_sdiv64_p625(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = sdiv_imm v0, 625
|
||||
; check: iconst.i64 0x346d_c5d6_3886_594b
|
||||
; check: smulhi v0, v2
|
||||
; check: sshr_imm v3, 7
|
||||
; check: ushr_imm v4, 63
|
||||
; check: v6 = iadd v4, v5
|
||||
; check: v1 -> v6
|
||||
return v1
|
||||
}
|
||||
@@ -1,293 +0,0 @@
|
||||
test peepmatic
|
||||
target aarch64
|
||||
target i686 baseline
|
||||
|
||||
; -------- U32 --------
|
||||
|
||||
; ignored
|
||||
function %t_udiv32_p0(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = udiv_imm v0, 0
|
||||
; check: udiv_imm v0, 0
|
||||
return v1
|
||||
}
|
||||
|
||||
; converted to a nop
|
||||
function %t_udiv32_p1(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = udiv_imm v0, 1
|
||||
; check: nop
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_udiv32_p2(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = udiv_imm v0, 2
|
||||
; check: ushr_imm v0, 1
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_udiv32_p2p31(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = udiv_imm v0, 0x8000_0000
|
||||
; check: ushr_imm v0, 31
|
||||
return v1
|
||||
}
|
||||
|
||||
|
||||
; -------- U64 --------
|
||||
|
||||
; ignored
|
||||
function %t_udiv64_p0(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = udiv_imm v0, 0
|
||||
; check: udiv_imm v0, 0
|
||||
return v1
|
||||
}
|
||||
|
||||
; converted to a nop
|
||||
function %t_udiv64_p1(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = udiv_imm v0, 1
|
||||
; check: nop
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_udiv64_p2(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = udiv_imm v0, 2
|
||||
; check: ushr_imm v0, 1
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_udiv64_p2p63(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = udiv_imm v0, 0x8000_0000_0000_0000
|
||||
; check: ushr_imm v0, 63
|
||||
return v1
|
||||
}
|
||||
|
||||
|
||||
; -------- S32 --------
|
||||
|
||||
; ignored
|
||||
function %t_sdiv32_p0(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = sdiv_imm v0, 0
|
||||
; check: sdiv_imm v0, 0
|
||||
return v1
|
||||
}
|
||||
|
||||
; converted to a nop
|
||||
function %t_sdiv32_p1(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = sdiv_imm v0, 1
|
||||
; check: nop
|
||||
return v1
|
||||
}
|
||||
|
||||
; ignored
|
||||
function %t_sdiv32_n1(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = sdiv_imm v0, -1
|
||||
; check: sdiv_imm v0, -1
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_sdiv32_p2(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = sdiv_imm v0, 2
|
||||
; check: ushr_imm v0, 31
|
||||
; check: iadd v0, v2
|
||||
; check: sshr_imm v3, 1
|
||||
; check: v1 -> v4
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_sdiv32_n2(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = sdiv_imm v0, -2
|
||||
; check: ushr_imm v0, 31
|
||||
; check: iadd v0, v2
|
||||
; check: sshr_imm v3, 1
|
||||
; check: irsub_imm v4, 0
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_sdiv32_p4(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = sdiv_imm v0, 4
|
||||
; check: v2 = sshr_imm v0, 1
|
||||
; check: ushr_imm v2, 30
|
||||
; check: iadd v0, v3
|
||||
; check: v5 = sshr_imm v4, 2
|
||||
; check: v1 -> v5
|
||||
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_sdiv32_n4(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = sdiv_imm v0, -4
|
||||
; check: sshr_imm v0, 1
|
||||
; check: ushr_imm v2, 30
|
||||
; check: iadd v0, v3
|
||||
; check: sshr_imm v4, 2
|
||||
; check: irsub_imm v5, 0
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_sdiv32_p2p30(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = sdiv_imm v0, 0x4000_0000
|
||||
; check: sshr_imm v0, 29
|
||||
; check: ushr_imm v2, 2
|
||||
; check: iadd v0, v3
|
||||
; check: v5 = sshr_imm v4, 30
|
||||
; check: v1 -> v5
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_sdiv32_n2p30(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = sdiv_imm v0, -0x4000_0000
|
||||
; check: sshr_imm v0, 29
|
||||
; check: ushr_imm v2, 2
|
||||
; check: iadd v0, v3
|
||||
; check: sshr_imm v4, 30
|
||||
; check: irsub_imm v5, 0
|
||||
return v1
|
||||
}
|
||||
|
||||
; there's no positive version of this, since -(-0x8000_0000) isn't
|
||||
; representable.
|
||||
function %t_sdiv32_n2p31(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = sdiv_imm v0, -0x8000_0000
|
||||
; check: sshr_imm v0, 30
|
||||
; check: ushr_imm v2, 1
|
||||
; check: iadd v0, v3
|
||||
; check: sshr_imm v4, 31
|
||||
; check: irsub_imm v5, 0
|
||||
return v1
|
||||
}
|
||||
|
||||
|
||||
; -------- S64 --------
|
||||
|
||||
; ignored
|
||||
function %t_sdiv64_p0(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = sdiv_imm v0, 0
|
||||
; check: sdiv_imm v0, 0
|
||||
return v1
|
||||
}
|
||||
|
||||
; converted to a nop
|
||||
function %t_sdiv64_p1(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = sdiv_imm v0, 1
|
||||
; check: nop
|
||||
return v1
|
||||
}
|
||||
|
||||
; ignored
|
||||
function %t_sdiv64_n1(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = sdiv_imm v0, -1
|
||||
; check: sdiv_imm v0, -1
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_sdiv64_p2(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = sdiv_imm v0, 2
|
||||
; check: ushr_imm v0, 63
|
||||
; check: iadd v0, v2
|
||||
; check: v4 = sshr_imm v3, 1
|
||||
; check: v1 -> v4
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_sdiv64_n2(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = sdiv_imm v0, -2
|
||||
; check: ushr_imm v0, 63
|
||||
; check: iadd v0, v2
|
||||
; check: sshr_imm v3, 1
|
||||
; check: irsub_imm v4, 0
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_sdiv64_p4(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = sdiv_imm v0, 4
|
||||
; check: sshr_imm v0, 1
|
||||
; check: ushr_imm v2, 62
|
||||
; check: iadd v0, v3
|
||||
; check: v5 = sshr_imm v4, 2
|
||||
; check: v1 -> v5
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_sdiv64_n4(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = sdiv_imm v0, -4
|
||||
; check: sshr_imm v0, 1
|
||||
; check: ushr_imm v2, 62
|
||||
; check: iadd v0, v3
|
||||
; check: sshr_imm v4, 2
|
||||
; check: irsub_imm v5, 0
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_sdiv64_p2p62(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = sdiv_imm v0, 0x4000_0000_0000_0000
|
||||
; check: sshr_imm v0, 61
|
||||
; check: ushr_imm v2, 2
|
||||
; check: iadd v0, v3
|
||||
; check: v5 = sshr_imm v4, 62
|
||||
; check: v1 -> v5
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_sdiv64_n2p62(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = sdiv_imm v0, -0x4000_0000_0000_0000
|
||||
; check: sshr_imm v0, 61
|
||||
; check: ushr_imm v2, 2
|
||||
; check: iadd v0, v3
|
||||
; check: sshr_imm v4, 62
|
||||
; check: irsub_imm v5, 0
|
||||
return v1
|
||||
}
|
||||
|
||||
; there's no positive version of this, since -(-0x8000_0000_0000_0000) isn't
|
||||
; representable.
|
||||
function %t_sdiv64_n2p63(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = sdiv_imm v0, -0x8000_0000_0000_0000
|
||||
; check: sshr_imm v0, 62
|
||||
; check: ushr_imm v2, 1
|
||||
; check: iadd v0, v3
|
||||
; check: sshr_imm v4, 63
|
||||
; check: irsub_imm v5, 0
|
||||
return v1
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
test peepmatic
|
||||
target aarch64
|
||||
target x86_64
|
||||
|
||||
;; This file used to trigger assertions where we would keep trying to
|
||||
;; unnecessarily apply optimizations after replacing an instruction with an
|
||||
;; alias of another value that we had already optimized.
|
||||
|
||||
function %foo() {
|
||||
block0:
|
||||
v0 = iconst.i32 3
|
||||
v1 = srem_imm v0, 2
|
||||
v2 = sdiv_imm v1, 1
|
||||
trap unreachable
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
test peepmatic
|
||||
target aarch64
|
||||
target x86_64
|
||||
|
||||
;; Test that although v5 can be replaced with v1, we don't transplant `load.i32
|
||||
;; v0` on top of `iadd v3, v4`, because that would move the load past other uses
|
||||
;; of its result.
|
||||
|
||||
function %foo(i64) -> i32 {
|
||||
block0(v0: i64):
|
||||
v1 = load.i32 v0
|
||||
v2 = iconst.i32 16
|
||||
v3 = iadd_imm v1, -16
|
||||
v4 = iconst.i32 16
|
||||
v5 = iadd v3, v4
|
||||
; check: v1 = load.i32 v0
|
||||
; nextln: v5 -> v1
|
||||
; nextln: v2 = iconst.i32 16
|
||||
; nextln: v3 = iadd_imm v1, -16
|
||||
; nextln: v4 = iconst.i32 16
|
||||
; nextln: nop
|
||||
return v5
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
test peepmatic
|
||||
target aarch64
|
||||
target x86_64
|
||||
|
||||
function %wraparound(i64 vmctx) -> f32 system_v {
|
||||
gv0 = vmctx
|
||||
gv1 = iadd_imm.i64 gv0, 48
|
||||
|
||||
block35(v0: i64):
|
||||
v88 = iconst.i64 0
|
||||
v89 = iconst.i64 0x8000_0000_0000_0000
|
||||
v90 = ishl_imm v88, 0x8000_0000_0000_0000
|
||||
v91 = sshr v90, v89; check: sshr_imm v90, 0x8000_0000_0000_0000
|
||||
trap user0
|
||||
}
|
||||
@@ -1,286 +0,0 @@
|
||||
test peepmatic
|
||||
target aarch64
|
||||
target i686 baseline
|
||||
|
||||
; -------- U32 --------
|
||||
|
||||
; complex case (mul, sub, shift, add, shift)
|
||||
function %t_urem32_p7(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = urem_imm v0, 7
|
||||
; check: iconst.i32 0x2492_4925
|
||||
; check: umulhi v0, v2
|
||||
; check: isub v0, v3
|
||||
; check: ushr_imm v4, 1
|
||||
; check: iadd v5, v3
|
||||
; check: ushr_imm v6, 2
|
||||
; check: imul_imm v7, 7
|
||||
; check: isub v0, v8
|
||||
return v1
|
||||
}
|
||||
|
||||
; simple case (mul, shift)
|
||||
function %t_urem32_p125(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = urem_imm v0, 125
|
||||
; check: iconst.i32 0x1062_4dd3
|
||||
; check: umulhi v0, v2
|
||||
; check: ushr_imm v3, 3
|
||||
; check: imul_imm v4, 125
|
||||
; check: isub v0, v5
|
||||
return v1
|
||||
}
|
||||
|
||||
; simple case w/ shift by zero (mul)
|
||||
function %t_urem32_p641(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = urem_imm v0, 641
|
||||
; check: iconst.i32 0x0066_3d81
|
||||
; check: umulhi v0, v2
|
||||
; check: imul_imm v3, 641
|
||||
; check: isub v0, v4
|
||||
return v1
|
||||
}
|
||||
|
||||
|
||||
; -------- S32 --------
|
||||
|
||||
; simple case w/ shift by zero (mul, add-sign-bit)
|
||||
function %t_srem32_n6(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = srem_imm v0, -6
|
||||
; check: iconst.i32 0xffff_ffff_d555_5555
|
||||
; check: smulhi v0, v2
|
||||
; check: ushr_imm v3, 31
|
||||
; check: iadd v3, v4
|
||||
; check: imul_imm v5, -6
|
||||
; check: isub v0, v6
|
||||
return v1
|
||||
}
|
||||
|
||||
; simple case (mul, shift, add-sign-bit)
|
||||
function %t_srem32_n5(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = srem_imm v0, -5
|
||||
; check: iconst.i32 0xffff_ffff_9999_9999
|
||||
; check: smulhi v0, v2
|
||||
; check: sshr_imm v3, 1
|
||||
; check: ushr_imm v4, 31
|
||||
; check: iadd v4, v5
|
||||
; check: imul_imm v6, -5
|
||||
; check: isub v0, v7
|
||||
return v1
|
||||
}
|
||||
|
||||
; case d < 0 && M > 0 (mul, sub, shift, add-sign-bit)
|
||||
function %t_srem32_n3(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = srem_imm v0, -3
|
||||
; check: iconst.i32 0x5555_5555
|
||||
; check: smulhi v0, v2
|
||||
; check: isub v3, v0
|
||||
; check: sshr_imm v4, 1
|
||||
; check: ushr_imm v5, 31
|
||||
; check: iadd v5, v6
|
||||
; check: imul_imm v7, -3
|
||||
; check: isub v0, v8
|
||||
return v1
|
||||
}
|
||||
|
||||
; simple case w/ shift by zero (mul, add-sign-bit)
|
||||
function %t_srem32_p6(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = srem_imm v0, 6
|
||||
; check: iconst.i32 0x2aaa_aaab
|
||||
; check: smulhi v0, v2
|
||||
; check: ushr_imm v3, 31
|
||||
; check: iadd v3, v4
|
||||
; check: imul_imm v5, 6
|
||||
; check: isub v0, v6
|
||||
return v1
|
||||
}
|
||||
|
||||
; case d > 0 && M < 0 (mull, add, shift, add-sign-bit)
|
||||
function %t_srem32_p7(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = srem_imm v0, 7
|
||||
; check: iconst.i32 0xffff_ffff_9249_2493
|
||||
; check: smulhi v0, v2
|
||||
; check: iadd v3, v0
|
||||
; check: sshr_imm v4, 2
|
||||
; check: ushr_imm v5, 31
|
||||
; check: iadd v5, v6
|
||||
; check: imul_imm v7, 7
|
||||
; check: isub v0, v8
|
||||
return v1
|
||||
}
|
||||
|
||||
; simple case (mul, shift, add-sign-bit)
|
||||
function %t_srem32_p625(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = srem_imm v0, 625
|
||||
; check: iconst.i32 0x68db_8bad
|
||||
; check: smulhi v0, v2
|
||||
; check: sshr_imm v3, 8
|
||||
; check: ushr_imm v4, 31
|
||||
; check: iadd v4, v5
|
||||
; check: imul_imm v6, 625
|
||||
; check: isub v0, v7
|
||||
return v1
|
||||
}
|
||||
|
||||
|
||||
; -------- U64 --------
|
||||
|
||||
; complex case (mul, sub, shift, add, shift)
|
||||
function %t_urem64_p7(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = urem_imm v0, 7
|
||||
; check: umulhi v0, v2
|
||||
; check: isub v0, v3
|
||||
; check: ushr_imm v4, 1
|
||||
; check: iadd v5, v3
|
||||
; check: ushr_imm v6, 2
|
||||
; check: imul_imm v7, 7
|
||||
; check: isub v0, v8
|
||||
return v1
|
||||
}
|
||||
|
||||
; simple case (mul, shift)
|
||||
function %t_urem64_p9(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = urem_imm v0, 9
|
||||
; check: iconst.i64 0xe38e_38e3_8e38_e38f
|
||||
; check: umulhi v0, v2
|
||||
; check: ushr_imm v3, 3
|
||||
; check: imul_imm v4, 9
|
||||
; check: isub v0, v5
|
||||
return v1
|
||||
}
|
||||
|
||||
; complex case (mul, sub, shift, add, shift)
|
||||
function %t_urem64_p125(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = urem_imm v0, 125
|
||||
; check: iconst.i64 0x0624_dd2f_1a9f_be77
|
||||
; check: umulhi v0, v2
|
||||
; check: isub v0, v3
|
||||
; check: ushr_imm v4, 1
|
||||
; check: iadd v5, v3
|
||||
; check: ushr_imm v6, 6
|
||||
; check: imul_imm v7, 125
|
||||
; check: isub v0, v8
|
||||
return v1
|
||||
}
|
||||
|
||||
; simple case w/ shift by zero (mul)
|
||||
function %t_urem64_p274177(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = urem_imm v0, 274177
|
||||
; check: iconst.i64 0x3d30_f19c_d101
|
||||
; check: umulhi v0, v2
|
||||
; check: imul_imm v3, 0x0004_2f01
|
||||
; check: isub v0, v4
|
||||
return v1
|
||||
}
|
||||
|
||||
|
||||
; -------- S64 --------
|
||||
|
||||
; simple case (mul, shift, add-sign-bit)
|
||||
function %t_srem64_n625(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = srem_imm v0, -625
|
||||
; check: iconst.i64 0xcb92_3a29_c779_a6b5
|
||||
; check: smulhi v0, v2
|
||||
; check: sshr_imm v3, 7
|
||||
; check: ushr_imm v4, 63
|
||||
; check: iadd v4, v5
|
||||
; check: imul_imm v6, -625
|
||||
; check: isub v0, v7
|
||||
return v1
|
||||
}
|
||||
|
||||
; simple case w/ zero shift (mul, add-sign-bit)
|
||||
function %t_srem64_n6(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = srem_imm v0, -6
|
||||
; check: iconst.i64 0xd555_5555_5555_5555
|
||||
; check: smulhi v0, v2
|
||||
; check: ushr_imm v3, 63
|
||||
; check: iadd v3, v4
|
||||
; check: imul_imm v5, -6
|
||||
; check: isub v0, v6
|
||||
return v1
|
||||
}
|
||||
|
||||
; simple case w/ zero shift (mul, add-sign-bit)
|
||||
function %t_srem64_n5(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = srem_imm v0, -5
|
||||
; check: iconst.i64 0x9999_9999_9999_9999
|
||||
; check: smulhi v0, v2
|
||||
; check: sshr_imm v3, 1
|
||||
; check: ushr_imm v4, 63
|
||||
; check: iadd v4, v5
|
||||
; check: imul_imm v6, -5
|
||||
; check: isub v0, v7
|
||||
return v1
|
||||
}
|
||||
|
||||
; case d < 0 && M > 0 (mul, sub, shift, add-sign-bit)
|
||||
function %t_srem64_n3(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = srem_imm v0, -3
|
||||
; check: iconst.i64 0x5555_5555_5555_5555
|
||||
; check: smulhi v0, v2
|
||||
; check: isub v3, v0
|
||||
; check: sshr_imm v4, 1
|
||||
; check: ushr_imm v5, 63
|
||||
; check: iadd v5, v6
|
||||
; check: imul_imm v7, -3
|
||||
; check: isub v0, v8
|
||||
return v1
|
||||
}
|
||||
|
||||
; simple case w/ zero shift (mul, add-sign-bit)
|
||||
function %t_srem64_p6(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = srem_imm v0, 6
|
||||
; check: iconst.i64 0x2aaa_aaaa_aaaa_aaab
|
||||
; check: smulhi v0, v2
|
||||
; check: ushr_imm v3, 63
|
||||
; check: iadd v3, v4
|
||||
; check: imul_imm v5, 6
|
||||
; check: isub v0, v6
|
||||
return v1
|
||||
}
|
||||
|
||||
; case d > 0 && M < 0 (mul, add, shift, add-sign-bit)
|
||||
function %t_srem64_p15(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = srem_imm v0, 15
|
||||
; check: iconst.i64 0x8888_8888_8888_8889
|
||||
; check: smulhi v0, v2
|
||||
; check: iadd v3, v0
|
||||
; check: sshr_imm v4, 3
|
||||
; check: ushr_imm v5, 63
|
||||
; check: iadd v5, v6
|
||||
; check: imul_imm v7, 15
|
||||
; check: isub v0, v8
|
||||
return v1
|
||||
}
|
||||
|
||||
; simple case (mul, shift, add-sign-bit)
|
||||
function %t_srem64_p625(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = srem_imm v0, 625
|
||||
; check: iconst.i64 0x346d_c5d6_3886_594b
|
||||
; check: smulhi v0, v2
|
||||
; check: sshr_imm v3, 7
|
||||
; check: ushr_imm v4, 63
|
||||
; check: iadd v4, v5
|
||||
; check: imul_imm v6, 625
|
||||
; check: isub v0, v7
|
||||
return v1
|
||||
}
|
||||
@@ -1,292 +0,0 @@
|
||||
test peepmatic
|
||||
target aarch64
|
||||
target i686 baseline
|
||||
|
||||
; -------- U32 --------
|
||||
|
||||
; ignored
|
||||
function %t_urem32_p0(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = urem_imm v0, 0
|
||||
; check: urem_imm v0, 0
|
||||
return v1
|
||||
}
|
||||
|
||||
; converted to constant zero
|
||||
function %t_urem32_p1(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = urem_imm v0, 1
|
||||
; check: iconst.i32 0
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_urem32_p2(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = urem_imm v0, 2
|
||||
; check: band_imm v0, 1
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_urem32_p2p31(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = urem_imm v0, 0x8000_0000
|
||||
; check: band_imm v0, 0x7fff_ffff
|
||||
return v1
|
||||
}
|
||||
|
||||
|
||||
; -------- U64 --------
|
||||
|
||||
; ignored
|
||||
function %t_urem64_p0(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = urem_imm v0, 0
|
||||
; check: urem_imm v0, 0
|
||||
return v1
|
||||
}
|
||||
|
||||
; converted to constant zero
|
||||
function %t_urem64_p1(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = urem_imm v0, 1
|
||||
; check: iconst.i64 0
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_urem64_p2(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = urem_imm v0, 2
|
||||
; check: band_imm v0, 1
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_urem64_p2p63(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = urem_imm v0, 0x8000_0000_0000_0000
|
||||
; check: band_imm v0, 0x7fff_ffff_ffff_ffff
|
||||
return v1
|
||||
}
|
||||
|
||||
|
||||
; -------- S32 --------
|
||||
|
||||
; ignored
|
||||
function %t_srem32_n1(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = srem_imm v0, -1
|
||||
; check: srem_imm v0, -1
|
||||
return v1
|
||||
}
|
||||
|
||||
; ignored
|
||||
function %t_srem32_p0(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = srem_imm v0, 0
|
||||
; check: srem_imm v0, 0
|
||||
return v1
|
||||
}
|
||||
|
||||
; converted to constant zero
|
||||
function %t_srem32_p1(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = srem_imm v0, 1
|
||||
; check: iconst.i32 0
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_srem32_p2(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = srem_imm v0, 2
|
||||
; check: ushr_imm v0, 31
|
||||
; check: iadd v0, v2
|
||||
; check: band_imm v3, -2
|
||||
; check: isub v0, v4
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_srem32_n2(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = srem_imm v0, -2
|
||||
; check: ushr_imm v0, 31
|
||||
; check: iadd v0, v2
|
||||
; check: band_imm v3, -2
|
||||
; check: isub v0, v4
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_srem32_p4(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = srem_imm v0, 4
|
||||
; check: sshr_imm v0, 1
|
||||
; check: ushr_imm v2, 30
|
||||
; check: iadd v0, v3
|
||||
; check: band_imm v4, -4
|
||||
; check: isub v0, v5
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_srem32_n4(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = srem_imm v0, -4
|
||||
; check: sshr_imm v0, 1
|
||||
; check: ushr_imm v2, 30
|
||||
; check: iadd v0, v3
|
||||
; check: band_imm v4, -4
|
||||
; check: isub v0, v5
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_srem32_p2p30(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = srem_imm v0, 0x4000_0000
|
||||
; check: sshr_imm v0, 29
|
||||
; check: ushr_imm v2, 2
|
||||
; check: iadd v0, v3
|
||||
; check: band_imm v4, 0xffff_ffff_c000_0000
|
||||
; check: isub v0, v5
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_srem32_n2p30(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = srem_imm v0, -0x4000_0000
|
||||
; check: sshr_imm v0, 29
|
||||
; check: ushr_imm v2, 2
|
||||
; check: iadd v0, v3
|
||||
; check: band_imm v4, 0xffff_ffff_c000_0000
|
||||
; check: isub v0, v5
|
||||
return v1
|
||||
}
|
||||
|
||||
; there's no positive version of this, since -(-0x8000_0000) isn't
|
||||
; representable.
|
||||
function %t_srem32_n2p31(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = srem_imm v0, -0x8000_0000
|
||||
; check: sshr_imm v0, 30
|
||||
; check: ushr_imm v2, 1
|
||||
; check: iadd v0, v3
|
||||
; check: band_imm v4, 0xffff_ffff_8000_0000
|
||||
; check: isub v0, v5
|
||||
return v1
|
||||
}
|
||||
|
||||
|
||||
; -------- S64 --------
|
||||
|
||||
; ignored
|
||||
function %t_srem64_n1(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = srem_imm v0, -1
|
||||
; check: srem_imm v0, -1
|
||||
return v1
|
||||
}
|
||||
|
||||
; ignored
|
||||
function %t_srem64_p0(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = srem_imm v0, 0
|
||||
; check: srem_imm v0, 0
|
||||
return v1
|
||||
}
|
||||
|
||||
; converted to constant zero
|
||||
function %t_srem64_p1(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = srem_imm v0, 1
|
||||
; check: iconst.i64 0
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_srem64_p2(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = srem_imm v0, 2
|
||||
; check: ushr_imm v0, 63
|
||||
; check: iadd v0, v2
|
||||
; check: band_imm v3, -2
|
||||
; check: isub v0, v4
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_srem64_n2(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = srem_imm v0, -2
|
||||
; check: ushr_imm v0, 63
|
||||
; check: iadd v0, v2
|
||||
; check: band_imm v3, -2
|
||||
; check: isub v0, v4
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_srem64_p4(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = srem_imm v0, 4
|
||||
; check: sshr_imm v0, 1
|
||||
; check: ushr_imm v2, 62
|
||||
; check: iadd v0, v3
|
||||
; check: band_imm v4, -4
|
||||
; check: isub v0, v5
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_srem64_n4(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = srem_imm v0, -4
|
||||
; check: sshr_imm v0, 1
|
||||
; check: ushr_imm v2, 62
|
||||
; check: iadd v0, v3
|
||||
; check: band_imm v4, -4
|
||||
; check: isub v0, v5
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_srem64_p2p62(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = srem_imm v0, 0x4000_0000_0000_0000
|
||||
; check: sshr_imm v0, 61
|
||||
; check: ushr_imm v2, 2
|
||||
; check: iadd v0, v3
|
||||
; check: band_imm v4, 0xc000_0000_0000_0000
|
||||
; check: isub v0, v5
|
||||
return v1
|
||||
}
|
||||
|
||||
; shift
|
||||
function %t_srem64_n2p62(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = srem_imm v0, -0x4000_0000_0000_0000
|
||||
; check: sshr_imm v0, 61
|
||||
; check: ushr_imm v2, 2
|
||||
; check: iadd v0, v3
|
||||
; check: band_imm v4, 0xc000_0000_0000_0000
|
||||
; check: isub v0, v5
|
||||
return v1
|
||||
}
|
||||
|
||||
; there's no positive version of this, since -(-0x8000_0000_0000_0000) isn't
|
||||
; representable.
|
||||
function %t_srem64_n2p63(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = srem_imm v0, -0x8000_0000_0000_0000
|
||||
; check: sshr_imm v0, 62
|
||||
; check: ushr_imm v2, 1
|
||||
; check: iadd v0, v3
|
||||
; check: band_imm v4, 0x8000_0000_0000_0000
|
||||
; check: isub v0, v5
|
||||
return v1
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
test peepmatic
|
||||
target aarch64
|
||||
target x86_64
|
||||
|
||||
function u0:2(i64 , i64) {
|
||||
gv1 = load.i64 notrap aligned gv0
|
||||
heap0 = static gv1
|
||||
block0(v0: i64, v1: i64):
|
||||
v16 = iconst.i32 6
|
||||
v17 = heap_addr.i64 heap0, v16, 1
|
||||
v18 = load.i32 v17
|
||||
v19 = iconst.i32 4
|
||||
v20 = icmp ne v18, v19
|
||||
v21 = bint.i32 v20
|
||||
brnz v21, block2
|
||||
jump block4
|
||||
block4:
|
||||
jump block1
|
||||
block2:
|
||||
jump block1
|
||||
block1:
|
||||
return
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
test peepmatic
|
||||
target aarch64
|
||||
target i686
|
||||
|
||||
;; 32-bits platforms.
|
||||
|
||||
function %iadd_imm(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = iconst.i32 2
|
||||
v2 = iadd v0, v1
|
||||
return v2
|
||||
}
|
||||
; sameln: function %iadd_imm
|
||||
; nextln: block0(v0: i32):
|
||||
; nextln: v1 = iconst.i32 2
|
||||
; nextln: v2 = iadd_imm v0, 2
|
||||
; nextln: return v2
|
||||
; nextln: }
|
||||
|
||||
function %isub_imm(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = iconst.i32 2
|
||||
v2 = isub v0, v1
|
||||
return v2
|
||||
}
|
||||
; sameln: function %isub_imm
|
||||
; nextln: block0(v0: i32):
|
||||
; nextln: v1 = iconst.i32 2
|
||||
; nextln: v2 = iadd_imm v0, -2
|
||||
; nextln: return v2
|
||||
; nextln: }
|
||||
|
||||
function %icmp_imm(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = iconst.i32 2
|
||||
v2 = icmp slt v0, v1
|
||||
v3 = bint.i32 v2
|
||||
return v3
|
||||
}
|
||||
; sameln: function %icmp_imm
|
||||
; nextln: block0(v0: i32):
|
||||
; nextln: v1 = iconst.i32 2
|
||||
; nextln: v2 = icmp_imm slt v0, 2
|
||||
; nextln: v3 = bint.i32 v2
|
||||
; nextln: return v3
|
||||
; nextln: }
|
||||
|
||||
;; Don't simplify operations that would get illegal because of lack of native
|
||||
;; support.
|
||||
function %iadd_imm(i64) -> i64 {
|
||||
block0(v0: i64):
|
||||
v1 = iconst.i64 2
|
||||
v2 = iadd v0, v1
|
||||
return v2
|
||||
}
|
||||
; sameln: function %iadd_imm
|
||||
; nextln: block0(v0: i64):
|
||||
; nextln: v1 = iconst.i64 2
|
||||
; nextln: v2 = iadd v0, v1
|
||||
; nextln: return v2
|
||||
; nextln: }
|
||||
@@ -1,327 +0,0 @@
|
||||
test peepmatic
|
||||
target aarch64
|
||||
target x86_64
|
||||
|
||||
;; 64-bits platforms.
|
||||
|
||||
function %iadd_imm(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = iconst.i32 2
|
||||
v2 = iadd v0, v1
|
||||
return v2
|
||||
}
|
||||
; sameln: function %iadd_imm
|
||||
; nextln: block0(v0: i32):
|
||||
; nextln: v1 = iconst.i32 2
|
||||
; nextln: v2 = iadd_imm v0, 2
|
||||
; nextln: return v2
|
||||
; nextln: }
|
||||
|
||||
function %isub_imm(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = iconst.i32 2
|
||||
v2 = isub v0, v1
|
||||
return v2
|
||||
}
|
||||
; sameln: function %isub_imm
|
||||
; nextln: block0(v0: i32):
|
||||
; nextln: v1 = iconst.i32 2
|
||||
; nextln: v2 = iadd_imm v0, -2
|
||||
; nextln: return v2
|
||||
; nextln: }
|
||||
|
||||
function %icmp_imm(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = iconst.i32 2
|
||||
v2 = icmp slt v0, v1
|
||||
v3 = bint.i32 v2
|
||||
return v3
|
||||
}
|
||||
; sameln: function %icmp_imm
|
||||
; nextln: block0(v0: i32):
|
||||
; nextln: v1 = iconst.i32 2
|
||||
; nextln: v2 = icmp_imm slt v0, 2
|
||||
; nextln: v3 = bint.i32 v2
|
||||
; nextln: return v3
|
||||
; nextln: }
|
||||
|
||||
function %ifcmp_imm(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = iconst.i32 2
|
||||
v2 = ifcmp v0, v1
|
||||
brif eq v2, block1
|
||||
jump block2
|
||||
|
||||
block1:
|
||||
v3 = iconst.i32 1
|
||||
return v3
|
||||
|
||||
block2:
|
||||
v4 = iconst.i32 2
|
||||
return v4
|
||||
}
|
||||
; sameln: function %ifcmp_imm
|
||||
; nextln: block0(v0: i32):
|
||||
; nextln: v1 = iconst.i32 2
|
||||
; nextln: v2 = ifcmp_imm v0, 2
|
||||
; nextln: brif eq v2, block1
|
||||
; nextln: jump block2
|
||||
; nextln:
|
||||
; nextln: block1:
|
||||
; nextln: v3 = iconst.i32 1
|
||||
; nextln: return v3
|
||||
; nextln:
|
||||
; nextln: block2:
|
||||
; nextln: v4 = iconst.i32 2
|
||||
; nextln: return v4
|
||||
; nextln: }
|
||||
|
||||
function %brz_bint(i32) {
|
||||
block0(v0: i32):
|
||||
v3 = icmp_imm slt v0, 0
|
||||
v1 = bint.i32 v3
|
||||
v2 = select v1, v1, v1
|
||||
trapz v1, user0
|
||||
brz v1, block1
|
||||
jump block2
|
||||
|
||||
block1:
|
||||
return
|
||||
|
||||
block2:
|
||||
return
|
||||
}
|
||||
; sameln: function %brz_bint
|
||||
; nextln: (v0: i32):
|
||||
; nextln: v3 = icmp_imm slt v0, 0
|
||||
; nextln: v1 = bint.i32 v3
|
||||
; nextln: v2 = select v3, v1, v1
|
||||
; nextln: trapz v3, user0
|
||||
; nextln: brnz v3, block2
|
||||
; nextln: jump block1
|
||||
|
||||
function %irsub_imm(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = iconst.i32 2
|
||||
v2 = isub v1, v0
|
||||
return v2
|
||||
}
|
||||
; sameln: function %irsub_imm
|
||||
; nextln: block0(v0: i32):
|
||||
; nextln: v1 = iconst.i32 2
|
||||
; nextln: v2 = irsub_imm v0, 2
|
||||
; nextln: return v2
|
||||
; nextln: }
|
||||
|
||||
;; Sign-extensions.
|
||||
|
||||
;; 8 -> 16
|
||||
function %uextend_8_16() -> i16 {
|
||||
block0:
|
||||
v0 = iconst.i16 37
|
||||
v1 = ishl_imm v0, 8
|
||||
v2 = ushr_imm v1, 8
|
||||
return v2
|
||||
}
|
||||
; sameln: function %uextend_8_16
|
||||
; nextln: block0:
|
||||
; nextln: v0 = iconst.i16 37
|
||||
; nextln: v1 = ishl_imm v0, 8
|
||||
; nextln: v3 = ireduce.i8 v0
|
||||
; nextln: v2 = uextend.i16 v3
|
||||
; nextln: return v2
|
||||
; nextln: }
|
||||
|
||||
function %sextend_8_16() -> i16 {
|
||||
block0:
|
||||
v0 = iconst.i16 37
|
||||
v1 = ishl_imm v0, 8
|
||||
v2 = sshr_imm v1, 8
|
||||
return v2
|
||||
}
|
||||
; sameln: function %sextend_8_16
|
||||
; nextln: block0:
|
||||
; nextln: v0 = iconst.i16 37
|
||||
; nextln: v1 = ishl_imm v0, 8
|
||||
; nextln: v3 = ireduce.i8 v0
|
||||
; nextln: v2 = sextend.i16 v3
|
||||
; nextln: return v2
|
||||
; nextln: }
|
||||
|
||||
;; 8 -> 32
|
||||
function %uextend_8_32() -> i32 {
|
||||
block0:
|
||||
v0 = iconst.i32 37
|
||||
v1 = ishl_imm v0, 24
|
||||
v2 = ushr_imm v1, 24
|
||||
return v2
|
||||
}
|
||||
; sameln: function %uextend_8_32
|
||||
; nextln: block0:
|
||||
; nextln: v0 = iconst.i32 37
|
||||
; nextln: v1 = ishl_imm v0, 24
|
||||
; nextln: v3 = ireduce.i8 v0
|
||||
; nextln: v2 = uextend.i32 v3
|
||||
; nextln: return v2
|
||||
; nextln: }
|
||||
|
||||
function %sextend_8_32() -> i32 {
|
||||
block0:
|
||||
v0 = iconst.i32 37
|
||||
v1 = ishl_imm v0, 24
|
||||
v2 = sshr_imm v1, 24
|
||||
return v2
|
||||
}
|
||||
; sameln: function %sextend_8_32
|
||||
; nextln: block0:
|
||||
; nextln: v0 = iconst.i32 37
|
||||
; nextln: v1 = ishl_imm v0, 24
|
||||
; nextln: v3 = ireduce.i8 v0
|
||||
; nextln: v2 = sextend.i32 v3
|
||||
; nextln: return v2
|
||||
; nextln: }
|
||||
|
||||
;; 16 -> 32
|
||||
function %uextend_16_32() -> i32 {
|
||||
block0:
|
||||
v0 = iconst.i32 37
|
||||
v1 = ishl_imm v0, 16
|
||||
v2 = ushr_imm v1, 16
|
||||
return v2
|
||||
}
|
||||
; sameln: function %uextend_16_32
|
||||
; nextln: block0:
|
||||
; nextln: v0 = iconst.i32 37
|
||||
; nextln: v1 = ishl_imm v0, 16
|
||||
; nextln: v3 = ireduce.i16 v0
|
||||
; nextln: v2 = uextend.i32 v3
|
||||
; nextln: return v2
|
||||
; nextln: }
|
||||
|
||||
function %sextend_16_32() -> i32 {
|
||||
block0:
|
||||
v0 = iconst.i32 37
|
||||
v1 = ishl_imm v0, 16
|
||||
v2 = sshr_imm v1, 16
|
||||
return v2
|
||||
}
|
||||
; sameln: function %sextend_16_32
|
||||
; nextln: block0:
|
||||
; nextln: v0 = iconst.i32 37
|
||||
; nextln: v1 = ishl_imm v0, 16
|
||||
; nextln: v3 = ireduce.i16 v0
|
||||
; nextln: v2 = sextend.i32 v3
|
||||
; nextln: return v2
|
||||
; nextln: }
|
||||
|
||||
;; 8 -> 64
|
||||
function %uextend_8_64() -> i64 {
|
||||
block0:
|
||||
v0 = iconst.i64 37
|
||||
v1 = ishl_imm v0, 56
|
||||
v2 = ushr_imm v1, 56
|
||||
return v2
|
||||
}
|
||||
; sameln: function %uextend_8_64
|
||||
; nextln: block0:
|
||||
; nextln: v0 = iconst.i64 37
|
||||
; nextln: v1 = ishl_imm v0, 56
|
||||
; nextln: v3 = ireduce.i8 v0
|
||||
; nextln: v2 = uextend.i64 v3
|
||||
; nextln: return v2
|
||||
; nextln: }
|
||||
|
||||
function %sextend_8_64() -> i64 {
|
||||
block0:
|
||||
v0 = iconst.i64 37
|
||||
v1 = ishl_imm v0, 56
|
||||
v2 = sshr_imm v1, 56
|
||||
return v2
|
||||
}
|
||||
; sameln: function %sextend_8_64
|
||||
; nextln: block0:
|
||||
; nextln: v0 = iconst.i64 37
|
||||
; nextln: v1 = ishl_imm v0, 56
|
||||
; nextln: v3 = ireduce.i8 v0
|
||||
; nextln: v2 = sextend.i64 v3
|
||||
; nextln: return v2
|
||||
; nextln: }
|
||||
|
||||
;; 16 -> 64
|
||||
function %uextend_16_64() -> i64 {
|
||||
block0:
|
||||
v0 = iconst.i64 37
|
||||
v1 = ishl_imm v0, 48
|
||||
v2 = ushr_imm v1, 48
|
||||
return v2
|
||||
}
|
||||
; sameln: function %uextend_16_64
|
||||
; nextln: block0:
|
||||
; nextln: v0 = iconst.i64 37
|
||||
; nextln: v1 = ishl_imm v0, 48
|
||||
; nextln: v3 = ireduce.i16 v0
|
||||
; nextln: v2 = uextend.i64 v3
|
||||
; nextln: return v2
|
||||
; nextln: }
|
||||
|
||||
function %sextend_16_64() -> i64 {
|
||||
block0:
|
||||
v0 = iconst.i64 37
|
||||
v1 = ishl_imm v0, 48
|
||||
v2 = sshr_imm v1, 48
|
||||
return v2
|
||||
}
|
||||
; sameln: function %sextend_16_64
|
||||
; nextln: block0:
|
||||
; nextln: v0 = iconst.i64 37
|
||||
; nextln: v1 = ishl_imm v0, 48
|
||||
; nextln: v3 = ireduce.i16 v0
|
||||
; nextln: v2 = sextend.i64 v3
|
||||
; nextln: return v2
|
||||
; nextln: }
|
||||
|
||||
;; 32 -> 64
|
||||
function %uextend_32_64() -> i64 {
|
||||
block0:
|
||||
v0 = iconst.i64 37
|
||||
v1 = ishl_imm v0, 32
|
||||
v2 = ushr_imm v1, 32
|
||||
return v2
|
||||
}
|
||||
; sameln: function %uextend_32_64
|
||||
; nextln: block0:
|
||||
; nextln: v0 = iconst.i64 37
|
||||
; nextln: v1 = ishl_imm v0, 32
|
||||
; nextln: v3 = ireduce.i32 v0
|
||||
; nextln: v2 = uextend.i64 v3
|
||||
; nextln: return v2
|
||||
; nextln: }
|
||||
|
||||
function %sextend_32_64() -> i64 {
|
||||
block0:
|
||||
v0 = iconst.i64 37
|
||||
v1 = ishl_imm v0, 32
|
||||
v2 = sshr_imm v1, 32
|
||||
return v2
|
||||
}
|
||||
; sameln: function %sextend_32_64
|
||||
; nextln: block0:
|
||||
; nextln: v0 = iconst.i64 37
|
||||
; nextln: v1 = ishl_imm v0, 32
|
||||
; nextln: v3 = ireduce.i32 v0
|
||||
; nextln: v2 = sextend.i64 v3
|
||||
; nextln: return v2
|
||||
; nextln: }
|
||||
|
||||
function %add_imm_fold(i32) -> i32 {
|
||||
block0(v0: i32):
|
||||
v1 = iadd_imm v0, 42
|
||||
v2 = iadd_imm v1, -42
|
||||
return v2
|
||||
}
|
||||
; sameln: function %add_imm_fold(i32)
|
||||
; nextln: block0(v0: i32):
|
||||
; nextln: v2 -> v0
|
||||
; nextln: v1 = iadd_imm v0, 42
|
||||
; nextln: nop
|
||||
; nextln: return v2
|
||||
@@ -1,21 +0,0 @@
|
||||
test peepmatic
|
||||
target aarch64
|
||||
target x86_64
|
||||
|
||||
;; The `isub` is a no-op, but we can't replace the whole `isub` instruction with
|
||||
;; its `v2` operand's instruction because `v2` is one of many results. Instead,
|
||||
;; we need to make an alias `v3 -> v2`.
|
||||
|
||||
function %replace_inst_with_alias() -> i32 {
|
||||
sig0 = (i32, i32) -> i32, i32
|
||||
fn0 = u0:0 sig0
|
||||
|
||||
block0:
|
||||
v0 = iconst.i32 0
|
||||
v1, v2 = call fn0(v0, v0)
|
||||
v3 = isub v2, v0
|
||||
; check: v0 = iconst.i32 0
|
||||
; nextln: v1, v2 = call fn0(v0, v0)
|
||||
; nextln: v3 -> v2
|
||||
return v3
|
||||
}
|
||||
@@ -44,7 +44,6 @@ mod test_domtree;
|
||||
mod test_interpret;
|
||||
mod test_legalizer;
|
||||
mod test_licm;
|
||||
mod test_peepmatic;
|
||||
mod test_preopt;
|
||||
mod test_print_cfg;
|
||||
mod test_run;
|
||||
@@ -119,7 +118,6 @@ fn new_subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn subtest::SubTest>
|
||||
"interpret" => test_interpret::subtest(parsed),
|
||||
"legalizer" => test_legalizer::subtest(parsed),
|
||||
"licm" => test_licm::subtest(parsed),
|
||||
"peepmatic" => test_peepmatic::subtest(parsed),
|
||||
"preopt" => test_preopt::subtest(parsed),
|
||||
"print-cfg" => test_print_cfg::subtest(parsed),
|
||||
"run" => test_run::subtest(parsed),
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
//! Test command for `peepmatic`-generated peephole optimizers.
|
||||
|
||||
use crate::subtest::{run_filecheck, Context, SubTest};
|
||||
use cranelift_codegen;
|
||||
use cranelift_codegen::ir::Function;
|
||||
use cranelift_reader::TestCommand;
|
||||
use std::borrow::Cow;
|
||||
|
||||
struct TestPreopt;
|
||||
|
||||
pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
|
||||
assert_eq!(parsed.command, "peepmatic");
|
||||
if parsed.options.is_empty() {
|
||||
Ok(Box::new(TestPreopt))
|
||||
} else {
|
||||
anyhow::bail!("No options allowed on {}", parsed);
|
||||
}
|
||||
}
|
||||
|
||||
impl SubTest for TestPreopt {
|
||||
fn name(&self) -> &'static str {
|
||||
"peepmatic"
|
||||
}
|
||||
|
||||
fn is_mutating(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn needs_isa(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(&self, func: Cow<Function>, context: &Context) -> anyhow::Result<()> {
|
||||
let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());
|
||||
let isa = context.isa.expect("preopt needs an ISA");
|
||||
|
||||
comp_ctx.compute_cfg();
|
||||
comp_ctx
|
||||
.preopt(isa)
|
||||
.map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, Into::into(e)))?;
|
||||
let text = &comp_ctx.func.display().to_string();
|
||||
log::debug!("After peepmatic-based simple_preopt:\n{}", text);
|
||||
|
||||
// Only actually run the filecheck if peepmatic is enabled, because it
|
||||
// can generate slightly different code (alias a result vs replace an
|
||||
// instruction) than the non-peepmatic versions of peephole
|
||||
// optimizations. Note that the non-`peepmatic` results can be tested
|
||||
// with the `test simple_preopt` subtest.
|
||||
if cfg!(feature = "enable-peepmatic") {
|
||||
run_filecheck(&text, context)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,16 +41,6 @@ impl SubTest for TestSimplePreopt {
|
||||
.map_err(|e| crate::pretty_anyhow_error(&comp_ctx.func, e))?;
|
||||
let text = &comp_ctx.func.display().to_string();
|
||||
log::debug!("After simple_preopt:\n{}", text);
|
||||
|
||||
// Only actually run the filecheck if peepmatic is *not* enabled,
|
||||
// because it can generate slightly different code (alias a result vs
|
||||
// replace an instruction) than the non-peepmatic versions of peephole
|
||||
// optimizations. Note that the `peepmatic`-based results can be tested
|
||||
// with the `test peepmatic` subtest.
|
||||
if cfg!(feature = "enable-peepmatic") {
|
||||
Ok(())
|
||||
} else {
|
||||
run_filecheck(&text, context)
|
||||
}
|
||||
run_filecheck(&text, context)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
[package]
|
||||
name = "peepmatic"
|
||||
version = "0.78.0"
|
||||
authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
|
||||
edition = "2018"
|
||||
license = "Apache-2.0 WITH LLVM-exception"
|
||||
description = "DSL and compiler for generating peephole optimizers"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.27"
|
||||
peepmatic-automata = { version = "=0.78.0", path = "crates/automata", features = ["dot"] }
|
||||
peepmatic-macro = { version = "=0.78.0", path = "crates/macro" }
|
||||
peepmatic-runtime = { version = "=0.78.0", path = "crates/runtime", features = ["construct"] }
|
||||
peepmatic-traits = { version = "=0.78.0", path = "crates/traits" }
|
||||
serde = { version = "1.0.105", features = ["derive"] }
|
||||
wast = "38.0.0"
|
||||
z3 = { version = "0.7.1", features = ["static-link-z3"] }
|
||||
|
||||
[dev-dependencies]
|
||||
peepmatic-test-operator = { path = "crates/test-operator" }
|
||||
@@ -1,220 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
--- LLVM Exceptions to the Apache 2.0 License ----
|
||||
|
||||
As an exception, if, as a result of your compiling your source code, portions
|
||||
of this Software are embedded into an Object form of such source code, you
|
||||
may redistribute such embedded portions in such Object form without complying
|
||||
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
|
||||
|
||||
In addition, if you combine or link compiled forms of this Software with
|
||||
software that is licensed under the GPLv2 ("Combined Software") and if a
|
||||
court of competent jurisdiction determines that the patent provision (Section
|
||||
3), the indemnity provision (Section 9) or other Section of the License
|
||||
conflicts with the conditions of the GPLv2, you may retroactively and
|
||||
prospectively choose to deem waived or otherwise exclude such Section(s) of
|
||||
the License, but only in their entirety and only with respect to the Combined
|
||||
Software.
|
||||
|
||||
@@ -1,418 +0,0 @@
|
||||
<div align="center">
|
||||
<h1><code>peepmatic</code></h1>
|
||||
|
||||
<p>
|
||||
<b>
|
||||
<code>peepmatic</code> is a DSL and compiler for peephole optimizers for
|
||||
<a href="https://github.com/bytecodealliance/wasmtime/tree/main/cranelift#readme">Cranelift</a>.
|
||||
</b>
|
||||
</p>
|
||||
|
||||
<img src="https://github.com/fitzgen/peepmatic/workflows/Rust/badge.svg"/>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
|
||||
|
||||
- [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)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
## About
|
||||
|
||||
Peepmatic is a DSL for peephole optimizations and compiler for generating
|
||||
peephole optimizers from them. The user writes a set of optimizations in the
|
||||
DSL, and then `peepmatic` compiles the set of optimizations into an efficient
|
||||
peephole optimizer:
|
||||
|
||||
```
|
||||
DSL ----peepmatic----> Peephole Optimizer
|
||||
```
|
||||
|
||||
The generated peephole optimizer has all of its optimizations' left-hand sides
|
||||
collapsed into a compact automata that makes matching candidate instruction
|
||||
sequences fast.
|
||||
|
||||
The DSL's optimizations may be written by hand or discovered mechanically with a
|
||||
superoptimizer like [Souper][]. Eventually, `peepmatic` should have a verifier
|
||||
that ensures that the DSL's optimizations are sound, similar to what [Alive][]
|
||||
does for LLVM optimizations.
|
||||
|
||||
[Cranelift]: https://github.com/bytecodealliance/wasmtime/tree/main/cranelift#readme
|
||||
[Souper]: https://github.com/google/souper
|
||||
[Alive]: https://github.com/AliveToolkit/alive2
|
||||
|
||||
## Example
|
||||
|
||||
This snippet of our DSL describes optimizations for removing redundant
|
||||
bitwise-or instructions that are no-ops:
|
||||
|
||||
```lisp
|
||||
(=> (bor $x (bor $x $y))
|
||||
(bor $x $y))
|
||||
|
||||
(=> (bor $y (bor $x $y))
|
||||
(bor $x $y))
|
||||
|
||||
(=> (bor (bor $x $y) $x)
|
||||
(bor $x $y))
|
||||
|
||||
(=> (bor (bor $x $y) $y)
|
||||
(bor $x $y))
|
||||
```
|
||||
|
||||
When compiled into a peephole optimizer automaton, they look like this:
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
(=> <left-hand-side> <right-hand-side>)
|
||||
```
|
||||
|
||||
### Variables
|
||||
|
||||
Variables begin with a dollar sign and are followed by lowercase letters,
|
||||
numbers, hyphens, and underscores: `$x`, `$y`, `$my-var`, `$operand2`.
|
||||
|
||||
Left-hand side patterns may contain variables that match any kind of
|
||||
sub-expression and give it a name so that it may be reused in the right-hand
|
||||
side.
|
||||
|
||||
```lisp
|
||||
;; Replace `x + 0` with simply `x`.
|
||||
(=> (iadd $x 0)
|
||||
$x)
|
||||
```
|
||||
|
||||
Within a pattern, every occurrence of a variable with the same name must match
|
||||
the same value. That is `(iadd $x $x)` matches `(iadd 1 1)` but does not match
|
||||
`(iadd 1 2)`. This lets us write optimizations such as this:
|
||||
|
||||
```lisp
|
||||
;; Xor'ing a value with itself is always zero.
|
||||
(=> (bxor $x $x)
|
||||
(iconst 0))
|
||||
```
|
||||
|
||||
### Constants
|
||||
|
||||
We've already seen specific integer literals and wildcard variables in patterns,
|
||||
but we can also match any constant. These are written similar to variables, but
|
||||
use uppercase letters rather than lowercase: `$C`, `$MY-CONST`, and `$OPERAND2`.
|
||||
|
||||
For example, we can use constant patterns to combine an `iconst` and `iadd` into
|
||||
a single `iadd_imm` instruction:
|
||||
|
||||
```lisp
|
||||
(=> (iadd (iconst $C) $x)
|
||||
(iadd_imm $C $x))
|
||||
```
|
||||
|
||||
### Nested Patterns
|
||||
|
||||
Patterns can also match nested operations with their own nesting:
|
||||
|
||||
```lisp
|
||||
(=> (bor $x (bor $x $y))
|
||||
(bor $x $y))
|
||||
```
|
||||
|
||||
### Preconditions and Unquoting
|
||||
|
||||
Let's reconsider our first example optimization:
|
||||
|
||||
```lisp
|
||||
(=> (imul $x 2)
|
||||
(ishl $x 1))
|
||||
```
|
||||
|
||||
This optimization is a little too specific. Here is another version of this
|
||||
optimization that we'd like to support:
|
||||
|
||||
```lisp
|
||||
(=> (imul $x 4)
|
||||
(ishl $x 2))
|
||||
```
|
||||
|
||||
We don't want to have to write out all instances of this general class of
|
||||
optimizations! That would be a lot of repetition and could also bloat the size
|
||||
of our generated peephole optimizer's matching automata.
|
||||
|
||||
Instead, we can generalize this optimization by matching any multiplication by a
|
||||
power of two constant `C` and replacing it with a shift left of `log2(C)`.
|
||||
|
||||
First, rather than match `2` directly, we want to match any constant variable `C`:
|
||||
|
||||
```lisp
|
||||
(imul $x $C)
|
||||
```
|
||||
|
||||
Note that variables begin with lowercase letters, while constants begin with
|
||||
uppercase letters. Both the constant pattern `$C` and variable pattern `$x` will
|
||||
match `5`, but only the variable pattern `$x` will match a whole sub-expression
|
||||
like `(iadd 1 2)`. The constant pattern `$C` only matches constant values.
|
||||
|
||||
Next, we augment our left-hand side's pattern with a **precondition** that the
|
||||
constant `$C` must be a power of two. Preconditions are introduced by wrapping
|
||||
a pattern in the `when` form:
|
||||
|
||||
```lisp
|
||||
;; Our new left-hand side, augmenting a pattern with a precondition!
|
||||
(when
|
||||
;; The pattern matching multiplication by a constant value.
|
||||
(imul $x $C)
|
||||
|
||||
;; The precondition that $C must be a power of two.
|
||||
(is-power-of-two $C))
|
||||
```
|
||||
|
||||
In the right-hand side, we use **unquoting** to perform compile-time evaluation
|
||||
of `log2($C)`. Unquoting is done with the `$(...)` form:
|
||||
|
||||
```lisp
|
||||
;; Our new right-hand side, using unqouting to do compile-time evaluation of
|
||||
;; constants that were matched and bound in the left-hand side!
|
||||
(ishl $x $(log2 $C))
|
||||
```
|
||||
|
||||
Finally, here is the general optimization putting our new left-hand and
|
||||
right-hand sides together:
|
||||
|
||||
```lisp
|
||||
(=> (when (imul $x $C)
|
||||
(is-power-of-two $C))
|
||||
(ishl $x $(log2 $C)))
|
||||
```
|
||||
|
||||
### Bit Widths
|
||||
|
||||
Similar to how Cranelift's instructions are bit-width polymorphic, `peepmatic`
|
||||
optimizations are also bit-width polymorphic. Unless otherwise specified, a
|
||||
pattern will match expressions manipulating `i32`s just the same as expressions
|
||||
manipulating `i64`s, etc... An optimization that doesn't constrain its pattern's
|
||||
bit widths must be valid for all bit widths:
|
||||
|
||||
* 1
|
||||
* 8
|
||||
* 16
|
||||
* 32
|
||||
* 64
|
||||
* 128
|
||||
|
||||
To constrain an optimization to only match `i32`s, for example, you can use the
|
||||
`bit-width` precondition:
|
||||
|
||||
```lisp
|
||||
(=> (when (iadd $x $y)
|
||||
(bit-width $x 32)
|
||||
(bit-width $y 32))
|
||||
...)
|
||||
```
|
||||
|
||||
Alternatively, you can ascribe a type to an operation by putting the type inside
|
||||
curly brackets after the operator, like this:
|
||||
|
||||
```lisp
|
||||
(=> (when (sextend{i64} (ireduce{i32} $x))
|
||||
(bit-width $x 64))
|
||||
(sshr (ishl $x 32) 32))
|
||||
```
|
||||
|
||||
## Implementation
|
||||
|
||||
Peepmatic has roughly four phases:
|
||||
|
||||
1. Parsing
|
||||
2. Type Checking
|
||||
3. Linearization
|
||||
4. Automatization
|
||||
|
||||
(I say "roughly" because there are a couple micro-passes that happen after
|
||||
linearization and before automatization. But those are the four main phases.)
|
||||
|
||||
### Parsing
|
||||
|
||||
Parsing transforms the DSL source text into an abstract syntax tree (AST).
|
||||
|
||||
We use [the `wast` crate][wast]. It gives us nicely formatted errors with source
|
||||
context, as well as some other generally nice-to-have parsing infrastructure.
|
||||
|
||||
Relevant source files:
|
||||
|
||||
* `src/parser.rs`
|
||||
* `src/ast.rs`
|
||||
|
||||
[wast]: https://crates.io/crates/wast
|
||||
|
||||
### Type Checking
|
||||
|
||||
Type checking operates on the AST. It checks that types and bit widths in the
|
||||
optimizations are all valid. For example, it ensures that the type and bit width
|
||||
of an optimization's left-hand side is the same as its right-hand side, because
|
||||
it doesn't make sense to replace an integer expression with a boolean
|
||||
expression.
|
||||
|
||||
After type checking is complete, certain AST nodes are assigned a type and bit
|
||||
width, that are later used in linearization and when matching and applying
|
||||
optimizations.
|
||||
|
||||
We walk the AST and gather type constraints. Every constraint is associated with
|
||||
a span in the source file. We hand these constraints off to Z3. In the case that
|
||||
there are type errors (i.e. Z3 returns `unsat`), we get the constraints that are
|
||||
in conflict with each other via `z3::Solver::get_unsat_core` and report the type
|
||||
errors to the user, with the source context, thanks to the constraints'
|
||||
associated spans.
|
||||
|
||||
Using Z3 not only makes implementing type checking easier than it otherwise
|
||||
would be, but makes it that much easier to extend type checking with searching
|
||||
for counterexample inputs in the future. That is, inputs for which the RHS is
|
||||
not equivalent to the LHS, implying that the optimization is unsound.
|
||||
|
||||
Relevant source files:
|
||||
|
||||
* `src/verify.rs`
|
||||
|
||||
### Linearization
|
||||
|
||||
Linearization takes the AST of optimizations and converts each optimization into
|
||||
a linear form. The goal is to make automaton construction easier in the
|
||||
automatization step, as well as simplifying the language to make matching and
|
||||
applying optimizations easier.
|
||||
|
||||
Each optimization's left-hand side is converted into a sequence of
|
||||
|
||||
* match operation,
|
||||
* path to the instruction/value/immediate to which the operation is applied, and
|
||||
* expected result of the operation.
|
||||
|
||||
All match operations must have the expected result for the optimization to be
|
||||
applicable to an instruction sequence.
|
||||
|
||||
Each optimization's right-hand side is converted into a sequence of build
|
||||
actions. These are commands that describe how to construct the right-hand side,
|
||||
given that the left-hand side has been matched.
|
||||
|
||||
Relevant source files:
|
||||
|
||||
* `src/linearize.rs`
|
||||
* `src/linear_passes.rs`
|
||||
* `crates/runtime/src/linear.rs`
|
||||
|
||||
### Automatization
|
||||
|
||||
Automatization takes a set of linear optimizations and combines them into a
|
||||
transducer automaton. This automaton is the final, compiled peephole
|
||||
optimizations. The goal is to de-duplicate as much as we can from all the linear
|
||||
optimizations, producing as compact and cache-friendly a representation as we
|
||||
can.
|
||||
|
||||
Plain automata can tell you whether it matches an input string. It can be
|
||||
thought of as a compact representation of a set of strings. A transducer is a
|
||||
type of automaton that doesn't just match input strings, but can map them to
|
||||
output values. It can be thought of as a compact representation of a dictionary
|
||||
or map. By using transducers, we de-duplicate not only the prefix and suffix of
|
||||
the match operations, but also the right-hand side build actions.
|
||||
|
||||
Each state in the emitted transducers is associated with a match operation and
|
||||
path. The transitions out of that state are over the result of the match
|
||||
operation. Each transition optionally accumulates some RHS build actions. By the
|
||||
time we reach a final state, the RHS build actions are complete and can be
|
||||
interpreted to apply the matched optimization.
|
||||
|
||||
The relevant source files for constructing the transducer automaton are:
|
||||
|
||||
* `src/automatize.rs`
|
||||
* `crates/automata/src/lib.rs`
|
||||
|
||||
The relevant source files for the runtime that interprets the transducers and
|
||||
applies optimizations are:
|
||||
|
||||
* `crates/runtime/src/optimizations.rs`
|
||||
* `crates/runtime/src/optimizer.rs`
|
||||
|
||||
## References
|
||||
|
||||
I found these resources helpful when designing `peepmatic`:
|
||||
|
||||
* [Extending tree pattern matching for application to peephole
|
||||
optimizations](https://pure.tue.nl/ws/portalfiles/portal/125543109/Thesis_JanekvOirschot.pdf)
|
||||
by van Oirschot
|
||||
|
||||
* [Interpreted Pattern Match Execution for
|
||||
MLIR](https://drive.google.com/drive/folders/1hb_sXbdMbIz95X-aaa6Vf5wSYRwsJuve)
|
||||
by Jeff Niu
|
||||
|
||||
* [Direct Construction of Minimal Acyclic Subsequential
|
||||
Transducers](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.24.3698&rep=rep1&type=pdf)
|
||||
by Mihov and Maurel
|
||||
|
||||
* [Index 1,600,000,000 Keys with Automata and
|
||||
Rust](https://blog.burntsushi.net/transducers/) and [the `fst`
|
||||
crate](https://crates.io/crates/fst) by Andrew Gallant
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
Thanks to [Jubi Taneja], [Dan Gohman], [John Regehr], and [Nuno Lopes] for their
|
||||
input in design discussions and for sharing helpful resources!
|
||||
|
||||
[Jubi Taneja]: https://www.cs.utah.edu/~jubi/
|
||||
[Dan Gohman]: https://github.com/sunfishcode
|
||||
[John Regehr]: https://www.cs.utah.edu/~regehr/
|
||||
[Nuno Lopes]: http://web.ist.utl.pt/nuno.lopes/
|
||||
@@ -1,21 +0,0 @@
|
||||
[package]
|
||||
name = "peepmatic-automata"
|
||||
version = "0.78.0"
|
||||
authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
|
||||
edition = "2018"
|
||||
license = "Apache-2.0 WITH LLVM-exception"
|
||||
description = "Finite-state transducer automata"
|
||||
|
||||
# FIXME(rust-lang/cargo#9300): uncomment once that lands
|
||||
# [package.metadata.docs.rs]
|
||||
# all-features = true
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0.106", optional = true }
|
||||
|
||||
[features]
|
||||
# Enable support for generating GraphViz Dot files that can be used to visually
|
||||
# render an automaton.
|
||||
#
|
||||
# https://en.wikipedia.org/wiki/DOT_%28graph_description_language%29
|
||||
dot = []
|
||||
@@ -1,220 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
--- LLVM Exceptions to the Apache 2.0 License ----
|
||||
|
||||
As an exception, if, as a result of your compiling your source code, portions
|
||||
of this Software are embedded into an Object form of such source code, you
|
||||
may redistribute such embedded portions in such Object form without complying
|
||||
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
|
||||
|
||||
In addition, if you combine or link compiled forms of this Software with
|
||||
software that is licensed under the GPLv2 ("Combined Software") and if a
|
||||
court of competent jurisdiction determines that the patent provision (Section
|
||||
3), the indemnity provision (Section 9) or other Section of the License
|
||||
conflicts with the conditions of the GPLv2, you may retroactively and
|
||||
prospectively choose to deem waived or otherwise exclude such Section(s) of
|
||||
the License, but only in their entirety and only with respect to the Combined
|
||||
Software.
|
||||
|
||||
@@ -1,273 +0,0 @@
|
||||
//! Helpers for generating [GraphViz
|
||||
//! Dot](https://graphviz.gitlab.io/_pages/pdf/dotguide.pdf) files to visually
|
||||
//! render automata.
|
||||
//!
|
||||
//! **This module only exists when the `"dot"` cargo feature is enabled.**
|
||||
|
||||
use crate::{Automaton, Output, State};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::fs;
|
||||
use std::hash::Hash;
|
||||
use std::io::{self, Write};
|
||||
use std::path::Path;
|
||||
|
||||
/// Format the user-provided bits of an `Automaton` for Graphviz Dot output.
|
||||
///
|
||||
/// There are two provided implementations of `DotFmt`:
|
||||
///
|
||||
/// * [`DebugDotFmt`][crate::dot::DebugDotFmt] -- format each type parameter
|
||||
/// with its `std::fmt::Debug` implementation.
|
||||
///
|
||||
/// * [`DisplayDotFmt`][crate::dot::DisplayDotFmt] -- format each type parameter
|
||||
/// with its `std::fmt::Display` implementation.
|
||||
///
|
||||
/// You can also implement this trait yourself if your type parameters don't
|
||||
/// implement `Debug` or `Display`, or if you want to format them in some other
|
||||
/// way.
|
||||
pub trait DotFmt<TAlphabet, TState, TOutput> {
|
||||
/// Format a transition edge: `from ---input---> to`.
|
||||
///
|
||||
/// This will be inside an [HTML
|
||||
/// label](https://www.graphviz.org/doc/info/shapes.html#html), so you may
|
||||
/// use balanced HTML tags.
|
||||
fn fmt_transition(
|
||||
&self,
|
||||
w: &mut impl Write,
|
||||
from: Option<&TState>,
|
||||
input: &TAlphabet,
|
||||
to: Option<&TState>,
|
||||
) -> io::Result<()>;
|
||||
|
||||
/// Format the custom data associated with a state.
|
||||
///
|
||||
/// This will be inside an [HTML
|
||||
/// label](https://www.graphviz.org/doc/info/shapes.html#html), so you may
|
||||
/// use balanced HTML tags.
|
||||
fn fmt_state(&self, w: &mut impl Write, state: &TState) -> io::Result<()>;
|
||||
|
||||
/// Format a transition's output or the final output of a final state.
|
||||
///
|
||||
/// This will be inside an [HTML
|
||||
/// label](https://www.graphviz.org/doc/info/shapes.html#html), so you may
|
||||
/// use balanced HTML tags.
|
||||
fn fmt_output(&self, w: &mut impl Write, output: &TOutput) -> io::Result<()>;
|
||||
}
|
||||
|
||||
impl<TAlphabet, TState, TOutput> Automaton<TAlphabet, TState, TOutput>
|
||||
where
|
||||
TAlphabet: Clone + Eq + Hash + Ord,
|
||||
TState: Clone + Eq + Hash,
|
||||
TOutput: Output,
|
||||
{
|
||||
/// Write this `Automaton` out as a [GraphViz
|
||||
/// Dot](https://graphviz.gitlab.io/_pages/pdf/dotguide.pdf) file at the
|
||||
/// given path.
|
||||
///
|
||||
/// The `formatter` parameter controls how `TAlphabet`, `TState`, and
|
||||
/// `TOutput` are rendered. See the [`DotFmt`][crate::dot::DotFmt] trait for
|
||||
/// details.
|
||||
///
|
||||
/// **This method only exists when the `"dot"` cargo feature is enabled.**
|
||||
pub fn write_dot_file(
|
||||
&self,
|
||||
formatter: &impl DotFmt<TAlphabet, TState, TOutput>,
|
||||
path: impl AsRef<Path>,
|
||||
) -> io::Result<()> {
|
||||
let mut file = fs::File::create(path)?;
|
||||
self.write_dot(formatter, &mut file)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write this `Automaton` out to the given write-able as a [GraphViz
|
||||
/// Dot](https://graphviz.gitlab.io/_pages/pdf/dotguide.pdf) file.
|
||||
///
|
||||
/// The `formatter` parameter controls how `TAlphabet`, `TState`, and
|
||||
/// `TOutput` are rendered. See the [`DotFmt`][crate::dot::DotFmt] trait for
|
||||
/// details.
|
||||
///
|
||||
/// **This method only exists when the `"dot"` cargo feature is enabled.**
|
||||
pub fn write_dot(
|
||||
&self,
|
||||
formatter: &impl DotFmt<TAlphabet, TState, TOutput>,
|
||||
w: &mut impl Write,
|
||||
) -> io::Result<()> {
|
||||
writeln!(w, "digraph {{")?;
|
||||
writeln!(w, " rankdir = \"LR\";")?;
|
||||
writeln!(w, " nodesep = 2;")?;
|
||||
|
||||
// Fake state for the incoming arrow to the start state.
|
||||
writeln!(w, " \"\" [shape = none];")?;
|
||||
|
||||
// Each state, its associated custom data, and its final output.
|
||||
for (i, state_data) in self.state_data.iter().enumerate() {
|
||||
write!(
|
||||
w,
|
||||
r#" state_{i} [shape = {shape}, label = <<table border="0"><tr><td cellpadding="5">{i}</td></tr><tr><td cellpadding="5">"#,
|
||||
i = i,
|
||||
shape = if self.final_states.contains_key(&State(i as u32)) {
|
||||
"doublecircle"
|
||||
} else {
|
||||
"circle"
|
||||
}
|
||||
)?;
|
||||
if let Some(state_data) = state_data {
|
||||
formatter.fmt_state(w, state_data)?;
|
||||
} else {
|
||||
write!(w, "(no state data)")?;
|
||||
}
|
||||
write!(w, "</td></tr>")?;
|
||||
if let Some(final_output) = self.final_states.get(&State(i as u32)) {
|
||||
write!(w, r#"<tr><td cellpadding="5" align="left">"#)?;
|
||||
formatter.fmt_output(w, final_output)?;
|
||||
write!(w, "</td></tr>")?;
|
||||
}
|
||||
writeln!(w, "</table>>];")?;
|
||||
}
|
||||
|
||||
// Fake transition to the start state.
|
||||
writeln!(w, r#" "" -> state_{};"#, self.start_state.0)?;
|
||||
|
||||
// Transitions between states and their outputs.
|
||||
for (from, transitions) in self.transitions.iter().enumerate() {
|
||||
for (input, (to, output)) in transitions {
|
||||
write!(
|
||||
w,
|
||||
r#" state_{from} -> state_{to} [label = <<table border="0"><tr><td cellpadding="5" align="left">Input:</td><td cellpadding="5" align="left">"#,
|
||||
from = from,
|
||||
to = to.0,
|
||||
)?;
|
||||
formatter.fmt_transition(
|
||||
w,
|
||||
self.state_data[from].as_ref(),
|
||||
input,
|
||||
self.state_data[to.0 as usize].as_ref(),
|
||||
)?;
|
||||
write!(
|
||||
w,
|
||||
r#"</td></tr><tr><td cellpadding="5" align="left">Output:</td><td cellpadding="5" align="left">"#,
|
||||
)?;
|
||||
formatter.fmt_output(w, output)?;
|
||||
writeln!(w, "</td></tr></table>>];")?;
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(w, "}}")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Format an `Automaton`'s `TAlphabet`, `TState`, and `TOutput` with their
|
||||
/// `std::fmt::Debug` implementations.
|
||||
#[derive(Debug)]
|
||||
pub struct DebugDotFmt;
|
||||
|
||||
impl<TAlphabet, TState, TOutput> DotFmt<TAlphabet, TState, TOutput> for DebugDotFmt
|
||||
where
|
||||
TAlphabet: Debug,
|
||||
TState: Debug,
|
||||
TOutput: Debug,
|
||||
{
|
||||
fn fmt_transition(
|
||||
&self,
|
||||
w: &mut impl Write,
|
||||
_from: Option<&TState>,
|
||||
input: &TAlphabet,
|
||||
_to: Option<&TState>,
|
||||
) -> io::Result<()> {
|
||||
write!(w, r#"<font face="monospace">{:?}</font>"#, input)
|
||||
}
|
||||
|
||||
fn fmt_state(&self, w: &mut impl Write, state: &TState) -> io::Result<()> {
|
||||
write!(w, r#"<font face="monospace">{:?}</font>"#, state)
|
||||
}
|
||||
|
||||
fn fmt_output(&self, w: &mut impl Write, output: &TOutput) -> io::Result<()> {
|
||||
write!(w, r#"<font face="monospace">{:?}</font>"#, output)
|
||||
}
|
||||
}
|
||||
|
||||
/// Format an `Automaton`'s `TAlphabet`, `TState`, and `TOutput` with their
|
||||
/// `std::fmt::Display` implementations.
|
||||
#[derive(Debug)]
|
||||
pub struct DisplayDotFmt;
|
||||
|
||||
impl<TAlphabet, TState, TOutput> DotFmt<TAlphabet, TState, TOutput> for DisplayDotFmt
|
||||
where
|
||||
TAlphabet: Display,
|
||||
TState: Display,
|
||||
TOutput: Display,
|
||||
{
|
||||
fn fmt_transition(
|
||||
&self,
|
||||
w: &mut impl Write,
|
||||
_from: Option<&TState>,
|
||||
input: &TAlphabet,
|
||||
_to: Option<&TState>,
|
||||
) -> io::Result<()> {
|
||||
write!(w, "{}", input)
|
||||
}
|
||||
|
||||
fn fmt_state(&self, w: &mut impl Write, state: &TState) -> io::Result<()> {
|
||||
write!(w, "{}", state)
|
||||
}
|
||||
|
||||
fn fmt_output(&self, w: &mut impl Write, output: &TOutput) -> io::Result<()> {
|
||||
write!(w, "{}", output)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::Builder;
|
||||
|
||||
#[test]
|
||||
fn test_write_dot() {
|
||||
let mut builder = Builder::<char, (), u64>::new();
|
||||
|
||||
// Insert "mon" -> 1
|
||||
let mut insertion = builder.insert();
|
||||
insertion.next('m', 1).next('o', 0).next('n', 0);
|
||||
insertion.finish();
|
||||
|
||||
// Insert "sat" -> 6
|
||||
let mut insertion = builder.insert();
|
||||
insertion.next('s', 6).next('a', 0).next('t', 0);
|
||||
insertion.finish();
|
||||
|
||||
// Insert "sun" -> 0
|
||||
let mut insertion = builder.insert();
|
||||
insertion.next('s', 0).next('u', 0).next('n', 0);
|
||||
insertion.finish();
|
||||
|
||||
let automata = builder.finish();
|
||||
|
||||
let expected = r#"
|
||||
digraph {
|
||||
rankdir = "LR";
|
||||
nodesep = 2;
|
||||
"" [shape = none];
|
||||
state_0 [shape = doublecircle, label = <<table border="0"><tr><td cellpadding="5">0</td></tr><tr><td cellpadding="5">(no state data)</td></tr><tr><td cellpadding="5" align="left"><font face="monospace">0</font></td></tr></table>>];
|
||||
state_1 [shape = circle, label = <<table border="0"><tr><td cellpadding="5">1</td></tr><tr><td cellpadding="5">(no state data)</td></tr></table>>];
|
||||
state_2 [shape = circle, label = <<table border="0"><tr><td cellpadding="5">2</td></tr><tr><td cellpadding="5">(no state data)</td></tr></table>>];
|
||||
state_3 [shape = circle, label = <<table border="0"><tr><td cellpadding="5">3</td></tr><tr><td cellpadding="5">(no state data)</td></tr></table>>];
|
||||
state_4 [shape = circle, label = <<table border="0"><tr><td cellpadding="5">4</td></tr><tr><td cellpadding="5">(no state data)</td></tr></table>>];
|
||||
state_5 [shape = circle, label = <<table border="0"><tr><td cellpadding="5">5</td></tr><tr><td cellpadding="5">(no state data)</td></tr></table>>];
|
||||
"" -> state_5;
|
||||
state_1 -> state_0 [label = <<table border="0"><tr><td cellpadding="5" align="left">Input:</td><td cellpadding="5" align="left"><font face="monospace">'n'</font></td></tr><tr><td cellpadding="5" align="left">Output:</td><td cellpadding="5" align="left"><font face="monospace">0</font></td></tr></table>>];
|
||||
state_2 -> state_1 [label = <<table border="0"><tr><td cellpadding="5" align="left">Input:</td><td cellpadding="5" align="left"><font face="monospace">'o'</font></td></tr><tr><td cellpadding="5" align="left">Output:</td><td cellpadding="5" align="left"><font face="monospace">0</font></td></tr></table>>];
|
||||
state_3 -> state_0 [label = <<table border="0"><tr><td cellpadding="5" align="left">Input:</td><td cellpadding="5" align="left"><font face="monospace">'t'</font></td></tr><tr><td cellpadding="5" align="left">Output:</td><td cellpadding="5" align="left"><font face="monospace">0</font></td></tr></table>>];
|
||||
state_4 -> state_3 [label = <<table border="0"><tr><td cellpadding="5" align="left">Input:</td><td cellpadding="5" align="left"><font face="monospace">'a'</font></td></tr><tr><td cellpadding="5" align="left">Output:</td><td cellpadding="5" align="left"><font face="monospace">6</font></td></tr></table>>];
|
||||
state_4 -> state_1 [label = <<table border="0"><tr><td cellpadding="5" align="left">Input:</td><td cellpadding="5" align="left"><font face="monospace">'u'</font></td></tr><tr><td cellpadding="5" align="left">Output:</td><td cellpadding="5" align="left"><font face="monospace">0</font></td></tr></table>>];
|
||||
state_5 -> state_2 [label = <<table border="0"><tr><td cellpadding="5" align="left">Input:</td><td cellpadding="5" align="left"><font face="monospace">'m'</font></td></tr><tr><td cellpadding="5" align="left">Output:</td><td cellpadding="5" align="left"><font face="monospace">1</font></td></tr></table>>];
|
||||
state_5 -> state_4 [label = <<table border="0"><tr><td cellpadding="5" align="left">Input:</td><td cellpadding="5" align="left"><font face="monospace">'s'</font></td></tr><tr><td cellpadding="5" align="left">Output:</td><td cellpadding="5" align="left"><font face="monospace">0</font></td></tr></table>>];
|
||||
}
|
||||
"#;
|
||||
|
||||
let mut buf = vec![];
|
||||
automata.write_dot(&DebugDotFmt, &mut buf).unwrap();
|
||||
let actual = String::from_utf8(buf).unwrap();
|
||||
eprintln!("{}", actual);
|
||||
assert_eq!(expected.trim(), actual.trim());
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,130 +0,0 @@
|
||||
use crate::Output;
|
||||
use std::cmp;
|
||||
use std::hash::Hash;
|
||||
|
||||
impl Output for u64 {
|
||||
fn empty() -> Self {
|
||||
0
|
||||
}
|
||||
|
||||
fn prefix(a: &Self, b: &Self) -> Self {
|
||||
cmp::min(*a, *b)
|
||||
}
|
||||
|
||||
fn difference(a: &Self, b: &Self) -> Self {
|
||||
a - b
|
||||
}
|
||||
|
||||
fn concat(a: &Self, b: &Self) -> Self {
|
||||
a + b
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Output for Vec<T>
|
||||
where
|
||||
T: Clone + Eq + Hash,
|
||||
{
|
||||
fn empty() -> Self {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.is_empty()
|
||||
}
|
||||
|
||||
fn prefix(a: &Self, b: &Self) -> Self {
|
||||
a.iter()
|
||||
.cloned()
|
||||
.zip(b.iter().cloned())
|
||||
.take_while(|(a, b)| a == b)
|
||||
.map(|(a, _)| a)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn difference(a: &Self, b: &Self) -> Self {
|
||||
let i = a
|
||||
.iter()
|
||||
.zip(b.iter())
|
||||
.position(|(a, b)| a != b)
|
||||
.unwrap_or(cmp::min(a.len(), b.len()));
|
||||
a[i..].to_vec()
|
||||
}
|
||||
|
||||
fn concat(a: &Self, b: &Self) -> Self {
|
||||
let mut c = a.clone();
|
||||
c.extend(b.iter().cloned());
|
||||
c
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Output for Box<[T]>
|
||||
where
|
||||
T: Clone + Eq + Hash,
|
||||
{
|
||||
fn empty() -> Self {
|
||||
vec![].into_boxed_slice()
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
fn prefix(a: &Self, b: &Self) -> Self {
|
||||
a.iter()
|
||||
.cloned()
|
||||
.zip(b.iter().cloned())
|
||||
.take_while(|(a, b)| a == b)
|
||||
.map(|(a, _)| a)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn difference(a: &Self, b: &Self) -> Self {
|
||||
let i = a
|
||||
.iter()
|
||||
.zip(b.iter())
|
||||
.position(|(a, b)| a != b)
|
||||
.unwrap_or(cmp::min(a.len(), b.len()));
|
||||
a[i..].to_vec().into_boxed_slice()
|
||||
}
|
||||
|
||||
fn concat(a: &Self, b: &Self) -> Self {
|
||||
let mut c = a.clone().to_vec();
|
||||
c.extend(b.iter().cloned());
|
||||
c.into_boxed_slice()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::Output;
|
||||
use std::fmt::Debug;
|
||||
|
||||
// Assert the laws that `Output` requires for correctness. `a` and `b`
|
||||
// should be two different instances of an `Output` type.
|
||||
fn assert_laws<O>(a: O, b: O)
|
||||
where
|
||||
O: Clone + Debug + Output,
|
||||
{
|
||||
// Law 1
|
||||
assert_eq!(O::concat(&O::empty(), &a), a.clone());
|
||||
|
||||
// Law 2
|
||||
assert_eq!(O::prefix(&b, &a), O::prefix(&a, &b));
|
||||
|
||||
// Law 3
|
||||
assert_eq!(O::prefix(&O::empty(), &a), O::empty());
|
||||
|
||||
// Law 4
|
||||
assert_eq!(O::difference(&O::concat(&a, &b), &a), b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn impl_for_u64() {
|
||||
assert_laws(3, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn impl_for_vec() {
|
||||
assert_laws(vec![0, 1, 2, 3], vec![0, 2, 4, 6]);
|
||||
}
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
//! `serde::Serialize` and `serde::Deserialize` implementations for `Automaton`.
|
||||
//!
|
||||
//! Rather than prefix each serialized field with which field it is, we always
|
||||
//! serialize fields in alphabetical order. Make sure to maintain this if you
|
||||
//! add or remove fields!
|
||||
//!
|
||||
//! Each time you add/remove a field, or change serialization in any other way,
|
||||
//! make sure to bump `SERIALIZATION_VERSION`.
|
||||
|
||||
use crate::{Automaton, Output, State};
|
||||
use serde::{
|
||||
de::{self, Deserializer, SeqAccess, Visitor},
|
||||
ser::SerializeTupleStruct,
|
||||
Deserialize, Serialize, Serializer,
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
use std::hash::Hash;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
const SERIALIZATION_VERSION: u32 = 1;
|
||||
|
||||
impl Serialize for State {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_u32(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for State {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Ok(State(deserializer.deserialize_u32(U32Visitor)?))
|
||||
}
|
||||
}
|
||||
|
||||
struct U32Visitor;
|
||||
|
||||
impl<'de> Visitor<'de> for U32Visitor {
|
||||
type Value = u32;
|
||||
|
||||
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str("an integer between `0` and `2^32 - 1`")
|
||||
}
|
||||
|
||||
fn visit_u8<E>(self, value: u8) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
Ok(u32::from(value))
|
||||
}
|
||||
|
||||
fn visit_u32<E>(self, value: u32) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
use std::u32;
|
||||
if value <= u64::from(u32::MAX) {
|
||||
Ok(value as u32)
|
||||
} else {
|
||||
Err(E::custom(format!("u32 out of range: {}", value)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<TAlphabet, TState, TOutput> Serialize for Automaton<TAlphabet, TState, TOutput>
|
||||
where
|
||||
TAlphabet: Serialize + Clone + Eq + Hash + Ord,
|
||||
TState: Serialize + Clone + Eq + Hash,
|
||||
TOutput: Serialize + Output,
|
||||
{
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let Automaton {
|
||||
final_states,
|
||||
start_state,
|
||||
state_data,
|
||||
transitions,
|
||||
} = self;
|
||||
|
||||
let mut s = serializer.serialize_tuple_struct("Automaton", 5)?;
|
||||
s.serialize_field(&SERIALIZATION_VERSION)?;
|
||||
s.serialize_field(final_states)?;
|
||||
s.serialize_field(start_state)?;
|
||||
s.serialize_field(state_data)?;
|
||||
s.serialize_field(transitions)?;
|
||||
s.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, TAlphabet, TState, TOutput> Deserialize<'de> for Automaton<TAlphabet, TState, TOutput>
|
||||
where
|
||||
TAlphabet: 'de + Deserialize<'de> + Clone + Eq + Hash + Ord,
|
||||
TState: 'de + Deserialize<'de> + Clone + Eq + Hash,
|
||||
TOutput: 'de + Deserialize<'de> + Output,
|
||||
{
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_tuple_struct(
|
||||
"Automaton",
|
||||
5,
|
||||
AutomatonVisitor {
|
||||
phantom: PhantomData,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct AutomatonVisitor<'de, TAlphabet, TState, TOutput>
|
||||
where
|
||||
TAlphabet: 'de + Deserialize<'de> + Clone + Eq + Hash + Ord,
|
||||
TState: 'de + Deserialize<'de> + Clone + Eq + Hash,
|
||||
TOutput: 'de + Deserialize<'de> + Output,
|
||||
{
|
||||
phantom: PhantomData<&'de (TAlphabet, TState, TOutput)>,
|
||||
}
|
||||
|
||||
impl<'de, TAlphabet, TState, TOutput> Visitor<'de>
|
||||
for AutomatonVisitor<'de, TAlphabet, TState, TOutput>
|
||||
where
|
||||
TAlphabet: 'de + Deserialize<'de> + Clone + Eq + Hash + Ord,
|
||||
TState: 'de + Deserialize<'de> + Clone + Eq + Hash,
|
||||
TOutput: 'de + Deserialize<'de> + Output,
|
||||
{
|
||||
type Value = Automaton<TAlphabet, TState, TOutput>;
|
||||
|
||||
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.write_str("Automaton")
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: SeqAccess<'de>,
|
||||
{
|
||||
match seq.next_element::<u32>()? {
|
||||
Some(v) if v == SERIALIZATION_VERSION => {}
|
||||
Some(v) => {
|
||||
return Err(de::Error::invalid_value(
|
||||
de::Unexpected::Unsigned(v as u64),
|
||||
&self,
|
||||
));
|
||||
}
|
||||
None => {
|
||||
return Err(de::Error::invalid_length(
|
||||
0,
|
||||
&"Automaton expects 5 elements",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
let final_states = match seq.next_element::<BTreeMap<State, TOutput>>()? {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
return Err(de::Error::invalid_length(
|
||||
1,
|
||||
&"Automaton expects 5 elements",
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let start_state = match seq.next_element::<State>()? {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
return Err(de::Error::invalid_length(
|
||||
2,
|
||||
&"Automaton expects 5 elements",
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let state_data = match seq.next_element::<Vec<Option<TState>>>()? {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
return Err(de::Error::invalid_length(
|
||||
3,
|
||||
&"Automaton expects 5 elements",
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let transitions = match seq.next_element::<Vec<BTreeMap<TAlphabet, (State, TOutput)>>>()? {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
return Err(de::Error::invalid_length(
|
||||
4,
|
||||
&"Automaton expects 5 elements",
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let automata = Automaton {
|
||||
final_states,
|
||||
start_state,
|
||||
state_data,
|
||||
transitions,
|
||||
};
|
||||
|
||||
// Ensure that the deserialized automata is well-formed.
|
||||
automata
|
||||
.check_representation()
|
||||
.map_err(|msg| de::Error::custom(msg))?;
|
||||
|
||||
Ok(automata)
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
[package]
|
||||
name = "peepmatic-fuzzing"
|
||||
version = "0.66.0"
|
||||
authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
arbitrary = { version = "1.0.0", features = ["derive"] }
|
||||
bincode = "1.2.1"
|
||||
env_logger = "0.8.1"
|
||||
fst = "0.4.1"
|
||||
log = "0.4.8"
|
||||
peepmatic = { path = "../.." }
|
||||
peepmatic-automata = { path = "../automata", features = ["serde"] }
|
||||
peepmatic-runtime = { path = "../runtime", features = ["construct"] }
|
||||
peepmatic-test = { path = "../test" }
|
||||
peepmatic-test-operator = { path = "../test-operator" }
|
||||
peepmatic-traits = { path = "../traits" }
|
||||
rand = { version = "0.8.3", features = ["small_rng"] }
|
||||
serde = "1.0.106"
|
||||
wast = "38.0.0"
|
||||
@@ -1,201 +0,0 @@
|
||||
//! Helpers for fuzzing the `peepmatic-automata` crate.
|
||||
|
||||
use peepmatic_automata::{Automaton, Builder, Output};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::hash::Hash;
|
||||
|
||||
fn serde_roundtrip<TAlphabet, TState, TOutput>(
|
||||
automata: Automaton<TAlphabet, TState, TOutput>,
|
||||
) -> Automaton<TAlphabet, TState, TOutput>
|
||||
where
|
||||
TAlphabet: Serialize + for<'de> Deserialize<'de> + Clone + Eq + Hash + Ord,
|
||||
TState: Serialize + for<'de> Deserialize<'de> + Clone + Eq + Hash,
|
||||
TOutput: Serialize + for<'de> Deserialize<'de> + Output,
|
||||
{
|
||||
let encoded: Vec<u8> = bincode::serialize(&automata).expect("should serialize OK");
|
||||
bincode::deserialize(&encoded).expect("should deserialize OK")
|
||||
}
|
||||
|
||||
const MAX_AUTOMATON_KEY_LEN: usize = 256;
|
||||
|
||||
/// Construct an automaton from the the given input-output pairs, and assert
|
||||
/// that:
|
||||
///
|
||||
/// * Putting in each of the input strings should result in the expected output
|
||||
/// string.
|
||||
///
|
||||
/// * Putting in an input string that is not one of the given inputs from our
|
||||
/// input-output pairs should never yield an output value.
|
||||
pub fn simple_automata(input_output_pairs: Vec<Vec<(u8, Vec<u8>)>>) {
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
let full_input = |pair: &[(u8, Vec<u8>)]| {
|
||||
let mut full_input = vec![];
|
||||
for (input, _) in pair {
|
||||
full_input.push(*input);
|
||||
}
|
||||
full_input
|
||||
};
|
||||
|
||||
let mut inputs = HashSet::new();
|
||||
|
||||
let mut input_output_pairs: Vec<_> = input_output_pairs
|
||||
.into_iter()
|
||||
.filter(|pair| {
|
||||
if pair.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure that we don't generate huge input keys.
|
||||
let full_input = full_input(pair);
|
||||
if full_input.len() >= MAX_AUTOMATON_KEY_LEN {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make sure we don't have duplicate inputs.
|
||||
let is_new = inputs.insert(full_input);
|
||||
is_new
|
||||
})
|
||||
.collect();
|
||||
|
||||
input_output_pairs.sort_by(|a, b| full_input(a).cmp(&full_input(b)));
|
||||
|
||||
if input_output_pairs.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// A map from one of our concatenated input strings to its concatenated
|
||||
// output.
|
||||
let mut expected = HashMap::with_capacity(input_output_pairs.len());
|
||||
|
||||
let mut builder = Builder::<u8, (), Vec<u8>>::new();
|
||||
for pair in &input_output_pairs {
|
||||
let mut full_input = vec![];
|
||||
let mut full_output = vec![];
|
||||
|
||||
let mut ins = builder.insert();
|
||||
for (input, output) in pair.iter().cloned() {
|
||||
full_input.push(input);
|
||||
full_output.extend(output.iter().copied());
|
||||
|
||||
ins.next(input, output);
|
||||
}
|
||||
|
||||
let old = expected.insert(full_input, full_output);
|
||||
assert!(old.is_none());
|
||||
|
||||
ins.finish();
|
||||
}
|
||||
|
||||
let automata = builder.finish();
|
||||
let automata = serde_roundtrip(automata);
|
||||
|
||||
// Assert that each of our input strings yields the expected output.
|
||||
for (input, expected_output) in &expected {
|
||||
log::debug!("Testing input: {:?}", input);
|
||||
let actual_output = automata.get(input);
|
||||
assert!(actual_output.is_some());
|
||||
assert_eq!(actual_output.as_ref().unwrap(), expected_output);
|
||||
}
|
||||
|
||||
// Test that mutations of our input strings (that aren't themselves other
|
||||
// input strings!) do not yeild any output.
|
||||
for input in expected.keys() {
|
||||
for i in 0..input.len() {
|
||||
let mut mutated = input.clone();
|
||||
mutated[i] = mutated[i].wrapping_add(1);
|
||||
log::debug!("Testing mutated input: {:?}", mutated);
|
||||
if !expected.contains_key(&mutated) {
|
||||
assert!(automata.get(&mutated).is_none());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Do differential testing against the `fst` crate, which is another
|
||||
/// implementation of the algorithm we use for finite-state transducer
|
||||
/// construction in `peepmatic-automata`.
|
||||
pub fn fst_differential(map: HashMap<Vec<u8>, u64>) {
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
let mut inputs: Vec<_> = map
|
||||
.keys()
|
||||
.filter(|k| !k.is_empty() && k.len() < MAX_AUTOMATON_KEY_LEN)
|
||||
.cloned()
|
||||
.collect();
|
||||
inputs.sort();
|
||||
inputs.dedup();
|
||||
if inputs.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut fst = fst::MapBuilder::memory();
|
||||
let mut builder = Builder::<u8, (), u64>::new();
|
||||
|
||||
for inp in &inputs {
|
||||
fst.insert(inp, map[inp]).unwrap();
|
||||
|
||||
let mut ins = builder.insert();
|
||||
for (i, ch) in inp.iter().enumerate() {
|
||||
ins.next(*ch, if i == 0 { map[inp] } else { 0 });
|
||||
}
|
||||
ins.finish();
|
||||
}
|
||||
|
||||
let fst = fst.into_map();
|
||||
let automata = builder.finish();
|
||||
let automata = serde_roundtrip(automata);
|
||||
|
||||
for inp in inputs {
|
||||
// Check we have the same result as `fst` for inputs we know are in the
|
||||
// automata.
|
||||
log::debug!("Testing input {:?}", inp);
|
||||
let expected = fst.get(&inp).expect("`fst` should have entry for `inp`");
|
||||
let actual = automata
|
||||
.get(&inp)
|
||||
.expect("automata should have entry for `inp`");
|
||||
assert_eq!(expected, actual);
|
||||
|
||||
// Check that we have the same result as `fst` for inputs that may or
|
||||
// may not be in the automata.
|
||||
for i in 0..inp.len() {
|
||||
let mut mutated = inp.clone();
|
||||
mutated[i] = mutated[i].wrapping_add(1);
|
||||
log::debug!("Testing mutated input {:?}", mutated);
|
||||
let expected = fst.get(&mutated);
|
||||
let actual = automata.get(&mutated);
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn check_simple_automata() {
|
||||
crate::check(simple_automata);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_fst_differential() {
|
||||
crate::check(fst_differential);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_test_0() {
|
||||
simple_automata(vec![vec![(0, vec![0]), (0, vec![1])], vec![(0, vec![2])]]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_test_1() {
|
||||
fst_differential(vec![(vec![1, 3], 5), (vec![1, 2], 4)].into_iter().collect());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_test_2() {
|
||||
simple_automata(vec![vec![(0, vec![11]), (0, vec![])], vec![(0, vec![11])]]);
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
//! Fuzz testing utilities related to AST pattern matching.
|
||||
|
||||
use peepmatic_runtime::PeepholeOptimizations;
|
||||
use peepmatic_test_operator::TestOperator;
|
||||
use std::path::Path;
|
||||
use std::str;
|
||||
|
||||
// To avoid timeouts, don't deal with inputs larger than this.
|
||||
const MAX_LEN: usize = 2048;
|
||||
|
||||
/// Attempt to interpret the given bytes as UTF-8 and then compile them as if
|
||||
/// they were source text of our DSL.
|
||||
pub fn compile(data: &[u8]) {
|
||||
if data.len() > MAX_LEN {
|
||||
return;
|
||||
}
|
||||
|
||||
let source = match str::from_utf8(data) {
|
||||
Err(_) => return,
|
||||
Ok(s) => s,
|
||||
};
|
||||
|
||||
let opt = match peepmatic::compile_str::<TestOperator>(source, Path::new("fuzz")) {
|
||||
Err(_) => return,
|
||||
Ok(o) => o,
|
||||
};
|
||||
|
||||
// Should be able to serialize and deserialize the peephole optimizer.
|
||||
let opt_bytes = bincode::serialize(&opt).expect("should serialize peephole optimizations OK");
|
||||
let _: PeepholeOptimizations<TestOperator> =
|
||||
bincode::deserialize(&opt_bytes).expect("should deserialize peephole optimizations OK");
|
||||
|
||||
// Compiling the same source text again should be deterministic.
|
||||
let opt2 = peepmatic::compile_str::<TestOperator>(source, Path::new("fuzz"))
|
||||
.expect("should be able to compile source text again, if it compiled OK the first time");
|
||||
let opt2_bytes =
|
||||
bincode::serialize(&opt2).expect("should serialize second peephole optimizations OK");
|
||||
assert_eq!(opt_bytes, opt2_bytes);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn check_compile() {
|
||||
crate::check(|s: String| compile(s.as_bytes()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_0() {
|
||||
compile(
|
||||
b"
|
||||
(=> (bor (bor $x $y) $y) $x)
|
||||
(=> (bor (bor $x $z) $y) $x)
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_1() {
|
||||
compile(
|
||||
b"
|
||||
(=> (bor (bor $x $y) 0) $x)
|
||||
(=> (bor $x 0) $x)
|
||||
(=> (bor $y $x) $x)
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_2() {
|
||||
compile(
|
||||
b"
|
||||
(=> (sshr $x 11111111110) $x)
|
||||
",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,367 +0,0 @@
|
||||
//! Interpreting compiled peephole optimizations against test instruction sequences.
|
||||
|
||||
use peepmatic::{
|
||||
Constraint, Dfs, DynAstRef, Optimizations, Pattern, Span, TraversalEvent, ValueLiteral,
|
||||
Variable,
|
||||
};
|
||||
use peepmatic_runtime::{
|
||||
cc::ConditionCode,
|
||||
part::Constant,
|
||||
r#type::BitWidth,
|
||||
r#type::{Kind, Type},
|
||||
};
|
||||
use peepmatic_test::{Program, TestIsa};
|
||||
use peepmatic_test_operator::TestOperator;
|
||||
use peepmatic_traits::{TypingContext as TypingContextTrait, TypingRules};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::path::Path;
|
||||
use std::str;
|
||||
|
||||
/// Compile the given source text, and if it is a valid set of optimizations,
|
||||
/// then interpret the optimizations against test instruction sequences created
|
||||
/// to reflect the optimizations.
|
||||
pub fn interp(data: &[u8]) {
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
let source = match str::from_utf8(data) {
|
||||
Err(_) => return,
|
||||
Ok(s) => s,
|
||||
};
|
||||
|
||||
let peep_opts = match peepmatic::compile_str(source, Path::new("fuzz")) {
|
||||
Err(_) => return,
|
||||
Ok(o) => o,
|
||||
};
|
||||
let mut optimizer = peep_opts.optimizer(TestIsa {
|
||||
native_word_size_in_bits: 32,
|
||||
});
|
||||
|
||||
// Okay, we know it compiles and verifies alright, so (re)parse the AST.
|
||||
let buf = wast::parser::ParseBuffer::new(&source).unwrap();
|
||||
let ast = wast::parser::parse::<Optimizations<TestOperator>>(&buf).unwrap();
|
||||
|
||||
// And we need access to the assigned types, so re-verify it as well.
|
||||
peepmatic::verify(&ast).unwrap();
|
||||
|
||||
// Walk over each optimization and create an instruction sequence that
|
||||
// matches the optimization.
|
||||
let mut program = Program::default();
|
||||
for opt in &ast.optimizations {
|
||||
// The instruction sequence we generate must match an optimization (not
|
||||
// necessarily *this* optimization, if there is another that is more
|
||||
// specific but also matches) unless there is an `bit-width`
|
||||
// precondition or an implicit `bit-width` precondition via a type
|
||||
// ascription. When those things exist, we might have constructed
|
||||
// instructions with the wrong bit widths to match.
|
||||
let mut allow_no_match = false;
|
||||
|
||||
// The last instruction we generated. After we've generated the full
|
||||
// instruction sequence, this will be its root.
|
||||
let mut last_inst = None;
|
||||
|
||||
// Remember the instructions associated with variables and constants, so
|
||||
// that when they appear multiple times, we reuse the same instruction.
|
||||
let mut id_to_inst = HashMap::new();
|
||||
|
||||
// Map from a pattern's span to the instruction we generated for
|
||||
// it. This allows parent operations to get the instructions for their
|
||||
// children.
|
||||
let mut span_to_inst = BTreeMap::new();
|
||||
|
||||
for (te, lhs) in Dfs::new(&opt.lhs) {
|
||||
// NB: We use a post-order traversal because we want arguments to be
|
||||
// generated before they are used.
|
||||
if te != TraversalEvent::Exit {
|
||||
continue;
|
||||
}
|
||||
|
||||
match lhs {
|
||||
DynAstRef::Precondition(p) => {
|
||||
allow_no_match |= p.constraint == Constraint::BitWidth;
|
||||
}
|
||||
|
||||
DynAstRef::Pattern(Pattern::Operation(op)) => {
|
||||
allow_no_match |= op.r#type.get().is_some();
|
||||
|
||||
let num_imms = op.operator.immediates_arity() as usize;
|
||||
|
||||
// Generate this operation's immediates.
|
||||
let mut imm_tys = vec![];
|
||||
op.operator
|
||||
.immediate_types((), &mut TypingContext, &mut imm_tys);
|
||||
let imms: Vec<_> = op
|
||||
.operands
|
||||
.iter()
|
||||
.take(num_imms)
|
||||
.zip(imm_tys)
|
||||
.map(|(pat, ty)| match pat {
|
||||
Pattern::ValueLiteral(ValueLiteral::Integer(i)) => {
|
||||
Constant::Int(i.value as _, BitWidth::ThirtyTwo).into()
|
||||
}
|
||||
Pattern::ValueLiteral(ValueLiteral::Boolean(b)) => {
|
||||
Constant::Bool(b.value, BitWidth::One).into()
|
||||
}
|
||||
Pattern::ValueLiteral(ValueLiteral::ConditionCode(cc)) => cc.cc.into(),
|
||||
Pattern::Constant(_) | Pattern::Variable(_) => match ty {
|
||||
TypeOrConditionCode::ConditionCode => ConditionCode::Eq.into(),
|
||||
TypeOrConditionCode::Type(ty) => match ty.kind {
|
||||
Kind::Int => Constant::Int(1, ty.bit_width).into(),
|
||||
Kind::Bool => Constant::Bool(false, ty.bit_width).into(),
|
||||
Kind::Void | Kind::CpuFlags => {
|
||||
unreachable!("void and cpu flags cannot be immediates")
|
||||
}
|
||||
},
|
||||
},
|
||||
Pattern::Operation(_) => {
|
||||
unreachable!("operations not allowed as immediates")
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Generate (or collect already-generated) instructions for
|
||||
// this operation's arguments.
|
||||
let mut arg_tys = vec![];
|
||||
op.operator
|
||||
.parameter_types((), &mut TypingContext, &mut arg_tys);
|
||||
let args: Vec<_> = op
|
||||
.operands
|
||||
.iter()
|
||||
.skip(num_imms)
|
||||
.zip(arg_tys)
|
||||
.map(|(pat, ty)| match pat {
|
||||
Pattern::Operation(op) => span_to_inst[&op.span()],
|
||||
Pattern::ValueLiteral(ValueLiteral::Integer(i)) => program.r#const(
|
||||
Constant::Int(i.value as _, BitWidth::ThirtyTwo),
|
||||
BitWidth::ThirtyTwo,
|
||||
),
|
||||
Pattern::ValueLiteral(ValueLiteral::Boolean(b)) => program.r#const(
|
||||
Constant::Bool(b.value, BitWidth::One),
|
||||
BitWidth::ThirtyTwo,
|
||||
),
|
||||
Pattern::ValueLiteral(ValueLiteral::ConditionCode(_)) => {
|
||||
unreachable!("condition codes cannot be arguments")
|
||||
}
|
||||
Pattern::Constant(peepmatic::Constant { id, .. })
|
||||
| Pattern::Variable(Variable { id, .. }) => match ty {
|
||||
TypeOrConditionCode::Type(ty) => {
|
||||
*id_to_inst.entry(id).or_insert_with(|| match ty.kind {
|
||||
Kind::Int => program.r#const(
|
||||
Constant::Int(1, ty.bit_width),
|
||||
BitWidth::ThirtyTwo,
|
||||
),
|
||||
Kind::Bool => program.r#const(
|
||||
Constant::Bool(false, ty.bit_width),
|
||||
BitWidth::ThirtyTwo,
|
||||
),
|
||||
Kind::CpuFlags => {
|
||||
unreachable!("cpu flags cannot be an argument")
|
||||
}
|
||||
Kind::Void => unreachable!("void cannot be an argument"),
|
||||
})
|
||||
}
|
||||
TypeOrConditionCode::ConditionCode => {
|
||||
unreachable!("condition codes cannot be arguments")
|
||||
}
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
|
||||
let ty = match op.operator.result_type((), &mut TypingContext) {
|
||||
TypeOrConditionCode::Type(ty) => ty,
|
||||
TypeOrConditionCode::ConditionCode => {
|
||||
unreachable!("condition codes cannot be operation results")
|
||||
}
|
||||
};
|
||||
let inst = program.new_instruction(op.operator, ty, imms, args);
|
||||
last_inst = Some(inst);
|
||||
let old_inst = span_to_inst.insert(op.span(), inst);
|
||||
assert!(old_inst.is_none());
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
|
||||
// Run the optimizer on our newly generated instruction sequence.
|
||||
if let Some(inst) = last_inst {
|
||||
let replacement = optimizer.apply_one(&mut program, inst);
|
||||
assert!(
|
||||
replacement.is_some() || allow_no_match,
|
||||
"an optimization should match the generated instruction sequence"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, just try and run the optimizer on every instruction we
|
||||
// generated, just to potentially shake out some more bugs.
|
||||
let instructions: Vec<_> = program.instructions().map(|(k, _)| k).collect();
|
||||
for inst in instructions {
|
||||
let _ = optimizer.apply_one(&mut program, inst);
|
||||
}
|
||||
}
|
||||
|
||||
enum TypeOrConditionCode {
|
||||
Type(Type),
|
||||
ConditionCode,
|
||||
}
|
||||
|
||||
struct TypingContext;
|
||||
|
||||
impl<'a> TypingContextTrait<'a> for TypingContext {
|
||||
type Span = ();
|
||||
type TypeVariable = TypeOrConditionCode;
|
||||
|
||||
fn cc(&mut self, _: ()) -> Self::TypeVariable {
|
||||
TypeOrConditionCode::ConditionCode
|
||||
}
|
||||
|
||||
fn bNN(&mut self, _: ()) -> Self::TypeVariable {
|
||||
TypeOrConditionCode::Type(Type::b1())
|
||||
}
|
||||
|
||||
fn iNN(&mut self, _: ()) -> Self::TypeVariable {
|
||||
TypeOrConditionCode::Type(Type::i32())
|
||||
}
|
||||
|
||||
fn iMM(&mut self, _: ()) -> Self::TypeVariable {
|
||||
TypeOrConditionCode::Type(Type::i32())
|
||||
}
|
||||
|
||||
fn cpu_flags(&mut self, _: ()) -> Self::TypeVariable {
|
||||
TypeOrConditionCode::Type(Type::cpu_flags())
|
||||
}
|
||||
|
||||
fn b1(&mut self, _: ()) -> Self::TypeVariable {
|
||||
TypeOrConditionCode::Type(Type::b1())
|
||||
}
|
||||
|
||||
fn void(&mut self, _: ()) -> Self::TypeVariable {
|
||||
TypeOrConditionCode::Type(Type::void())
|
||||
}
|
||||
|
||||
fn bool_or_int(&mut self, _: ()) -> Self::TypeVariable {
|
||||
TypeOrConditionCode::Type(Type::b1())
|
||||
}
|
||||
|
||||
fn any_t(&mut self, _: ()) -> Self::TypeVariable {
|
||||
TypeOrConditionCode::Type(Type::i32())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn check_interp() {
|
||||
crate::check(|s: Vec<u8>| interp(String::from_utf8_lossy(&s).as_bytes()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_0() {
|
||||
interp(b"(=> (imul $x $x) $x)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_1() {
|
||||
interp(b"(=> (when (imul $x $C) (is-power-of-two $C)) $x)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_2() {
|
||||
interp(
|
||||
b"
|
||||
(=> (bor (bor $x $y) $x) (bor $x $y))
|
||||
(=> (bor (bor $x $C) 5) $x)
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_3() {
|
||||
interp(
|
||||
b"
|
||||
(=> (bor $y (bor $x 9)) $x)
|
||||
(=> (bor (bor $x $y) $x) $x)
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_4() {
|
||||
interp(
|
||||
b"
|
||||
(=> (bor $C 33) 0)
|
||||
(=> (bor $x 22) 1)
|
||||
(=> (bor $x 11) 2)
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_5() {
|
||||
interp(
|
||||
b"
|
||||
(=> (bor $y (bor $x $y)) (bor $x $y))
|
||||
(=> (bor (bor $x $y) $z) $x)
|
||||
(=> (bor (bor $x $y) $y) $x)
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_6() {
|
||||
interp(b"(=> (imul $x $f) of)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_7() {
|
||||
interp(
|
||||
b"
|
||||
(=> (when (sdiv $x $C)
|
||||
(fits-in-native-word $y))
|
||||
(sdiv $C $x))
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_9() {
|
||||
interp(
|
||||
b"
|
||||
(=> (when $x) $x)
|
||||
(=> (trapnz $x) (trapnz $x))
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_10() {
|
||||
interp(b"(=> (sshr{i1} $x 0) $x)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_11() {
|
||||
interp(
|
||||
b"
|
||||
(=> (when (ushr_imm $x (ishl 4 3))
|
||||
(bit-width $x 64))
|
||||
(sextend{i64} (ireduce{i32} $x)))
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_12() {
|
||||
interp(b"(=> (band $C1 (band_imm $C1 1)) 1)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_13() {
|
||||
interp(b"(=> (brz (icmp eq 0 $x)) (brz (ireduce{i32} $x)))");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_14() {
|
||||
interp(b"(=> (brz (icmp $E 0 $x)) (brz $x))");
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
//! Utilities for fuzzing.
|
||||
//!
|
||||
//! The actual fuzz targets are defined in `peepmatic/fuzz/*`. This crate just
|
||||
//! has oracles and generators for fuzzing.
|
||||
|
||||
#![deny(missing_debug_implementations)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use arbitrary::{Arbitrary, Unstructured};
|
||||
use rand::prelude::*;
|
||||
use std::fmt::Debug;
|
||||
use std::time;
|
||||
|
||||
pub mod automata;
|
||||
pub mod compile;
|
||||
pub mod interp;
|
||||
pub mod parser;
|
||||
|
||||
/// A quickcheck-style runner for fuzz targets.
|
||||
///
|
||||
/// This is *not* intended to replace a long-running, coverage-guided fuzzing
|
||||
/// engine like libFuzzer! This is only for defining quick, purely random tests
|
||||
/// for use with `cargo test` and CI.
|
||||
pub fn check<A>(mut f: impl FnMut(A))
|
||||
where
|
||||
A: Clone + Debug + for<'a> Arbitrary<'a>,
|
||||
{
|
||||
let seed = rand::thread_rng().gen();
|
||||
let mut rng = rand::rngs::SmallRng::seed_from_u64(seed);
|
||||
|
||||
const INITIAL_LENGTH: usize = 16;
|
||||
const MAX_LENGTH: usize = 4096;
|
||||
|
||||
let mut buf: Vec<u8> = (0..INITIAL_LENGTH).map(|_| rng.gen()).collect();
|
||||
let mut num_checked = 0;
|
||||
|
||||
let time_budget = time::Duration::from_secs(2);
|
||||
let then = time::Instant::now();
|
||||
|
||||
loop {
|
||||
if num_checked > 0 && time::Instant::now().duration_since(then) > time_budget {
|
||||
eprintln!("Checked {} random inputs.", num_checked);
|
||||
return;
|
||||
}
|
||||
|
||||
match <A as Arbitrary>::arbitrary_take_rest(Unstructured::new(&buf)) {
|
||||
Ok(input) => {
|
||||
num_checked += 1;
|
||||
eprintln!("Checking input: {:#?}", input);
|
||||
f(input.clone());
|
||||
}
|
||||
Err(e @ arbitrary::Error::NotEnoughData) => {
|
||||
eprintln!("warning: {}", e);
|
||||
if *buf.last().unwrap() == 0 {
|
||||
if buf.len() < MAX_LENGTH {
|
||||
let new_size = std::cmp::min(buf.len() * 2, MAX_LENGTH);
|
||||
eprintln!("Growing buffer size to {}", new_size);
|
||||
let delta = new_size - buf.len();
|
||||
buf.reserve(delta);
|
||||
for _ in 0..delta {
|
||||
buf.push(rng.gen());
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
// Regenerate `buf` in the loop below and see if that
|
||||
// fixes things...
|
||||
eprintln!("Regenerating buffer data.");
|
||||
}
|
||||
} else {
|
||||
// Shrink values in the end of `buf`, which is where
|
||||
// `Arbitrary` pulls container lengths from. Then try again.
|
||||
eprintln!("Shrinking buffer's tail values.");
|
||||
let i = (buf.len() as f64).sqrt() as usize;
|
||||
for j in i..buf.len() {
|
||||
buf[j] /= 2;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("warning: {}", e);
|
||||
// Usually this happens because `A` requires a sequence utf-8
|
||||
// bytes but its given sequence wasn't valid utf-8. Just skip
|
||||
// this iteration and try again after we've updated `buf` below.
|
||||
}
|
||||
};
|
||||
|
||||
// Double the size of the buffer every so often, so we don't only
|
||||
// explore small inputs.
|
||||
if num_checked == buf.len() {
|
||||
buf.resize(std::cmp::min(buf.len() * 2, MAX_LENGTH), 0);
|
||||
}
|
||||
|
||||
for i in 0..buf.len() {
|
||||
buf[i] = rng.gen();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
//! Utilities for fuzzing our DSL's parser.
|
||||
|
||||
use peepmatic::Optimizations;
|
||||
use peepmatic_test_operator::TestOperator;
|
||||
use std::str;
|
||||
|
||||
/// Attempt to parse the given string as if it were a snippet of our DSL.
|
||||
pub fn parse(data: &[u8]) {
|
||||
let source = match str::from_utf8(data) {
|
||||
Ok(s) => s,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
let buf = match wast::parser::ParseBuffer::new(&source) {
|
||||
Ok(buf) => buf,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
let _ = wast::parser::parse::<Optimizations<TestOperator>>(&buf);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn check_parse() {
|
||||
crate::check(|s: String| parse(s.as_bytes()));
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
[package]
|
||||
name = "peepmatic-macro"
|
||||
version = "0.78.0"
|
||||
authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
|
||||
edition = "2018"
|
||||
license = "Apache-2.0 WITH LLVM-exception"
|
||||
description = "Macros for peepmatic"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.9"
|
||||
quote = "1.0.3"
|
||||
syn = { version = "1.0.16", features = ['extra-traits'] }
|
||||
|
||||
[lib]
|
||||
proc_macro = true
|
||||
test = false
|
||||
doctest = false
|
||||
@@ -1,220 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
--- LLVM Exceptions to the Apache 2.0 License ----
|
||||
|
||||
As an exception, if, as a result of your compiling your source code, portions
|
||||
of this Software are embedded into an Object form of such source code, you
|
||||
may redistribute such embedded portions in such Object form without complying
|
||||
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
|
||||
|
||||
In addition, if you combine or link compiled forms of this Software with
|
||||
software that is licensed under the GPLv2 ("Combined Software") and if a
|
||||
court of competent jurisdiction determines that the patent provision (Section
|
||||
3), the indemnity provision (Section 9) or other Section of the License
|
||||
conflicts with the conditions of the GPLv2, you may retroactively and
|
||||
prospectively choose to deem waived or otherwise exclude such Section(s) of
|
||||
the License, but only in their entirety and only with respect to the Combined
|
||||
Software.
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
use quote::quote;
|
||||
use syn::DeriveInput;
|
||||
use syn::{parse_quote, GenericParam, Generics, Result};
|
||||
|
||||
pub fn derive_child_nodes(input: &DeriveInput) -> Result<impl quote::ToTokens> {
|
||||
let children = get_child_nodes(&input.data)?;
|
||||
let name = &input.ident;
|
||||
let generics = add_trait_bounds(input.generics.clone());
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
|
||||
Ok(quote! {
|
||||
impl #impl_generics ChildNodes<'a, 'a, TOperator> for #name #ty_generics #where_clause {
|
||||
fn child_nodes(&'a self, children: &mut impl Extend<DynAstRef<'a, TOperator>>) {
|
||||
#children
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn get_child_nodes(data: &syn::Data) -> Result<impl quote::ToTokens> {
|
||||
match data {
|
||||
syn::Data::Struct(s) => {
|
||||
let mut fields = vec![];
|
||||
|
||||
match &s.fields {
|
||||
syn::Fields::Named(n) => {
|
||||
for f in n.named.iter() {
|
||||
let opts = crate::PeepmaticOpts::from_attrs(&mut f.attrs.clone())?;
|
||||
|
||||
if opts.skip_child {
|
||||
continue;
|
||||
}
|
||||
|
||||
let field_name = f.ident.as_ref().unwrap();
|
||||
if opts.flatten {
|
||||
fields.push(quote! {
|
||||
self.#field_name.iter().map(DynAstRef::from)
|
||||
});
|
||||
} else {
|
||||
fields.push(quote! {
|
||||
std::iter::once(DynAstRef::from(&self.#field_name))
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
syn::Fields::Unnamed(u) => {
|
||||
for (i, f) in u.unnamed.iter().enumerate() {
|
||||
let opts = crate::PeepmaticOpts::from_attrs(&mut f.attrs.clone())?;
|
||||
if opts.skip_child {
|
||||
continue;
|
||||
}
|
||||
if opts.flatten {
|
||||
return Err(syn::Error::new(
|
||||
u.paren_token.span,
|
||||
"#[peepmatic(flatten)] is only allowed with named fields",
|
||||
));
|
||||
}
|
||||
fields.push(quote! {
|
||||
std::iter::once(DynAstRef::from(&self.#i))
|
||||
});
|
||||
}
|
||||
}
|
||||
syn::Fields::Unit => {}
|
||||
}
|
||||
|
||||
Ok(match fields.as_slice() {
|
||||
[] => quote! { let _ = children; },
|
||||
[f, rest @ ..] => {
|
||||
let rest = rest.iter().map(|f| {
|
||||
quote! {
|
||||
.chain(#f)
|
||||
}
|
||||
});
|
||||
quote! {
|
||||
children.extend( #f #( #rest )* );
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
syn::Data::Enum(e) => {
|
||||
let mut match_arms = vec![];
|
||||
for v in e.variants.iter() {
|
||||
match v.fields {
|
||||
syn::Fields::Unnamed(ref u) if u.unnamed.len() == 1 => {
|
||||
let variant = &v.ident;
|
||||
match_arms.push(quote! {
|
||||
Self::#variant(x) => children.extend(Some(x.into())),
|
||||
});
|
||||
}
|
||||
_ => panic!("#[derive(ChildNodes)] only supports enums whose variants all ahve a single unnamed field")
|
||||
}
|
||||
}
|
||||
Ok(quote! {
|
||||
match self {
|
||||
#( #match_arms )*
|
||||
}
|
||||
})
|
||||
}
|
||||
syn::Data::Union(_) => panic!("#[derive(ChildNodes)] is not supported on unions"),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_trait_bounds(mut generics: Generics) -> Generics {
|
||||
for param in &mut generics.params {
|
||||
if let GenericParam::Type(type_param) = param {
|
||||
if type_param.ident == "TOperator" {
|
||||
continue;
|
||||
}
|
||||
type_param
|
||||
.bounds
|
||||
.push(parse_quote!(ChildNodes<'a, 'a, TOperator>));
|
||||
}
|
||||
}
|
||||
generics
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
use quote::quote;
|
||||
use syn::DeriveInput;
|
||||
use syn::Result;
|
||||
|
||||
pub fn derive_into_dyn_ast_ref(input: &DeriveInput) -> Result<impl quote::ToTokens> {
|
||||
let ty = &input.ident;
|
||||
|
||||
let opts = crate::PeepmaticOpts::from_attrs(&mut input.attrs.clone())?;
|
||||
if opts.no_into_dyn_node {
|
||||
return Ok(quote! {});
|
||||
}
|
||||
|
||||
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||
|
||||
Ok(quote! {
|
||||
impl #impl_generics From<&'a #ty #ty_generics> for DynAstRef<'a, TOperator> #where_clause {
|
||||
#[inline]
|
||||
fn from(x: &'a #ty #ty_generics) -> Self {
|
||||
Self::#ty(x)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
extern crate proc_macro;
|
||||
|
||||
use crate::proc_macro::TokenStream;
|
||||
use proc_macro2::Span;
|
||||
use quote::quote;
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::DeriveInput;
|
||||
use syn::Error;
|
||||
use syn::{parse_macro_input, Ident, Result};
|
||||
|
||||
mod child_nodes;
|
||||
mod into_dyn_ast_ref;
|
||||
mod span;
|
||||
|
||||
#[proc_macro_derive(Ast, attributes(peepmatic))]
|
||||
pub fn derive_ast(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
|
||||
let span_impl = match span::derive_span(&input) {
|
||||
Ok(s) => s,
|
||||
Err(e) => return e.to_compile_error().into(),
|
||||
};
|
||||
|
||||
let child_nodes_impl = match child_nodes::derive_child_nodes(&input) {
|
||||
Ok(c) => c,
|
||||
Err(e) => return e.to_compile_error().into(),
|
||||
};
|
||||
|
||||
let into_dyn_ast_ref_impl = match into_dyn_ast_ref::derive_into_dyn_ast_ref(&input) {
|
||||
Ok(n) => n,
|
||||
Err(e) => return e.to_compile_error().into(),
|
||||
};
|
||||
|
||||
let expanded = quote! {
|
||||
#span_impl
|
||||
#child_nodes_impl
|
||||
#into_dyn_ast_ref_impl
|
||||
};
|
||||
|
||||
TokenStream::from(expanded)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct PeepmaticOpts {
|
||||
// `ChildNodes` options.
|
||||
skip_child: bool,
|
||||
flatten: bool,
|
||||
|
||||
// `From<&'a Self> for DynAstRef<'a>` options.
|
||||
no_into_dyn_node: bool,
|
||||
|
||||
// Peepmatic operator options.
|
||||
immediates_paren: syn::token::Paren,
|
||||
immediates: Vec<syn::Ident>,
|
||||
params_paren: syn::token::Paren,
|
||||
params: Vec<syn::Ident>,
|
||||
result: Option<syn::Ident>,
|
||||
}
|
||||
|
||||
impl Parse for PeepmaticOpts {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
enum Attr {
|
||||
Immediates(syn::token::Paren, Vec<syn::Ident>),
|
||||
Params(syn::token::Paren, Vec<syn::Ident>),
|
||||
Result(syn::Ident),
|
||||
NoIntoDynNode,
|
||||
SkipChild,
|
||||
Flatten,
|
||||
}
|
||||
|
||||
let attrs = Punctuated::<_, syn::token::Comma>::parse_terminated(input)?;
|
||||
let mut ret = PeepmaticOpts::default();
|
||||
for attr in attrs {
|
||||
match attr {
|
||||
Attr::Immediates(paren, imms) => {
|
||||
ret.immediates_paren = paren;
|
||||
ret.immediates = imms;
|
||||
}
|
||||
Attr::Params(paren, ps) => {
|
||||
ret.params_paren = paren;
|
||||
ret.params = ps;
|
||||
}
|
||||
Attr::Result(r) => ret.result = Some(r),
|
||||
Attr::NoIntoDynNode => ret.no_into_dyn_node = true,
|
||||
Attr::SkipChild => ret.skip_child = true,
|
||||
Attr::Flatten => ret.flatten = true,
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(ret);
|
||||
|
||||
impl Parse for Attr {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let attr: Ident = input.parse()?;
|
||||
if attr == "immediates" {
|
||||
let inner;
|
||||
let paren = syn::parenthesized!(inner in input);
|
||||
let imms = Punctuated::<_, syn::token::Comma>::parse_terminated(&inner)?;
|
||||
return Ok(Attr::Immediates(paren, imms.into_iter().collect()));
|
||||
}
|
||||
if attr == "params" {
|
||||
let inner;
|
||||
let paren = syn::parenthesized!(inner in input);
|
||||
let params = Punctuated::<_, syn::token::Comma>::parse_terminated(&inner)?;
|
||||
return Ok(Attr::Params(paren, params.into_iter().collect()));
|
||||
}
|
||||
if attr == "result" {
|
||||
let inner;
|
||||
syn::parenthesized!(inner in input);
|
||||
return Ok(Attr::Result(syn::Ident::parse(&inner)?));
|
||||
}
|
||||
if attr == "skip_child" {
|
||||
return Ok(Attr::SkipChild);
|
||||
}
|
||||
if attr == "no_into_dyn_node" {
|
||||
return Ok(Attr::NoIntoDynNode);
|
||||
}
|
||||
if attr == "flatten" {
|
||||
return Ok(Attr::Flatten);
|
||||
}
|
||||
return Err(Error::new(attr.span(), "unexpected attribute"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn peepmatic_attrs(attrs: &mut Vec<syn::Attribute>) -> TokenStream {
|
||||
let mut ret = proc_macro2::TokenStream::new();
|
||||
let ident = syn::Path::from(syn::Ident::new("peepmatic", Span::call_site()));
|
||||
for i in (0..attrs.len()).rev() {
|
||||
if attrs[i].path != ident {
|
||||
continue;
|
||||
}
|
||||
let attr = attrs.remove(i);
|
||||
let group = match attr.tokens.into_iter().next().unwrap() {
|
||||
proc_macro2::TokenTree::Group(g) => g,
|
||||
_ => panic!("#[peepmatic(...)] expected"),
|
||||
};
|
||||
ret.extend(group.stream());
|
||||
ret.extend(quote! { , });
|
||||
}
|
||||
return ret.into();
|
||||
}
|
||||
|
||||
impl PeepmaticOpts {
|
||||
pub(crate) fn from_attrs(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Self> {
|
||||
syn::parse(peepmatic_attrs(attrs))
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
use quote::quote;
|
||||
use syn::DeriveInput;
|
||||
use syn::{parse_quote, GenericParam, Generics, Result};
|
||||
|
||||
pub fn derive_span(input: &DeriveInput) -> Result<impl quote::ToTokens> {
|
||||
let ty = &input.ident;
|
||||
|
||||
let body = match &input.data {
|
||||
syn::Data::Struct(_) => quote! { self.span },
|
||||
syn::Data::Enum(e) => {
|
||||
let variants = e.variants.iter().map(|v| match v.fields {
|
||||
syn::Fields::Unnamed(ref fields) if fields.unnamed.len() == 1 => {
|
||||
let variant = &v.ident;
|
||||
quote! { #ty::#variant(x) => x.span(), }
|
||||
}
|
||||
_ => panic!(
|
||||
"derive(Ast) on enums only supports variants with a single, unnamed field"
|
||||
),
|
||||
});
|
||||
quote! {
|
||||
match self {
|
||||
#( #variants )*
|
||||
}
|
||||
}
|
||||
}
|
||||
syn::Data::Union(_) => {
|
||||
panic!("derive(Ast) can only be used with structs and enums, not unions")
|
||||
}
|
||||
};
|
||||
|
||||
let generics = add_span_trait_bounds(input.generics.clone());
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
|
||||
Ok(quote! {
|
||||
impl #impl_generics Span for #ty #ty_generics #where_clause {
|
||||
#[inline]
|
||||
fn span(&self) -> wast::Span {
|
||||
#body
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Add a bound `T: Span` to every type parameter `T`.
|
||||
fn add_span_trait_bounds(mut generics: Generics) -> Generics {
|
||||
for param in &mut generics.params {
|
||||
if let GenericParam::Type(ref mut type_param) = *param {
|
||||
if type_param.ident == "TOperator" {
|
||||
continue;
|
||||
}
|
||||
type_param.bounds.push(parse_quote!(Span));
|
||||
}
|
||||
}
|
||||
generics
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
[package]
|
||||
name = "peepmatic-runtime"
|
||||
version = "0.78.0"
|
||||
authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
|
||||
edition = "2018"
|
||||
license = "Apache-2.0 WITH LLVM-exception"
|
||||
description = "Runtime support for peepmatic peephole optimizers"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bincode = "1.2.1"
|
||||
log = "0.4.8"
|
||||
peepmatic-automata = { version = "=0.78.0", path = "../automata", features = ["serde"] }
|
||||
peepmatic-traits = { version = "=0.78.0", path = "../traits" }
|
||||
serde = { version = "1.0.105", features = ["derive"] }
|
||||
thiserror = "1.0.15"
|
||||
wast = { version = "38.0.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
peepmatic-test-operator = { path = "../test-operator" }
|
||||
serde_test = "1.0.114"
|
||||
|
||||
[features]
|
||||
# Enable support for a few extra methods that are required by the `peepmatic`
|
||||
# crate when constructing peephole optimizers, but are not needed when simply
|
||||
# using already-constructed peephole optimizers.
|
||||
construct = ["wast"]
|
||||
@@ -1,220 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
--- LLVM Exceptions to the Apache 2.0 License ----
|
||||
|
||||
As an exception, if, as a result of your compiling your source code, portions
|
||||
of this Software are embedded into an Object form of such source code, you
|
||||
may redistribute such embedded portions in such Object form without complying
|
||||
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
|
||||
|
||||
In addition, if you combine or link compiled forms of this Software with
|
||||
software that is licensed under the GPLv2 ("Combined Software") and if a
|
||||
court of competent jurisdiction determines that the patent provision (Section
|
||||
3), the indemnity provision (Section 9) or other Section of the License
|
||||
conflicts with the conditions of the GPLv2, you may retroactively and
|
||||
prospectively choose to deem waived or otherwise exclude such Section(s) of
|
||||
the License, but only in their entirety and only with respect to the Combined
|
||||
Software.
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
//! Condition codes.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
|
||||
/// A condition code.
|
||||
///
|
||||
/// This is a special kind of immediate for `icmp` instructions that dictate
|
||||
/// which parts of the comparison result we care about.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[repr(u32)]
|
||||
pub enum ConditionCode {
|
||||
/// Equal.
|
||||
// NB: We convert `ConditionCode` into `NonZeroU32`s with unchecked
|
||||
// conversions; memory safety relies on no variant being zero.
|
||||
Eq = 1,
|
||||
|
||||
/// Not equal.
|
||||
Ne,
|
||||
|
||||
/// Signed less than.
|
||||
Slt,
|
||||
|
||||
/// Unsigned less than.
|
||||
Ult,
|
||||
|
||||
/// Signed greater than or equal.
|
||||
Sge,
|
||||
|
||||
/// Unsigned greater than or equal.
|
||||
Uge,
|
||||
|
||||
/// Signed greater than.
|
||||
Sgt,
|
||||
|
||||
/// Unsigned greater than.
|
||||
Ugt,
|
||||
|
||||
/// Signed less than or equal.
|
||||
Sle,
|
||||
|
||||
/// Unsigned less than or equal.
|
||||
Ule,
|
||||
|
||||
/// Overflow.
|
||||
Of,
|
||||
|
||||
/// No overflow.
|
||||
Nof,
|
||||
}
|
||||
|
||||
impl TryFrom<u32> for ConditionCode {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(x: u32) -> Result<Self, Self::Error> {
|
||||
Ok(match x {
|
||||
x if Self::Eq as u32 == x => Self::Eq,
|
||||
x if Self::Ne as u32 == x => Self::Ne,
|
||||
x if Self::Slt as u32 == x => Self::Slt,
|
||||
x if Self::Ult as u32 == x => Self::Ult,
|
||||
x if Self::Sge as u32 == x => Self::Sge,
|
||||
x if Self::Uge as u32 == x => Self::Uge,
|
||||
x if Self::Sgt as u32 == x => Self::Sgt,
|
||||
x if Self::Ugt as u32 == x => Self::Ugt,
|
||||
x if Self::Sle as u32 == x => Self::Sle,
|
||||
x if Self::Ule as u32 == x => Self::Ule,
|
||||
x if Self::Of as u32 == x => Self::Of,
|
||||
x if Self::Nof as u32 == x => Self::Nof,
|
||||
_ => return Err("not a valid condition code value"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ConditionCode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Eq => write!(f, "eq"),
|
||||
Self::Ne => write!(f, "ne"),
|
||||
Self::Slt => write!(f, "slt"),
|
||||
Self::Ult => write!(f, "ult"),
|
||||
Self::Sge => write!(f, "sge"),
|
||||
Self::Uge => write!(f, "uge"),
|
||||
Self::Sgt => write!(f, "sgt"),
|
||||
Self::Ugt => write!(f, "ugt"),
|
||||
Self::Sle => write!(f, "sle"),
|
||||
Self::Ule => write!(f, "ule"),
|
||||
Self::Of => write!(f, "of"),
|
||||
Self::Nof => write!(f, "nof"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
//! `Error` and `Result` types for this crate.
|
||||
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
|
||||
/// A result type containing `Ok(T)` or `Err(peepmatic_runtime::Error)`.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Errors that `peepmatic_runtime` may generate.
|
||||
#[derive(Debug, Error)]
|
||||
#[error(transparent)]
|
||||
pub struct Error {
|
||||
#[from]
|
||||
inner: Box<ErrorInner>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
enum ErrorInner {
|
||||
#[error(transparent)]
|
||||
Io(#[from] io::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
Bincode(#[from] bincode::Error),
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(e: io::Error) -> Error {
|
||||
let e: ErrorInner = e.into();
|
||||
e.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bincode::Error> for Error {
|
||||
fn from(e: bincode::Error) -> Error {
|
||||
let e: ErrorInner = e.into();
|
||||
e.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ErrorInner> for Error {
|
||||
fn from(e: ErrorInner) -> Error {
|
||||
Box::new(e).into()
|
||||
}
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
//! Interfacing with actual instructions.
|
||||
|
||||
use crate::part::{Constant, Part};
|
||||
use crate::r#type::Type;
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
/// A trait for interfacing with actual instruction sequences.
|
||||
///
|
||||
/// This trait enables both:
|
||||
///
|
||||
/// * `peepmatic-runtime` to be used by `cranelift-codegen` without a circular
|
||||
/// dependency from `peepmatic-runtime` to `cranelift-codegen` to get access
|
||||
/// to Cranelift's IR types, and
|
||||
///
|
||||
/// * enables us to write local tests that exercise peephole optimizers on a
|
||||
/// simple, testing-only instruction set without pulling in all of Cranelift.
|
||||
///
|
||||
/// Finally, this should also make the task of adding support for Cranelift's
|
||||
/// new `MachInst` and vcode backend easier, since all that needs to be done is
|
||||
/// "just" implementing this trait. (And probably add/modify some
|
||||
/// `peepmatic_runtime::operation::Operation`s as well).
|
||||
///
|
||||
/// ## Safety
|
||||
///
|
||||
/// See doc comment for `instruction_result_bit_width`.
|
||||
pub unsafe trait InstructionSet<'a> {
|
||||
/// Mutable context passed into all trait methods. Can be whatever you want!
|
||||
///
|
||||
/// In practice, this is a `FuncCursor` for `cranelift-codegen`'s trait
|
||||
/// implementation.
|
||||
type Context;
|
||||
|
||||
/// An operator.
|
||||
type Operator: 'static + Copy + Debug + Eq + Hash + Into<NonZeroU32>;
|
||||
|
||||
/// An instruction (or identifier for an instruction).
|
||||
type Instruction: Copy + Debug + Eq;
|
||||
|
||||
/// Replace the `old` instruction with `new`.
|
||||
///
|
||||
/// `new` is either a `Part::Instruction` or a constant `Part::Boolean` or
|
||||
/// `Part::Integer`. In the former case, it can directly replace `old`. In
|
||||
/// the latter case, implementations of this trait should transparently
|
||||
/// create an `iconst` or `bconst` instruction to wrap the given constant.
|
||||
///
|
||||
/// `new` will never be `Part::ConditionCode`.
|
||||
fn replace_instruction(
|
||||
&self,
|
||||
context: &mut Self::Context,
|
||||
old: Self::Instruction,
|
||||
new: Part<Self::Instruction>,
|
||||
) -> Self::Instruction;
|
||||
|
||||
/// Get the given instruction's operator.
|
||||
///
|
||||
/// If the instruction isn't supported, then `None` should be returned.
|
||||
///
|
||||
/// Additionally, if `Some` is returned, then the instruction's operands
|
||||
/// must be pushed in order into `operands`. E.g. calling this method on
|
||||
/// `(iadd $x $y)` would return `Some(iadd)` and extend `operands` with
|
||||
/// `[$x, $y]`.
|
||||
fn operator<E>(
|
||||
&self,
|
||||
context: &mut Self::Context,
|
||||
instr: Self::Instruction,
|
||||
operands: &mut E,
|
||||
) -> Option<Self::Operator>
|
||||
where
|
||||
E: Extend<Part<Self::Instruction>>;
|
||||
|
||||
/// Make a unary instruction.
|
||||
///
|
||||
/// If the type is not given, then it should be inferred.
|
||||
fn make_inst_1(
|
||||
&self,
|
||||
context: &mut Self::Context,
|
||||
root: Self::Instruction,
|
||||
operator: Self::Operator,
|
||||
r#type: Type,
|
||||
a: Part<Self::Instruction>,
|
||||
) -> Self::Instruction;
|
||||
|
||||
/// Make a binary instruction.
|
||||
///
|
||||
/// Operands are given as immediates first and arguments following
|
||||
/// them. Condition codes are treated as immediates. So if we are creating
|
||||
/// an `iadd_imm` instruction, then `a` will be the constant integer
|
||||
/// immediate and `b` will be the instruction whose result is the dynamic
|
||||
/// argument.
|
||||
fn make_inst_2(
|
||||
&self,
|
||||
context: &mut Self::Context,
|
||||
root: Self::Instruction,
|
||||
operator: Self::Operator,
|
||||
r#type: Type,
|
||||
a: Part<Self::Instruction>,
|
||||
b: Part<Self::Instruction>,
|
||||
) -> Self::Instruction;
|
||||
|
||||
/// Make a ternary instruction.
|
||||
///
|
||||
/// Operands are given as immediates first and arguments following
|
||||
/// them. Condition codes are treated as immediates. So if we are creating
|
||||
/// an `icmp` instruction, then `a` will be the condition code, and `b` and
|
||||
/// `c` will be instructions whose results are the dynamic arguments.
|
||||
fn make_inst_3(
|
||||
&self,
|
||||
context: &mut Self::Context,
|
||||
root: Self::Instruction,
|
||||
operator: Self::Operator,
|
||||
r#type: Type,
|
||||
a: Part<Self::Instruction>,
|
||||
b: Part<Self::Instruction>,
|
||||
c: Part<Self::Instruction>,
|
||||
) -> Self::Instruction;
|
||||
|
||||
/// Try to resolve the given instruction into a constant value.
|
||||
///
|
||||
/// If we can tell that the instruction returns a constant value, then
|
||||
/// return that constant value as either a `Part::Boolean` or
|
||||
/// `Part::Integer`. Otherwise, return `None`.
|
||||
fn instruction_to_constant(
|
||||
&self,
|
||||
context: &mut Self::Context,
|
||||
inst: Self::Instruction,
|
||||
) -> Option<Constant>;
|
||||
|
||||
/// Get the bit width of the given instruction's result.
|
||||
///
|
||||
/// ## Safety
|
||||
///
|
||||
/// There is code that makes memory-safety assumptions that the result is
|
||||
/// always one of 1, 8, 16, 32, 64, or 128. Implementors must uphold this.
|
||||
fn instruction_result_bit_width(
|
||||
&self,
|
||||
context: &mut Self::Context,
|
||||
inst: Self::Instruction,
|
||||
) -> u8;
|
||||
|
||||
/// Get the size of a native word in bits.
|
||||
fn native_word_size_in_bits(&self, context: &mut Self::Context) -> u8;
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
//! Interner for (potentially large) integer values.
|
||||
//!
|
||||
//! We support matching on integers that can be represented by `u64`, but only
|
||||
//! support automata results that fit in a `u32`. So we intern the (relatively
|
||||
//! few compared to the full range of `u64`) integers we are matching against
|
||||
//! here and then reference them by `IntegerId`.
|
||||
|
||||
use serde::de::{Deserializer, SeqAccess, Visitor};
|
||||
use serde::ser::{SerializeSeq, Serializer};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
use std::num::{NonZeroU16, NonZeroU32};
|
||||
|
||||
/// An identifier for an interned integer.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct IntegerId(#[doc(hidden)] pub NonZeroU16);
|
||||
|
||||
/// An interner for integer values.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct IntegerInterner {
|
||||
// Note: we use `BTreeMap`s for deterministic serialization.
|
||||
map: BTreeMap<u64, IntegerId>,
|
||||
values: Vec<u64>,
|
||||
}
|
||||
|
||||
impl IntegerInterner {
|
||||
/// Construct a new `IntegerInterner`.
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Intern a value into this `IntegerInterner`, returning its canonical
|
||||
/// `IntegerId`.
|
||||
#[inline]
|
||||
pub fn intern(&mut self, value: impl Into<u64>) -> IntegerId {
|
||||
debug_assert_eq!(self.map.len(), self.values.len());
|
||||
|
||||
let value = value.into();
|
||||
|
||||
if let Some(id) = self.map.get(&value) {
|
||||
return *id;
|
||||
}
|
||||
|
||||
assert!((self.values.len() as u64) < (std::u16::MAX as u64));
|
||||
let id = IntegerId(unsafe { NonZeroU16::new_unchecked(self.values.len() as u16 + 1) });
|
||||
|
||||
self.values.push(value);
|
||||
self.map.insert(value, id);
|
||||
debug_assert_eq!(self.map.len(), self.values.len());
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
/// Get the id of an already-interned integer, or `None` if it has not been
|
||||
/// interned.
|
||||
pub fn already_interned(&self, value: impl Into<u64>) -> Option<IntegerId> {
|
||||
let value = value.into();
|
||||
self.map.get(&value).copied()
|
||||
}
|
||||
|
||||
/// Lookup a previously interned integer by id.
|
||||
#[inline]
|
||||
pub fn lookup(&self, id: IntegerId) -> u64 {
|
||||
let index = id.0.get() as usize - 1;
|
||||
self.values[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IntegerId> for NonZeroU32 {
|
||||
#[inline]
|
||||
fn from(id: IntegerId) -> NonZeroU32 {
|
||||
id.0.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for IntegerInterner {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut seq = serializer.serialize_seq(Some(self.values.len()))?;
|
||||
for p in &self.values {
|
||||
seq.serialize_element(&p)?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for IntegerInterner {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_seq(IntegerInternerVisitor {
|
||||
marker: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct IntegerInternerVisitor {
|
||||
marker: PhantomData<fn() -> IntegerInterner>,
|
||||
}
|
||||
|
||||
impl<'de> Visitor<'de> for IntegerInternerVisitor {
|
||||
type Value = IntegerInterner;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
formatter,
|
||||
"a `peepmatic_runtime::integer_interner::IntegerInterner`"
|
||||
)
|
||||
}
|
||||
|
||||
fn visit_seq<M>(self, mut access: M) -> Result<Self::Value, M::Error>
|
||||
where
|
||||
M: SeqAccess<'de>,
|
||||
{
|
||||
const DEFAULT_CAPACITY: usize = 16;
|
||||
let capacity = access.size_hint().unwrap_or(DEFAULT_CAPACITY);
|
||||
|
||||
let mut interner = IntegerInterner {
|
||||
map: BTreeMap::new(),
|
||||
values: Vec::with_capacity(capacity),
|
||||
};
|
||||
|
||||
while let Some(path) = access.next_element::<u64>()? {
|
||||
interner.intern(path);
|
||||
}
|
||||
|
||||
Ok(interner)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_test::{assert_tokens, Token};
|
||||
use std::iter::successors;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct OrderedIntegerInterner(IntegerInterner);
|
||||
|
||||
impl PartialEq for OrderedIntegerInterner {
|
||||
fn eq(&self, other: &OrderedIntegerInterner) -> bool {
|
||||
self.0.values.iter().eq(other.0.values.iter())
|
||||
}
|
||||
}
|
||||
|
||||
fn intern_fib(interner: &mut IntegerInterner, skip: usize, take: usize) {
|
||||
successors(Some((0, 1)), |(a, b): &(u64, u64)| {
|
||||
a.checked_add(*b).map(|c| (*b, c))
|
||||
})
|
||||
.skip(skip)
|
||||
.take(take)
|
||||
.for_each(|(i, _)| {
|
||||
interner.intern(i);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ser_de_empty_interner() {
|
||||
let interner = IntegerInterner::new();
|
||||
|
||||
assert_tokens(
|
||||
&OrderedIntegerInterner(interner),
|
||||
&[Token::Seq { len: Some(0) }, Token::SeqEnd],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ser_de_fibonacci_interner() {
|
||||
let mut interner = IntegerInterner::new();
|
||||
intern_fib(&mut interner, 10, 5);
|
||||
|
||||
assert_tokens(
|
||||
&OrderedIntegerInterner(interner),
|
||||
&[
|
||||
Token::Seq { len: Some(5) },
|
||||
Token::U64(55),
|
||||
Token::U64(89),
|
||||
Token::U64(144),
|
||||
Token::U64(233),
|
||||
Token::U64(377),
|
||||
Token::SeqEnd,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
//! Runtime support for `peepmatic`'s peephole optimizers.
|
||||
//!
|
||||
//! This crate contains everything required to use a `peepmatic`-generated
|
||||
//! peephole optimizer.
|
||||
//!
|
||||
//! ## Why is this a different crate from `peepmatic`?
|
||||
//!
|
||||
//! In short: build times and code size.
|
||||
//!
|
||||
//! If you are just using a peephole optimizer, you shouldn't need the functions
|
||||
//! to construct it from scratch from the DSL (and the implied code size and
|
||||
//! compilation time), let alone even build it at all. You should just
|
||||
//! deserialize an already-built peephole optimizer, and then use it.
|
||||
//!
|
||||
//! That's all that is contained here in this crate.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![deny(missing_debug_implementations)]
|
||||
|
||||
pub mod cc;
|
||||
pub mod error;
|
||||
pub mod instruction_set;
|
||||
pub mod integer_interner;
|
||||
pub mod linear;
|
||||
pub mod optimizations;
|
||||
pub mod optimizer;
|
||||
pub mod part;
|
||||
pub mod r#type;
|
||||
pub mod unquote;
|
||||
|
||||
pub use error::{Error, Result};
|
||||
pub use optimizations::PeepholeOptimizations;
|
||||
pub use optimizer::PeepholeOptimizer;
|
||||
@@ -1,241 +0,0 @@
|
||||
//! A linear IR for optimizations.
|
||||
//!
|
||||
//! This IR is designed such that it should be easy to combine multiple linear
|
||||
//! optimizations into a single automata.
|
||||
//!
|
||||
//! See also `src/linearize.rs` for the AST to linear IR translation pass.
|
||||
|
||||
use crate::cc::ConditionCode;
|
||||
use crate::integer_interner::{IntegerId, IntegerInterner};
|
||||
use crate::r#type::{BitWidth, Type};
|
||||
use crate::unquote::UnquoteOperator;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
/// A set of linear optimizations.
|
||||
#[derive(Debug)]
|
||||
pub struct Optimizations<TOperator>
|
||||
where
|
||||
TOperator: 'static + Copy + Debug + Eq + Hash,
|
||||
{
|
||||
/// The linear optimizations.
|
||||
pub optimizations: Vec<Optimization<TOperator>>,
|
||||
|
||||
/// The integer literals referenced by these optimizations.
|
||||
pub integers: IntegerInterner,
|
||||
}
|
||||
|
||||
/// A linearized optimization.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Optimization<TOperator>
|
||||
where
|
||||
TOperator: 'static + Copy + Debug + Eq + Hash,
|
||||
{
|
||||
/// The chain of match operations and expected results for this
|
||||
/// optimization.
|
||||
pub matches: Vec<Match>,
|
||||
|
||||
/// Actions to perform, given that the operation resulted in the expected
|
||||
/// value.
|
||||
pub actions: Vec<Action<TOperator>>,
|
||||
}
|
||||
|
||||
/// Match any value.
|
||||
///
|
||||
/// This can be used to create fallback, wildcard-style transitions between
|
||||
/// states.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
pub struct Else;
|
||||
|
||||
/// The result of evaluating a `MatchOp`.
|
||||
///
|
||||
/// This is either a specific non-zero `u32`, or a fallback that matches
|
||||
/// everything.
|
||||
pub type MatchResult = Result<NonZeroU32, Else>;
|
||||
|
||||
/// Convert a boolean to a `MatchResult`.
|
||||
#[inline]
|
||||
pub fn bool_to_match_result(b: bool) -> MatchResult {
|
||||
let b = b as u32;
|
||||
unsafe { Ok(NonZeroU32::new_unchecked(b + 1)) }
|
||||
}
|
||||
|
||||
/// A partial match of an optimization's LHS.
|
||||
///
|
||||
/// An match is composed of a matching operation and the expected result of that
|
||||
/// operation. Each match will basically become a state and a transition edge
|
||||
/// out of that state in the final automata.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Match {
|
||||
/// The matching operation to perform.
|
||||
pub operation: MatchOp,
|
||||
|
||||
/// The expected result of our matching operation, that enables us to
|
||||
/// continue to the next match, or `Else` for "don't care" wildcard-style
|
||||
/// matching.
|
||||
pub expected: MatchResult,
|
||||
}
|
||||
|
||||
/// A matching operation to be performed on some Cranelift instruction as part
|
||||
/// of determining whether an optimization is applicable.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||
pub enum MatchOp {
|
||||
/// Switch on the opcode of an instruction.
|
||||
///
|
||||
/// Upon successfully matching an instruction's opcode, bind each of its
|
||||
/// operands to a LHS temporary.
|
||||
Opcode(LhsId),
|
||||
|
||||
/// Does an instruction have a constant value?
|
||||
IsConst(LhsId),
|
||||
|
||||
/// Is the constant value a power of two?
|
||||
IsPowerOfTwo(LhsId),
|
||||
|
||||
/// Switch on the bit width of a value.
|
||||
BitWidth(LhsId),
|
||||
|
||||
/// Does the value fit in our target architecture's native word size?
|
||||
FitsInNativeWord(LhsId),
|
||||
|
||||
/// Are the instructions (or immediates) the same?
|
||||
Eq(LhsId, LhsId),
|
||||
|
||||
/// Switch on the constant integer value of an instruction.
|
||||
IntegerValue(LhsId),
|
||||
|
||||
/// Switch on the constant boolean value of an instruction.
|
||||
BooleanValue(LhsId),
|
||||
|
||||
/// Switch on a condition code.
|
||||
ConditionCode(LhsId),
|
||||
|
||||
/// No operation. Always evaluates to `Else`.
|
||||
///
|
||||
/// Never appears in real optimizations; nonetheless required to support
|
||||
/// corner cases of the DSL, such as a LHS pattern that is nothing but a
|
||||
/// variable.
|
||||
Nop,
|
||||
}
|
||||
|
||||
/// A canonicalized identifier for a left-hand side value that was bound in a
|
||||
/// pattern.
|
||||
///
|
||||
/// These are defined in a pre-order traversal of the LHS pattern by successful
|
||||
/// `MatchOp::Opcode` matches.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct LhsId(pub u16);
|
||||
|
||||
/// A canonicalized identifier for a right-hand side value.
|
||||
///
|
||||
/// These are defined by RHS actions.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct RhsId(pub u16);
|
||||
|
||||
/// An action to perform when transitioning between states in the automata.
|
||||
///
|
||||
/// When evaluating actions, the `i^th` action implicitly defines the
|
||||
/// `RhsId(i)`.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum Action<TOperator> {
|
||||
/// Reuse something from the left-hand side.
|
||||
GetLhs {
|
||||
/// The left-hand side instruction or value.
|
||||
lhs: LhsId,
|
||||
},
|
||||
|
||||
/// Perform compile-time evaluation.
|
||||
UnaryUnquote {
|
||||
/// The unquote operator.
|
||||
operator: UnquoteOperator,
|
||||
/// The constant operand to this unquote.
|
||||
operand: RhsId,
|
||||
},
|
||||
|
||||
/// Perform compile-time evaluation.
|
||||
BinaryUnquote {
|
||||
/// The unquote operator.
|
||||
operator: UnquoteOperator,
|
||||
/// The constant operands to this unquote.
|
||||
operands: [RhsId; 2],
|
||||
},
|
||||
|
||||
/// Create an integer constant.
|
||||
MakeIntegerConst {
|
||||
/// The constant integer value.
|
||||
value: IntegerId,
|
||||
/// The bit width of this constant.
|
||||
bit_width: BitWidth,
|
||||
},
|
||||
|
||||
/// Create a boolean constant.
|
||||
MakeBooleanConst {
|
||||
/// The constant boolean value.
|
||||
value: bool,
|
||||
/// The bit width of this constant.
|
||||
bit_width: BitWidth,
|
||||
},
|
||||
|
||||
/// Create a condition code.
|
||||
MakeConditionCode {
|
||||
/// The condition code.
|
||||
cc: ConditionCode,
|
||||
},
|
||||
|
||||
/// Make a unary instruction.
|
||||
MakeUnaryInst {
|
||||
/// The operand for this instruction.
|
||||
operand: RhsId,
|
||||
/// The type of this instruction's result.
|
||||
r#type: Type,
|
||||
/// The operator for this instruction.
|
||||
operator: TOperator,
|
||||
},
|
||||
|
||||
/// Make a binary instruction.
|
||||
MakeBinaryInst {
|
||||
/// The opcode for this instruction.
|
||||
operator: TOperator,
|
||||
/// The type of this instruction's result.
|
||||
r#type: Type,
|
||||
/// The operands for this instruction.
|
||||
operands: [RhsId; 2],
|
||||
},
|
||||
|
||||
/// Make a ternary instruction.
|
||||
MakeTernaryInst {
|
||||
/// The opcode for this instruction.
|
||||
operator: TOperator,
|
||||
/// The type of this instruction's result.
|
||||
r#type: Type,
|
||||
/// The operands for this instruction.
|
||||
operands: [RhsId; 3],
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use peepmatic_test_operator::TestOperator;
|
||||
|
||||
// These types all end up in the automaton, so we should take care that they
|
||||
// are small and don't fill up the data cache (or take up too much
|
||||
// serialized size).
|
||||
|
||||
#[test]
|
||||
fn match_result_size() {
|
||||
assert_eq!(std::mem::size_of::<MatchResult>(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn match_op_size() {
|
||||
assert_eq!(std::mem::size_of::<MatchOp>(), 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn action_size() {
|
||||
assert_eq!(std::mem::size_of::<Action<TestOperator>>(), 16);
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
//! Compiled peephole optimizations.
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::instruction_set::InstructionSet;
|
||||
use crate::integer_interner::IntegerInterner;
|
||||
use crate::linear::{Action, MatchOp, MatchResult};
|
||||
use crate::optimizer::PeepholeOptimizer;
|
||||
use peepmatic_automata::Automaton;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
|
||||
#[cfg(feature = "construct")]
|
||||
use std::fs;
|
||||
#[cfg(feature = "construct")]
|
||||
use std::path::Path;
|
||||
|
||||
/// A compiled set of peephole optimizations.
|
||||
///
|
||||
/// This is the compilation result of the `peepmatic` crate, after its taken a
|
||||
/// bunch of optimizations written in the DSL and lowered and combined them.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PeepholeOptimizations<TOperator>
|
||||
where
|
||||
TOperator: 'static + Copy + Debug + Eq + Hash,
|
||||
{
|
||||
/// Not all integers we're matching on fit in the `u32` that we use as the
|
||||
/// result of match operations. So we intern them and refer to them by id.
|
||||
pub integers: IntegerInterner,
|
||||
|
||||
/// The underlying automata for matching optimizations' left-hand sides, and
|
||||
/// building up the corresponding right-hand side.
|
||||
pub automata: Automaton<MatchResult, MatchOp, Box<[Action<TOperator>]>>,
|
||||
}
|
||||
|
||||
impl<TOperator> PeepholeOptimizations<TOperator>
|
||||
where
|
||||
TOperator: 'static + Copy + Debug + Eq + Hash,
|
||||
{
|
||||
/// Deserialize a `PeepholeOptimizations` from bytes.
|
||||
pub fn deserialize<'a>(serialized: &'a [u8]) -> Result<Self>
|
||||
where
|
||||
TOperator: serde::Deserialize<'a>,
|
||||
{
|
||||
let peep_opt: Self = bincode::deserialize(serialized)?;
|
||||
Ok(peep_opt)
|
||||
}
|
||||
|
||||
/// Serialize these peephole optimizations out to the file at the given path.
|
||||
///
|
||||
/// Requires that the `"construct"` cargo feature is enabled.
|
||||
#[cfg(feature = "construct")]
|
||||
pub fn serialize_to_file(&self, path: &Path) -> Result<()>
|
||||
where
|
||||
TOperator: serde::Serialize,
|
||||
{
|
||||
let file = fs::File::create(path)?;
|
||||
bincode::serialize_into(file, self)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<TOperator> PeepholeOptimizations<TOperator>
|
||||
where
|
||||
TOperator: 'static + Copy + Debug + Eq + Hash,
|
||||
{
|
||||
/// Create a new peephole optimizer instance from this set of peephole
|
||||
/// optimizations.
|
||||
///
|
||||
/// The peephole optimizer instance can be used to apply these peephole
|
||||
/// optimizations. When checking multiple instructions for whether they can
|
||||
/// be optimized, it is more performant to reuse a single peephole optimizer
|
||||
/// instance, rather than create a new one for each instruction. Reusing the
|
||||
/// peephole optimizer instance allows the reuse of a few internal
|
||||
/// allocations.
|
||||
pub fn optimizer<'peep, 'ctx, TInstructionSet>(
|
||||
&'peep self,
|
||||
instr_set: TInstructionSet,
|
||||
) -> PeepholeOptimizer<'peep, 'ctx, TInstructionSet>
|
||||
where
|
||||
TInstructionSet: InstructionSet<'ctx, Operator = TOperator>,
|
||||
TOperator: Into<std::num::NonZeroU32>,
|
||||
{
|
||||
PeepholeOptimizer {
|
||||
peep_opt: self,
|
||||
instr_set,
|
||||
left_hand_sides: vec![],
|
||||
right_hand_sides: vec![],
|
||||
actions: vec![],
|
||||
backtracking_states: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,551 +0,0 @@
|
||||
//! An optimizer for a set of peephole optimizations.
|
||||
|
||||
use crate::instruction_set::InstructionSet;
|
||||
use crate::linear::{bool_to_match_result, Action, Else, MatchOp, MatchResult};
|
||||
use crate::optimizations::PeepholeOptimizations;
|
||||
use crate::part::{Constant, Part};
|
||||
use crate::r#type::{BitWidth, Type};
|
||||
use crate::unquote::UnquoteOperator;
|
||||
use peepmatic_automata::State;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::{self, Debug};
|
||||
use std::mem;
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
/// A peephole optimizer instance that can apply a set of peephole
|
||||
/// optimizations to instructions.
|
||||
///
|
||||
/// These are created from a set of peephole optimizations with the
|
||||
/// [`PeepholeOptimizations::optimizer`] method.
|
||||
///
|
||||
/// Reusing an instance when applying peephole optimizations to different
|
||||
/// instruction sequences means that you reuse internal allocations that are
|
||||
/// used to match left-hand sides and build up right-hand sides.
|
||||
pub struct PeepholeOptimizer<'peep, 'ctx, TInstructionSet>
|
||||
where
|
||||
TInstructionSet: InstructionSet<'ctx>,
|
||||
{
|
||||
pub(crate) peep_opt: &'peep PeepholeOptimizations<TInstructionSet::Operator>,
|
||||
pub(crate) instr_set: TInstructionSet,
|
||||
pub(crate) left_hand_sides: Vec<Part<TInstructionSet::Instruction>>,
|
||||
pub(crate) right_hand_sides: Vec<Part<TInstructionSet::Instruction>>,
|
||||
pub(crate) actions: Vec<Action<TInstructionSet::Operator>>,
|
||||
pub(crate) backtracking_states: Vec<(State, usize, usize)>,
|
||||
}
|
||||
|
||||
impl<'peep, 'ctx, TInstructionSet> Debug for PeepholeOptimizer<'peep, 'ctx, TInstructionSet>
|
||||
where
|
||||
TInstructionSet: InstructionSet<'ctx>,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let PeepholeOptimizer {
|
||||
peep_opt,
|
||||
instr_set: _,
|
||||
left_hand_sides,
|
||||
right_hand_sides,
|
||||
actions,
|
||||
backtracking_states,
|
||||
} = self;
|
||||
f.debug_struct("PeepholeOptimizer")
|
||||
.field("peep_opt", peep_opt)
|
||||
.field("instr_set", &"_")
|
||||
.field("left_hand_sides", left_hand_sides)
|
||||
.field("right_hand_sides", right_hand_sides)
|
||||
.field("actions", actions)
|
||||
.field("backtracking_states", backtracking_states)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'peep, 'ctx, TInstructionSet> PeepholeOptimizer<'peep, 'ctx, TInstructionSet>
|
||||
where
|
||||
TInstructionSet: InstructionSet<'ctx>,
|
||||
{
|
||||
fn eval_unquote_1(&self, operator: UnquoteOperator, a: Constant) -> Constant {
|
||||
use Constant::*;
|
||||
|
||||
macro_rules! map_int {
|
||||
( $c:expr , | $x:ident | $e:expr ) => {
|
||||
match $c {
|
||||
Int($x, w) => Int($e, w),
|
||||
Bool(..) => panic!("not an integer"),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
match operator {
|
||||
UnquoteOperator::Log2 => map_int!(a, |x| x.trailing_zeros() as _),
|
||||
UnquoteOperator::Neg => map_int!(a, |x| x.wrapping_neg()),
|
||||
UnquoteOperator::Band
|
||||
| UnquoteOperator::Bor
|
||||
| UnquoteOperator::Bxor
|
||||
| UnquoteOperator::Iadd
|
||||
| UnquoteOperator::Imul
|
||||
| UnquoteOperator::Isub => unreachable!("not a unary unquote operator: {:?}", operator),
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_unquote_2(&self, operator: UnquoteOperator, a: Constant, b: Constant) -> Constant {
|
||||
use Constant::*;
|
||||
|
||||
macro_rules! fold_ints {
|
||||
( $c1:expr , $c2:expr , | $x:ident , $y:ident | $e:expr ) => {
|
||||
match ($c1, $c2) {
|
||||
(Int($x, w1), Int($y, w2)) if w1 == w2 => Int($e, w1),
|
||||
_ => panic!("not two integers of the same width"),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
match operator {
|
||||
UnquoteOperator::Band => fold_ints!(a, b, |x, y| x & y),
|
||||
UnquoteOperator::Bor => fold_ints!(a, b, |x, y| x | y),
|
||||
UnquoteOperator::Bxor => fold_ints!(a, b, |x, y| x ^ y),
|
||||
UnquoteOperator::Iadd => fold_ints!(a, b, |x, y| x.wrapping_add(y)),
|
||||
UnquoteOperator::Imul => fold_ints!(a, b, |x, y| x.wrapping_mul(y)),
|
||||
UnquoteOperator::Isub => fold_ints!(a, b, |x, y| x.wrapping_sub(y)),
|
||||
UnquoteOperator::Log2 | UnquoteOperator::Neg => {
|
||||
unreachable!("not a binary unquote operator: {:?}", operator)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_actions(
|
||||
&mut self,
|
||||
context: &mut TInstructionSet::Context,
|
||||
root: TInstructionSet::Instruction,
|
||||
) {
|
||||
let mut actions = mem::replace(&mut self.actions, vec![]);
|
||||
|
||||
for action in actions.drain(..) {
|
||||
log::trace!("Evaluating action: {:?}", action);
|
||||
match action {
|
||||
Action::GetLhs { lhs } => {
|
||||
let lhs = self.left_hand_sides[lhs.0 as usize];
|
||||
self.right_hand_sides.push(lhs);
|
||||
}
|
||||
Action::UnaryUnquote { operator, operand } => {
|
||||
let operand = self.right_hand_sides[operand.0 as usize];
|
||||
let operand = match operand {
|
||||
Part::Instruction(i) => self
|
||||
.instr_set
|
||||
.instruction_to_constant(context, i)
|
||||
.expect("cannot convert instruction to constant for unquote operand"),
|
||||
Part::Constant(c) => c,
|
||||
Part::ConditionCode(_) => {
|
||||
panic!("cannot use a condition code as an unquote operand")
|
||||
}
|
||||
};
|
||||
let result = self.eval_unquote_1(operator, operand);
|
||||
self.right_hand_sides.push(result.into());
|
||||
}
|
||||
Action::BinaryUnquote { operator, operands } => {
|
||||
let a = self.right_hand_sides[operands[0].0 as usize];
|
||||
let a = match a {
|
||||
Part::Instruction(i) => self
|
||||
.instr_set
|
||||
.instruction_to_constant(context, i)
|
||||
.expect("cannot convert instruction to constant for unquote operand"),
|
||||
Part::Constant(c) => c,
|
||||
Part::ConditionCode(_) => {
|
||||
panic!("cannot use a condition code as an unquote operand")
|
||||
}
|
||||
};
|
||||
|
||||
let b = self.right_hand_sides[operands[1].0 as usize];
|
||||
let b = match b {
|
||||
Part::Instruction(i) => self
|
||||
.instr_set
|
||||
.instruction_to_constant(context, i)
|
||||
.expect("cannot convert instruction to constant for unquote operand"),
|
||||
Part::Constant(c) => c,
|
||||
Part::ConditionCode(_) => {
|
||||
panic!("cannot use a condition code as an unquote operand")
|
||||
}
|
||||
};
|
||||
|
||||
let result = self.eval_unquote_2(operator, a, b);
|
||||
self.right_hand_sides.push(result.into());
|
||||
}
|
||||
Action::MakeIntegerConst {
|
||||
value,
|
||||
mut bit_width,
|
||||
} => {
|
||||
let value = self.peep_opt.integers.lookup(value);
|
||||
if bit_width.is_polymorphic() {
|
||||
bit_width = BitWidth::try_from(
|
||||
self.instr_set.instruction_result_bit_width(context, root),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
self.right_hand_sides
|
||||
.push(Constant::Int(value, bit_width).into());
|
||||
}
|
||||
Action::MakeBooleanConst {
|
||||
value,
|
||||
mut bit_width,
|
||||
} => {
|
||||
if bit_width.is_polymorphic() {
|
||||
bit_width = BitWidth::try_from(
|
||||
self.instr_set.instruction_result_bit_width(context, root),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
self.right_hand_sides
|
||||
.push(Constant::Bool(value, bit_width).into());
|
||||
}
|
||||
Action::MakeConditionCode { cc } => {
|
||||
self.right_hand_sides.push(Part::ConditionCode(cc));
|
||||
}
|
||||
Action::MakeUnaryInst {
|
||||
operator,
|
||||
r#type:
|
||||
Type {
|
||||
kind,
|
||||
mut bit_width,
|
||||
},
|
||||
operand,
|
||||
} => {
|
||||
if bit_width.is_polymorphic() {
|
||||
bit_width = BitWidth::try_from(
|
||||
self.instr_set.instruction_result_bit_width(context, root),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
let ty = Type { kind, bit_width };
|
||||
let operand = self.right_hand_sides[operand.0 as usize];
|
||||
let inst = self
|
||||
.instr_set
|
||||
.make_inst_1(context, root, operator, ty, operand);
|
||||
self.right_hand_sides.push(Part::Instruction(inst));
|
||||
}
|
||||
Action::MakeBinaryInst {
|
||||
operator,
|
||||
r#type:
|
||||
Type {
|
||||
kind,
|
||||
mut bit_width,
|
||||
},
|
||||
operands,
|
||||
} => {
|
||||
if bit_width.is_polymorphic() {
|
||||
bit_width = BitWidth::try_from(
|
||||
self.instr_set.instruction_result_bit_width(context, root),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
let ty = Type { kind, bit_width };
|
||||
let a = self.right_hand_sides[operands[0].0 as usize];
|
||||
let b = self.right_hand_sides[operands[1].0 as usize];
|
||||
let inst = self
|
||||
.instr_set
|
||||
.make_inst_2(context, root, operator, ty, a, b);
|
||||
self.right_hand_sides.push(Part::Instruction(inst));
|
||||
}
|
||||
Action::MakeTernaryInst {
|
||||
operator,
|
||||
r#type:
|
||||
Type {
|
||||
kind,
|
||||
mut bit_width,
|
||||
},
|
||||
operands,
|
||||
} => {
|
||||
if bit_width.is_polymorphic() {
|
||||
bit_width = BitWidth::try_from(
|
||||
self.instr_set.instruction_result_bit_width(context, root),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
let ty = Type { kind, bit_width };
|
||||
let a = self.right_hand_sides[operands[0].0 as usize];
|
||||
let b = self.right_hand_sides[operands[1].0 as usize];
|
||||
let c = self.right_hand_sides[operands[2].0 as usize];
|
||||
let inst = self
|
||||
.instr_set
|
||||
.make_inst_3(context, root, operator, ty, a, b, c);
|
||||
self.right_hand_sides.push(Part::Instruction(inst));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reuse the heap elements allocation.
|
||||
self.actions = actions;
|
||||
}
|
||||
|
||||
fn eval_match_op(
|
||||
&mut self,
|
||||
context: &mut TInstructionSet::Context,
|
||||
root: TInstructionSet::Instruction,
|
||||
match_op: MatchOp,
|
||||
) -> MatchResult {
|
||||
use crate::linear::MatchOp::*;
|
||||
|
||||
log::trace!("Evaluating match operation: {:?}", match_op);
|
||||
let result: MatchResult = (|| match match_op {
|
||||
Opcode(id) => {
|
||||
let part = self.left_hand_sides[id.0 as usize];
|
||||
let inst = part.as_instruction().ok_or(Else)?;
|
||||
let op = self
|
||||
.instr_set
|
||||
.operator(context, inst, &mut self.left_hand_sides)
|
||||
.ok_or(Else)?;
|
||||
Ok(op.into())
|
||||
}
|
||||
IsConst(id) => {
|
||||
let part = self.left_hand_sides[id.0 as usize];
|
||||
let is_const = match part {
|
||||
Part::Instruction(i) => {
|
||||
self.instr_set.instruction_to_constant(context, i).is_some()
|
||||
}
|
||||
Part::ConditionCode(_) | Part::Constant(_) => true,
|
||||
};
|
||||
bool_to_match_result(is_const)
|
||||
}
|
||||
IsPowerOfTwo(id) => {
|
||||
let part = self.left_hand_sides[id.0 as usize];
|
||||
match part {
|
||||
Part::Constant(c) => {
|
||||
let is_pow2 = c.as_int().unwrap().is_power_of_two();
|
||||
bool_to_match_result(is_pow2)
|
||||
}
|
||||
Part::Instruction(i) => {
|
||||
let c = self
|
||||
.instr_set
|
||||
.instruction_to_constant(context, i)
|
||||
.ok_or(Else)?;
|
||||
let is_pow2 = c.as_int().unwrap().is_power_of_two();
|
||||
bool_to_match_result(is_pow2)
|
||||
}
|
||||
Part::ConditionCode(_) => unreachable!("IsPowerOfTwo on a condition code"),
|
||||
}
|
||||
}
|
||||
BitWidth(id) => {
|
||||
let part = self.left_hand_sides[id.0 as usize];
|
||||
let bit_width = match part {
|
||||
Part::Instruction(i) => self.instr_set.instruction_result_bit_width(context, i),
|
||||
Part::Constant(Constant::Int(_, w)) | Part::Constant(Constant::Bool(_, w)) => {
|
||||
w.fixed_width().unwrap_or_else(|| {
|
||||
self.instr_set.instruction_result_bit_width(context, root)
|
||||
})
|
||||
}
|
||||
Part::ConditionCode(_) => panic!("BitWidth on condition code"),
|
||||
};
|
||||
debug_assert!(
|
||||
bit_width != 0,
|
||||
"`InstructionSet` implementors must uphold the contract that \
|
||||
`instruction_result_bit_width` returns one of 1, 8, 16, 32, 64, or 128"
|
||||
);
|
||||
Ok(unsafe { NonZeroU32::new_unchecked(bit_width as u32) })
|
||||
}
|
||||
FitsInNativeWord(id) => {
|
||||
let native_word_size = self.instr_set.native_word_size_in_bits(context);
|
||||
debug_assert!(native_word_size.is_power_of_two());
|
||||
|
||||
let part = self.left_hand_sides[id.0 as usize];
|
||||
let fits = match part {
|
||||
Part::Instruction(i) => {
|
||||
let size = self.instr_set.instruction_result_bit_width(context, i);
|
||||
size <= native_word_size
|
||||
}
|
||||
Part::Constant(c) => {
|
||||
let root_width = self.instr_set.instruction_result_bit_width(context, root);
|
||||
let size = c.bit_width(root_width);
|
||||
size <= native_word_size
|
||||
}
|
||||
Part::ConditionCode(_) => panic!("FitsInNativeWord on condition code"),
|
||||
};
|
||||
bool_to_match_result(fits)
|
||||
}
|
||||
Eq(a, b) => {
|
||||
let part_a = self.left_hand_sides[a.0 as usize];
|
||||
let part_b = self.left_hand_sides[b.0 as usize];
|
||||
let eq = match (part_a, part_b) {
|
||||
(Part::Instruction(inst), Part::Constant(c1))
|
||||
| (Part::Constant(c1), Part::Instruction(inst)) => {
|
||||
match self.instr_set.instruction_to_constant(context, inst) {
|
||||
Some(c2) => c1 == c2,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
(a, b) => a == b,
|
||||
};
|
||||
bool_to_match_result(eq)
|
||||
}
|
||||
IntegerValue(id) => {
|
||||
let part = self.left_hand_sides[id.0 as usize];
|
||||
match part {
|
||||
Part::Constant(c) => {
|
||||
let x = c.as_int().ok_or(Else)?;
|
||||
let id = self.peep_opt.integers.already_interned(x).ok_or(Else)?;
|
||||
Ok(id.into())
|
||||
}
|
||||
Part::Instruction(i) => {
|
||||
let c = self
|
||||
.instr_set
|
||||
.instruction_to_constant(context, i)
|
||||
.ok_or(Else)?;
|
||||
let x = c.as_int().ok_or(Else)?;
|
||||
let id = self.peep_opt.integers.already_interned(x).ok_or(Else)?;
|
||||
Ok(id.into())
|
||||
}
|
||||
Part::ConditionCode(_) => unreachable!("IntegerValue on condition code"),
|
||||
}
|
||||
}
|
||||
BooleanValue(id) => {
|
||||
let part = self.left_hand_sides[id.0 as usize];
|
||||
match part {
|
||||
Part::Constant(c) => {
|
||||
let b = c.as_bool().ok_or(Else)?;
|
||||
bool_to_match_result(b)
|
||||
}
|
||||
Part::Instruction(i) => {
|
||||
let c = self
|
||||
.instr_set
|
||||
.instruction_to_constant(context, i)
|
||||
.ok_or(Else)?;
|
||||
let b = c.as_bool().ok_or(Else)?;
|
||||
bool_to_match_result(b)
|
||||
}
|
||||
Part::ConditionCode(_) => unreachable!("IntegerValue on condition code"),
|
||||
}
|
||||
}
|
||||
ConditionCode(id) => {
|
||||
let part = self.left_hand_sides[id.0 as usize];
|
||||
let cc = part.as_condition_code().ok_or(Else)?;
|
||||
let cc = cc as u32;
|
||||
debug_assert!(cc != 0);
|
||||
Ok(unsafe { NonZeroU32::new_unchecked(cc) })
|
||||
}
|
||||
MatchOp::Nop => Err(Else),
|
||||
})();
|
||||
log::trace!("Evaluated match operation: {:?} = {:?}", match_op, result);
|
||||
result
|
||||
}
|
||||
|
||||
/// Attempt to apply a single peephole optimization to the given root
|
||||
/// instruction.
|
||||
///
|
||||
/// If an optimization is applied, then the `root` is replaced with the
|
||||
/// optimization's right-hand side, and the root of the right-hand side is
|
||||
/// returned as `Some`.
|
||||
///
|
||||
/// If no optimization's left-hand side matches `root`, then `root` is left
|
||||
/// untouched and `None` is returned.
|
||||
pub fn apply_one(
|
||||
&mut self,
|
||||
context: &mut TInstructionSet::Context,
|
||||
root: TInstructionSet::Instruction,
|
||||
) -> Option<TInstructionSet::Instruction> {
|
||||
log::trace!("PeepholeOptimizer::apply_one");
|
||||
|
||||
self.backtracking_states.clear();
|
||||
self.actions.clear();
|
||||
self.right_hand_sides.clear();
|
||||
self.left_hand_sides.clear();
|
||||
|
||||
// `LhsId(0)` is always the root.
|
||||
self.left_hand_sides.push(Part::Instruction(root));
|
||||
|
||||
let mut r#final = None;
|
||||
|
||||
let mut query = self.peep_opt.automata.query();
|
||||
loop {
|
||||
log::trace!("Current state: {:?}", query.current_state());
|
||||
log::trace!(
|
||||
"self.left_hand_sides = {:#?}",
|
||||
self.left_hand_sides.iter().enumerate().collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
if query.is_in_final_state() {
|
||||
// If we're in a final state (which means an optimization is
|
||||
// applicable) then record that fact, but keep going. We don't
|
||||
// want to stop yet, because we might discover another,
|
||||
// more-specific optimization that is also applicable if we keep
|
||||
// going. And we always want to apply the most specific
|
||||
// optimization that matches.
|
||||
log::trace!("Found a match at state {:?}", query.current_state());
|
||||
r#final = Some((query.current_state(), self.actions.len()));
|
||||
}
|
||||
|
||||
// Anything following a `Else` transition doesn't care about the
|
||||
// result of this match operation, so if we partially follow the
|
||||
// current non-`Else` path, but don't ultimately find a matching
|
||||
// optimization, we want to be able to backtrack to this state and
|
||||
// then try taking the `Else` transition.
|
||||
if query.has_transition_on(&Err(Else)) {
|
||||
self.backtracking_states.push((
|
||||
query.current_state(),
|
||||
self.actions.len(),
|
||||
self.left_hand_sides.len(),
|
||||
));
|
||||
}
|
||||
|
||||
let match_op = match query.current_state_data() {
|
||||
None => break,
|
||||
Some(op) => op,
|
||||
};
|
||||
|
||||
let input = self.eval_match_op(context, root, *match_op);
|
||||
|
||||
let actions = if let Some(actions) = query.next(&input) {
|
||||
actions
|
||||
} else if r#final.is_some() {
|
||||
break;
|
||||
} else if let Some((state, actions_len, lhs_len)) = self.backtracking_states.pop() {
|
||||
query.go_to_state(state);
|
||||
self.actions.truncate(actions_len);
|
||||
self.left_hand_sides.truncate(lhs_len);
|
||||
query
|
||||
.next(&Err(Else))
|
||||
.expect("backtracking states always have `Else` transitions")
|
||||
} else {
|
||||
break;
|
||||
};
|
||||
|
||||
self.actions.extend(actions.iter().copied());
|
||||
}
|
||||
|
||||
// If `final` is none, then we didn't encounter any final states, so
|
||||
// there are no applicable optimizations.
|
||||
let (final_state, actions_len) = match r#final {
|
||||
Some(f) => f,
|
||||
None => {
|
||||
log::trace!("No optimizations matched");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// Go to the last final state we saw, reset the LHS and RHS to how
|
||||
// they were at the time we saw the final state, and process the
|
||||
// final actions.
|
||||
self.actions.truncate(actions_len);
|
||||
query.go_to_state(final_state);
|
||||
let final_actions = query.finish().expect("should be in a final state");
|
||||
self.actions.extend(final_actions.iter().copied());
|
||||
self.eval_actions(context, root);
|
||||
|
||||
// And finally, the root of the RHS for this optimization is the
|
||||
// last entry in `self.right_hand_sides`, so replace the old root
|
||||
// instruction with this one!
|
||||
let result = self.right_hand_sides.pop().unwrap();
|
||||
let new_root = self.instr_set.replace_instruction(context, root, result);
|
||||
Some(new_root)
|
||||
}
|
||||
|
||||
/// Keep applying peephole optimizations to the given instruction until none
|
||||
/// can be applied anymore.
|
||||
pub fn apply_all(
|
||||
&mut self,
|
||||
context: &mut TInstructionSet::Context,
|
||||
mut inst: TInstructionSet::Instruction,
|
||||
) {
|
||||
loop {
|
||||
if let Some(new_inst) = self.apply_one(context, inst) {
|
||||
inst = new_inst;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
//! Parts of instructions.
|
||||
|
||||
use crate::cc::ConditionCode;
|
||||
use crate::r#type::BitWidth;
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// A constant value.
|
||||
///
|
||||
/// Whether an integer is interpreted as signed or unsigned depends on the
|
||||
/// operations applied to it.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Constant {
|
||||
/// A boolean of the given width.
|
||||
Bool(bool, BitWidth),
|
||||
|
||||
/// An integer constant of the given width,
|
||||
Int(u64, BitWidth),
|
||||
}
|
||||
|
||||
/// A part of an instruction, or a whole instruction itself.
|
||||
///
|
||||
/// These are the different values that can be matched in an optimization's
|
||||
/// left-hand side and then built up in its right-hand side.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Part<I>
|
||||
where
|
||||
I: Copy + Debug + Eq,
|
||||
{
|
||||
/// An instruction (or result of an instruction).
|
||||
Instruction(I),
|
||||
|
||||
/// A constant value.
|
||||
Constant(Constant),
|
||||
|
||||
/// A condition code.
|
||||
ConditionCode(ConditionCode),
|
||||
}
|
||||
|
||||
impl<I> From<Constant> for Part<I>
|
||||
where
|
||||
I: Copy + Debug + Eq,
|
||||
{
|
||||
#[inline]
|
||||
fn from(c: Constant) -> Part<I> {
|
||||
Part::Constant(c)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> From<ConditionCode> for Part<I>
|
||||
where
|
||||
I: Copy + Debug + Eq,
|
||||
{
|
||||
#[inline]
|
||||
fn from(c: ConditionCode) -> Part<I> {
|
||||
Part::ConditionCode(c)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! accessors {
|
||||
( $( $variant:ident , $result:ty , $getter:ident , $is:ident , $unwrap:ident ; )* ) => {
|
||||
$(
|
||||
#[inline]
|
||||
#[allow(missing_docs)]
|
||||
pub fn $getter(&self) -> Option<$result> {
|
||||
match *self {
|
||||
Self::$variant(x, ..) => Some(x),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(missing_docs)]
|
||||
pub fn $is(&self) -> bool {
|
||||
self.$getter().is_some()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(missing_docs)]
|
||||
pub fn $unwrap(&self) -> $result {
|
||||
self.$getter().expect(concat!("failed to unwrap `", stringify!($variant), "`"))
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
impl Constant {
|
||||
/// If this is any kind of integer constant, get it as a 64-bit unsigned
|
||||
/// integer.
|
||||
pub fn as_int(&self) -> Option<u64> {
|
||||
match *self {
|
||||
Constant::Bool(..) => None,
|
||||
Constant::Int(x, _) => Some(x),
|
||||
}
|
||||
}
|
||||
|
||||
/// If this is any kind of boolean constant, get its value.
|
||||
pub fn as_bool(&self) -> Option<bool> {
|
||||
match *self {
|
||||
Constant::Bool(b, _) => Some(b),
|
||||
Constant::Int(..) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The number of bits required to represent this constant value's type.
|
||||
pub fn bit_width(&self, root_width: u8) -> u8 {
|
||||
match *self {
|
||||
Constant::Bool(_, w) | Constant::Int(_, w) => {
|
||||
if let Some(w) = w.fixed_width() {
|
||||
w
|
||||
} else {
|
||||
debug_assert!(w.is_polymorphic());
|
||||
root_width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Part<I>
|
||||
where
|
||||
I: Copy + Debug + Eq,
|
||||
{
|
||||
accessors! {
|
||||
Instruction, I, as_instruction, is_instruction, unwrap_instruction;
|
||||
Constant, Constant, as_constant, is_constant, unwrap_constant;
|
||||
ConditionCode, ConditionCode, as_condition_code, is_condition_code, unwrap_condition_code;
|
||||
}
|
||||
}
|
||||
@@ -1,262 +0,0 @@
|
||||
//! Types.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
|
||||
/// A bit width of a type.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[repr(u8)]
|
||||
pub enum BitWidth {
|
||||
/// Polymorphic over bit width, with the same width as the root of the
|
||||
/// optimization's LHS and RHS.
|
||||
Polymorphic = 0,
|
||||
|
||||
/// A fixed bit width of 1.
|
||||
One = 1,
|
||||
|
||||
/// A fixed bit width of 8.
|
||||
Eight = 8,
|
||||
|
||||
/// A fixed bit width of 16.
|
||||
Sixteen = 16,
|
||||
|
||||
/// A fixed bit width of 32.
|
||||
ThirtyTwo = 32,
|
||||
|
||||
/// A fixed bit width of 64.
|
||||
SixtyFour = 64,
|
||||
|
||||
/// A fixed bit width of 128.
|
||||
OneTwentyEight = 128,
|
||||
}
|
||||
|
||||
/// The kind of type we are looking at: either an integer kind or boolean kind.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum Kind {
|
||||
/// Integer kind.
|
||||
Int,
|
||||
|
||||
/// Boolean kind.
|
||||
Bool,
|
||||
|
||||
/// CPU flags kind.
|
||||
CpuFlags,
|
||||
|
||||
/// Void kind.
|
||||
Void,
|
||||
}
|
||||
|
||||
/// A type a value or the result of an operation.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct Type {
|
||||
/// This type's kind.
|
||||
pub kind: Kind,
|
||||
|
||||
/// This type's bit width.
|
||||
pub bit_width: BitWidth,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for BitWidth {
|
||||
type Error = &'static str;
|
||||
|
||||
#[inline]
|
||||
fn try_from(x: u8) -> Result<Self, Self::Error> {
|
||||
Ok(match x {
|
||||
1 => Self::One,
|
||||
8 => Self::Eight,
|
||||
16 => Self::Sixteen,
|
||||
32 => Self::ThirtyTwo,
|
||||
64 => Self::SixtyFour,
|
||||
128 => Self::OneTwentyEight,
|
||||
_ => return Err("not a valid bit width"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Type {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self.kind {
|
||||
Kind::CpuFlags => return write!(f, "cpu-flags"),
|
||||
Kind::Void => return write!(f, "void"),
|
||||
Kind::Int => write!(f, "i")?,
|
||||
Kind::Bool => write!(f, "b")?,
|
||||
}
|
||||
match self.bit_width {
|
||||
BitWidth::Polymorphic => write!(f, "NN"),
|
||||
otherwise => write!(f, "{}", otherwise as u8),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BitWidth {
|
||||
/// Is this a polymorphic bit width?
|
||||
pub fn is_polymorphic(&self) -> bool {
|
||||
matches!(self, BitWidth::Polymorphic)
|
||||
}
|
||||
|
||||
/// Get this width in bits, unless this is a polymorphic bit width.
|
||||
pub fn fixed_width(&self) -> Option<u8> {
|
||||
if self.is_polymorphic() {
|
||||
None
|
||||
} else {
|
||||
Some(*self as u8)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! type_ctors {
|
||||
( $( $( #[$attr:meta] )* $name:ident ( $kind:ident , $width:ident ) ; )* ) => {
|
||||
$(
|
||||
$( #[$attr] )*
|
||||
pub const fn $name() -> Self {
|
||||
Type {
|
||||
kind: Kind::$kind,
|
||||
bit_width: BitWidth::$width,
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
impl Type {
|
||||
type_ctors! {
|
||||
/// Get the `i1` type.
|
||||
i1(Int, One);
|
||||
/// Get the `i8` type.
|
||||
i8(Int, Eight);
|
||||
/// Get the `i16` type.
|
||||
i16(Int, Sixteen);
|
||||
/// Get the `i32` type.
|
||||
i32(Int, ThirtyTwo);
|
||||
/// Get the `i64` type.
|
||||
i64(Int, SixtyFour);
|
||||
/// Get the `i128` type.
|
||||
i128(Int, OneTwentyEight);
|
||||
/// Get the `b1` type.
|
||||
b1(Bool, One);
|
||||
/// Get the `b8` type.
|
||||
b8(Bool, Eight);
|
||||
/// Get the `b16` type.
|
||||
b16(Bool, Sixteen);
|
||||
/// Get the `b32` type.
|
||||
b32(Bool, ThirtyTwo);
|
||||
/// Get the `b64` type.
|
||||
b64(Bool, SixtyFour);
|
||||
/// Get the `b128` type.
|
||||
b128(Bool, OneTwentyEight);
|
||||
/// Get the CPU flags type.
|
||||
cpu_flags(CpuFlags, One);
|
||||
/// Get the void type.
|
||||
void(Void, One);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "construct")]
|
||||
mod tok {
|
||||
use wast::custom_keyword;
|
||||
custom_keyword!(b1);
|
||||
custom_keyword!(b8);
|
||||
custom_keyword!(b16);
|
||||
custom_keyword!(b32);
|
||||
custom_keyword!(b64);
|
||||
custom_keyword!(b128);
|
||||
custom_keyword!(i1);
|
||||
custom_keyword!(i8);
|
||||
custom_keyword!(i16);
|
||||
custom_keyword!(i32);
|
||||
custom_keyword!(i64);
|
||||
custom_keyword!(i128);
|
||||
}
|
||||
|
||||
#[cfg(feature = "construct")]
|
||||
impl<'a> wast::parser::Parse<'a> for Type {
|
||||
fn parse(p: wast::parser::Parser<'a>) -> wast::parser::Result<Self> {
|
||||
if p.peek::<tok::b1>() {
|
||||
p.parse::<tok::b1>()?;
|
||||
return Ok(Type {
|
||||
kind: Kind::Bool,
|
||||
bit_width: BitWidth::One,
|
||||
});
|
||||
}
|
||||
if p.peek::<tok::b8>() {
|
||||
p.parse::<tok::b8>()?;
|
||||
return Ok(Type {
|
||||
kind: Kind::Bool,
|
||||
bit_width: BitWidth::Eight,
|
||||
});
|
||||
}
|
||||
if p.peek::<tok::b16>() {
|
||||
p.parse::<tok::b16>()?;
|
||||
return Ok(Type {
|
||||
kind: Kind::Bool,
|
||||
bit_width: BitWidth::Sixteen,
|
||||
});
|
||||
}
|
||||
if p.peek::<tok::b32>() {
|
||||
p.parse::<tok::b32>()?;
|
||||
return Ok(Type {
|
||||
kind: Kind::Bool,
|
||||
bit_width: BitWidth::ThirtyTwo,
|
||||
});
|
||||
}
|
||||
if p.peek::<tok::b64>() {
|
||||
p.parse::<tok::b64>()?;
|
||||
return Ok(Type {
|
||||
kind: Kind::Bool,
|
||||
bit_width: BitWidth::SixtyFour,
|
||||
});
|
||||
}
|
||||
if p.peek::<tok::b128>() {
|
||||
p.parse::<tok::b128>()?;
|
||||
return Ok(Type {
|
||||
kind: Kind::Bool,
|
||||
bit_width: BitWidth::OneTwentyEight,
|
||||
});
|
||||
}
|
||||
if p.peek::<tok::i1>() {
|
||||
p.parse::<tok::i1>()?;
|
||||
return Ok(Type {
|
||||
kind: Kind::Int,
|
||||
bit_width: BitWidth::One,
|
||||
});
|
||||
}
|
||||
if p.peek::<tok::i8>() {
|
||||
p.parse::<tok::i8>()?;
|
||||
return Ok(Type {
|
||||
kind: Kind::Int,
|
||||
bit_width: BitWidth::Eight,
|
||||
});
|
||||
}
|
||||
if p.peek::<tok::i16>() {
|
||||
p.parse::<tok::i16>()?;
|
||||
return Ok(Type {
|
||||
kind: Kind::Int,
|
||||
bit_width: BitWidth::Sixteen,
|
||||
});
|
||||
}
|
||||
if p.peek::<tok::i32>() {
|
||||
p.parse::<tok::i32>()?;
|
||||
return Ok(Type {
|
||||
kind: Kind::Int,
|
||||
bit_width: BitWidth::ThirtyTwo,
|
||||
});
|
||||
}
|
||||
if p.peek::<tok::i64>() {
|
||||
p.parse::<tok::i64>()?;
|
||||
return Ok(Type {
|
||||
kind: Kind::Int,
|
||||
bit_width: BitWidth::SixtyFour,
|
||||
});
|
||||
}
|
||||
if p.peek::<tok::i128>() {
|
||||
p.parse::<tok::i128>()?;
|
||||
return Ok(Type {
|
||||
kind: Kind::Int,
|
||||
bit_width: BitWidth::OneTwentyEight,
|
||||
});
|
||||
}
|
||||
Err(p.error("expected an ascribed type"))
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
//! Unquote operator definition.
|
||||
|
||||
peepmatic_traits::define_operator! {
|
||||
/// Compile-time unquote operators.
|
||||
///
|
||||
/// These are used in the right-hand side to perform compile-time evaluation of
|
||||
/// constants matched on the left-hand side.
|
||||
#[allow(missing_docs)]
|
||||
UnquoteOperator {
|
||||
band => Band {
|
||||
parameters(iNN, iNN);
|
||||
result(iNN);
|
||||
}
|
||||
bor => Bor {
|
||||
parameters(iNN, iNN);
|
||||
result(iNN);
|
||||
}
|
||||
bxor => Bxor {
|
||||
parameters(iNN, iNN);
|
||||
result(iNN);
|
||||
}
|
||||
iadd => Iadd {
|
||||
parameters(iNN, iNN);
|
||||
result(iNN);
|
||||
}
|
||||
imul => Imul {
|
||||
parameters(iNN, iNN);
|
||||
result(iNN);
|
||||
}
|
||||
isub => Isub {
|
||||
parameters(iNN, iNN);
|
||||
result(iNN);
|
||||
}
|
||||
log2 => Log2 {
|
||||
parameters(iNN);
|
||||
result(iNN);
|
||||
}
|
||||
neg => Neg {
|
||||
parameters(iNN);
|
||||
result(iNN);
|
||||
}
|
||||
}
|
||||
parse_cfg(feature = "construct");
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
[package]
|
||||
name = "peepmatic-souper"
|
||||
version = "0.78.0"
|
||||
authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
|
||||
edition = "2018"
|
||||
license = "Apache-2.0 WITH LLVM-exception"
|
||||
description = "Converting Souper optimizations into Peepmatic DSL"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
souper-ir = { version = "2.1.0", features = ["parse"] }
|
||||
log = "0.4.8"
|
||||
|
||||
[dev-dependencies]
|
||||
peepmatic = { path = "../..", version = "=0.78.0" }
|
||||
peepmatic-test-operator = { version = "=0.78.0", path = "../test-operator" }
|
||||
wast = "38.0.0"
|
||||
@@ -1,220 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
--- LLVM Exceptions to the Apache 2.0 License ----
|
||||
|
||||
As an exception, if, as a result of your compiling your source code, portions
|
||||
of this Software are embedded into an Object form of such source code, you
|
||||
may redistribute such embedded portions in such Object form without complying
|
||||
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
|
||||
|
||||
In addition, if you combine or link compiled forms of this Software with
|
||||
software that is licensed under the GPLv2 ("Combined Software") and if a
|
||||
court of competent jurisdiction determines that the patent provision (Section
|
||||
3), the indemnity provision (Section 9) or other Section of the License
|
||||
conflicts with the conditions of the GPLv2, you may retroactively and
|
||||
prospectively choose to deem waived or otherwise exclude such Section(s) of
|
||||
the License, but only in their entirety and only with respect to the Combined
|
||||
Software.
|
||||
|
||||
@@ -1,710 +0,0 @@
|
||||
//! Converting Souper optimizations into Peepmatic DSL.
|
||||
//!
|
||||
//! Conversion from Souper into Peepmatic is implemented with a straightforward,
|
||||
//! top-down recursive traversal of the optimization's left- and right-hand side
|
||||
//! expression DAGs. Most Souper instructions have a corresponding Peepmatic
|
||||
//! instruction. If we run into an instruction where that isn't the case, we
|
||||
//! skip that Souper optimization and move on to the next one.
|
||||
//!
|
||||
//! Note that Souper fully supports DAGs, for example:
|
||||
//!
|
||||
//! ```text
|
||||
//! %0 = var
|
||||
//! %1 = add 1, %0
|
||||
//! %2 = add %1, %1 ;; Two edges to `%1` makes this a DAG.
|
||||
//! ```
|
||||
//!
|
||||
//! On the other hand, Peepmatic only currently supports trees, so shared
|
||||
//! subexpressions are duplicated:
|
||||
//!
|
||||
//! ```text
|
||||
//! (iadd (iadd 1 $x)
|
||||
//! (iadd 1 $x)) ;; The shared subexpression is duplicated.
|
||||
//! ```
|
||||
//!
|
||||
//! This does not affect correctness.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use souper_ir::ast;
|
||||
use std::path::Path;
|
||||
|
||||
/// Maximum recursion depth, to avoid blowing the stack.
|
||||
const MAX_DEPTH: u8 = 50;
|
||||
|
||||
/// Convert a file containing Souper optimizations into Peepmatic DSL.
|
||||
pub fn convert_file(path: &Path) -> Result<String> {
|
||||
let souper = std::fs::read_to_string(path)
|
||||
.with_context(|| format!("failed to read: {}", path.display()))?;
|
||||
convert_str(&souper, Some(path))
|
||||
}
|
||||
|
||||
/// Convert a string of Souper optimizations into Peepmatic DSL.
|
||||
///
|
||||
/// The optional `filename` parameter is used for better error messages.
|
||||
pub fn convert_str(souper: &str, filename: Option<&Path>) -> Result<String> {
|
||||
let mut peepmatic = String::new();
|
||||
|
||||
let replacements = souper_ir::parse::parse_replacements_str(souper, filename)?;
|
||||
for replacement in replacements {
|
||||
let (statements, lhs, rhs) = match replacement {
|
||||
ast::Replacement::LhsRhs {
|
||||
statements,
|
||||
lhs,
|
||||
rhs,
|
||||
} => {
|
||||
if !lhs.attributes.is_empty() {
|
||||
log::warn!("cannot translate Souper attributes to Peepmatic DSL");
|
||||
continue;
|
||||
}
|
||||
(statements, lhs.value, rhs)
|
||||
}
|
||||
ast::Replacement::Cand { statements, cand } => {
|
||||
if !cand.attributes.is_empty() {
|
||||
log::warn!("cannot translate Souper attributes to Peepmatic DSL");
|
||||
continue;
|
||||
}
|
||||
let lhs = match cand.lhs {
|
||||
ast::Operand::Value(v) => v,
|
||||
ast::Operand::Constant(_) => {
|
||||
log::warn!("optimization's LHS must not be a constant");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
(statements, lhs, cand.rhs)
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(s) = convert_replacement(&statements, lhs, rhs) {
|
||||
peepmatic.push_str(&s);
|
||||
peepmatic.push('\n');
|
||||
}
|
||||
}
|
||||
|
||||
Ok(peepmatic)
|
||||
}
|
||||
|
||||
fn convert_replacement(
|
||||
statements: &ast::Arena<ast::Statement>,
|
||||
lhs: ast::ValueId,
|
||||
rhs: ast::Operand,
|
||||
) -> Option<String> {
|
||||
let lhs = convert_lhs(statements, lhs)?;
|
||||
let rhs = convert_rhs(statements, rhs)?;
|
||||
Some(format!("(=> {}\n {})\n", lhs, rhs))
|
||||
}
|
||||
|
||||
fn convert_lhs(statements: &ast::Arena<ast::Statement>, lhs: ast::ValueId) -> Option<String> {
|
||||
let mut tys = vec![];
|
||||
let pattern = convert_operand(statements, lhs.into(), &mut tys, 0)?;
|
||||
|
||||
Some(if tys.is_empty() {
|
||||
pattern
|
||||
} else {
|
||||
let mut lhs = format!("(when {}", pattern);
|
||||
for (name, width) in tys {
|
||||
lhs.push_str("\n ");
|
||||
lhs.push_str(&format!("(bit-width {} {})", name, width));
|
||||
}
|
||||
lhs.push(')');
|
||||
lhs
|
||||
})
|
||||
}
|
||||
|
||||
fn convert_name(name: &str) -> String {
|
||||
debug_assert!(name.starts_with('%'));
|
||||
debug_assert!(name.len() >= 2);
|
||||
let c = name.as_bytes()[1];
|
||||
if b'a' <= c && c <= b'z' {
|
||||
format!("${}", &name[1..])
|
||||
} else {
|
||||
format!("$v{}", &name[1..])
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_operand(
|
||||
statements: &ast::Arena<ast::Statement>,
|
||||
value: ast::Operand,
|
||||
tys: &mut Vec<(String, u16)>,
|
||||
depth: u8,
|
||||
) -> Option<String> {
|
||||
if depth > MAX_DEPTH {
|
||||
log::warn!("optimization too deep to translate recursively; skipping");
|
||||
return None;
|
||||
}
|
||||
|
||||
let value = match value {
|
||||
ast::Operand::Value(v) => v,
|
||||
ast::Operand::Constant(c) => return Some(format!("{}", c.value)),
|
||||
};
|
||||
|
||||
match &statements[value.into()] {
|
||||
ast::Statement::Pc(_) | ast::Statement::BlockPc(_) => {
|
||||
log::warn!("Peepmatic does not support path conditions yet");
|
||||
None
|
||||
}
|
||||
ast::Statement::Assignment(assn) => {
|
||||
debug_assert!(assn.name.starts_with('%'));
|
||||
|
||||
if !assn.attributes.is_empty() {
|
||||
log::warn!("Peepmatic does not support attributes");
|
||||
return None;
|
||||
}
|
||||
|
||||
match assn.value {
|
||||
ast::AssignmentRhs::Block(_)
|
||||
| ast::AssignmentRhs::Phi(_)
|
||||
| ast::AssignmentRhs::ReservedConst
|
||||
| ast::AssignmentRhs::ReservedInst => {
|
||||
log::warn!("Peepmatic does not support {:?}", assn.value);
|
||||
return None;
|
||||
}
|
||||
ast::AssignmentRhs::Var => {
|
||||
if let Some(ast::Type { width }) = assn.r#type {
|
||||
match width {
|
||||
1 | 8 | 16 | 32 | 64 | 128 => {
|
||||
tys.push((convert_name(&assn.name), width))
|
||||
}
|
||||
_ => {
|
||||
log::warn!("unsupported bit width: {}", width);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(format!("{}", convert_name(&assn.name)))
|
||||
}
|
||||
ast::AssignmentRhs::Instruction(inst) => match inst {
|
||||
// Unsupported instructions.
|
||||
ast::Instruction::Bswap { .. }
|
||||
| ast::Instruction::SaddWithOverflow { .. }
|
||||
| ast::Instruction::UaddWithOverflow { .. }
|
||||
| ast::Instruction::SsubWithOverflow { .. }
|
||||
| ast::Instruction::UsubWithOverflow { .. }
|
||||
| ast::Instruction::SmulWithOverflow { .. }
|
||||
| ast::Instruction::UmulWithOverflow { .. }
|
||||
| ast::Instruction::Fshl { .. }
|
||||
| ast::Instruction::Fshr { .. }
|
||||
| ast::Instruction::ExtractValue { .. }
|
||||
| ast::Instruction::Hole
|
||||
| ast::Instruction::Freeze { .. } => {
|
||||
log::warn!("Operation is not supported by Peepmatic: {:?}", inst);
|
||||
return None;
|
||||
}
|
||||
|
||||
// Comparison instructions return an `i1` in Souper but a
|
||||
// `b1` in clif/Peepmatic. The `b1` needs to be extended
|
||||
// into an `i{8,16,32,654,128}` for us to continue, so these
|
||||
// instructions are special cased when handling `sext` and
|
||||
// `zext` conversions.
|
||||
ast::Instruction::Eq { .. }
|
||||
| ast::Instruction::Ne { .. }
|
||||
| ast::Instruction::Ult { .. }
|
||||
| ast::Instruction::Slt { .. }
|
||||
| ast::Instruction::Ule { .. }
|
||||
| ast::Instruction::Sle { .. } => {
|
||||
log::warn!("unsupported comparison");
|
||||
return None;
|
||||
}
|
||||
|
||||
// These instructions require type ascriptions.
|
||||
ast::Instruction::Zext { a } => {
|
||||
let width = require_width(assn.r#type)?;
|
||||
let cmp = try_convert_comparison(statements, a, tys, depth)?;
|
||||
if let Some(cmp) = cmp {
|
||||
Some(format!("(bint {})", cmp))
|
||||
} else {
|
||||
convert_operation(statements, "uextend", &[a], Some(width), tys, depth)
|
||||
}
|
||||
}
|
||||
ast::Instruction::Sext { a } => {
|
||||
let width = require_width(assn.r#type)?;
|
||||
let cmp = try_convert_comparison(statements, a, tys, depth)?;
|
||||
if let Some(cmp) = cmp {
|
||||
Some(format!("(bint {})", cmp))
|
||||
} else {
|
||||
convert_operation(statements, "sextend", &[a], Some(width), tys, depth)
|
||||
}
|
||||
}
|
||||
ast::Instruction::Trunc { a } => {
|
||||
let width = require_width(assn.r#type)?;
|
||||
convert_operation(statements, "ireduce", &[a], Some(width), tys, depth)
|
||||
}
|
||||
|
||||
ast::Instruction::Add { a, b }
|
||||
| ast::Instruction::AddNsw { a, b }
|
||||
| ast::Instruction::AddNuw { a, b }
|
||||
| ast::Instruction::AddNw { a, b } => convert_commutative_operation(
|
||||
statements, "iadd", "iadd_imm", a, b, tys, depth,
|
||||
),
|
||||
ast::Instruction::Sub { a, b }
|
||||
| ast::Instruction::SubNsw { a, b }
|
||||
| ast::Instruction::SubNuw { a, b }
|
||||
| ast::Instruction::SubNw { a, b } => {
|
||||
if let ast::Operand::Constant(c) = b {
|
||||
Some(format!(
|
||||
"(iadd_imm -{} {})",
|
||||
c.value,
|
||||
convert_operand(statements, a, tys, depth + 1)?,
|
||||
))
|
||||
} else if let ast::Operand::Constant(c) = a {
|
||||
Some(format!(
|
||||
"(irsub_imm {} {})",
|
||||
c.value,
|
||||
convert_operand(statements, b, tys, depth + 1)?,
|
||||
))
|
||||
} else {
|
||||
convert_operation(statements, "isub", &[a, b], None, tys, depth)
|
||||
}
|
||||
}
|
||||
ast::Instruction::Mul { a, b }
|
||||
| ast::Instruction::MulNsw { a, b }
|
||||
| ast::Instruction::MulNuw { a, b }
|
||||
| ast::Instruction::MulNw { a, b } => convert_commutative_operation(
|
||||
statements, "imul", "imul_imm", a, b, tys, depth,
|
||||
),
|
||||
ast::Instruction::Udiv { a, b } | ast::Instruction::UdivExact { a, b } => {
|
||||
if let ast::Operand::Constant(c) = b {
|
||||
Some(format!(
|
||||
"(udiv_imm {} {})",
|
||||
c.value,
|
||||
convert_operand(statements, a, tys, depth + 1)?,
|
||||
))
|
||||
} else {
|
||||
convert_operation(statements, "udiv", &[a, b], None, tys, depth)
|
||||
}
|
||||
}
|
||||
ast::Instruction::Sdiv { a, b } | ast::Instruction::SdivExact { a, b } => {
|
||||
if let ast::Operand::Constant(c) = b {
|
||||
Some(format!(
|
||||
"(sdiv_imm {} {})",
|
||||
c.value,
|
||||
convert_operand(statements, a, tys, depth + 1)?,
|
||||
))
|
||||
} else {
|
||||
convert_operation(statements, "sdiv", &[a, b], None, tys, depth)
|
||||
}
|
||||
}
|
||||
ast::Instruction::Urem { a, b } => {
|
||||
if let ast::Operand::Constant(c) = b {
|
||||
Some(format!(
|
||||
"(urem_imm {} {})",
|
||||
c.value,
|
||||
convert_operand(statements, a, tys, depth + 1)?,
|
||||
))
|
||||
} else {
|
||||
convert_operation(statements, "urem", &[a, b], None, tys, depth)
|
||||
}
|
||||
}
|
||||
ast::Instruction::Srem { a, b } => {
|
||||
if let ast::Operand::Constant(c) = b {
|
||||
Some(format!(
|
||||
"(srem_imm {} {})",
|
||||
c.value,
|
||||
convert_operand(statements, a, tys, depth + 1)?,
|
||||
))
|
||||
} else {
|
||||
convert_operation(statements, "srem", &[a, b], None, tys, depth)
|
||||
}
|
||||
}
|
||||
ast::Instruction::And { a, b } => convert_commutative_operation(
|
||||
statements, "band", "band_imm", a, b, tys, depth,
|
||||
),
|
||||
ast::Instruction::Or { a, b } => convert_commutative_operation(
|
||||
statements, "bor", "bor_imm", a, b, tys, depth,
|
||||
),
|
||||
ast::Instruction::Xor { a, b } => convert_commutative_operation(
|
||||
statements, "bxor", "bxor_imm", a, b, tys, depth,
|
||||
),
|
||||
ast::Instruction::Shl { a, b }
|
||||
| ast::Instruction::ShlNsw { a, b }
|
||||
| ast::Instruction::ShlNuw { a, b }
|
||||
| ast::Instruction::ShlNw { a, b } => {
|
||||
if let ast::Operand::Constant(c) = b {
|
||||
Some(format!(
|
||||
"(ishl_imm {} {})",
|
||||
c.value,
|
||||
convert_operand(statements, a, tys, depth + 1)?,
|
||||
))
|
||||
} else {
|
||||
convert_operation(statements, "ishl", &[a, b], None, tys, depth)
|
||||
}
|
||||
}
|
||||
ast::Instruction::Lshr { a, b } | ast::Instruction::LshrExact { a, b } => {
|
||||
if let ast::Operand::Constant(c) = b {
|
||||
Some(format!(
|
||||
"(ushr_imm {} {})",
|
||||
c.value,
|
||||
convert_operand(statements, a, tys, depth + 1)?,
|
||||
))
|
||||
} else {
|
||||
convert_operation(statements, "ushr", &[a, b], None, tys, depth)
|
||||
}
|
||||
}
|
||||
ast::Instruction::Ashr { a, b } | ast::Instruction::AshrExact { a, b } => {
|
||||
if let ast::Operand::Constant(c) = b {
|
||||
Some(format!(
|
||||
"(sshr_imm {} {})",
|
||||
c.value,
|
||||
convert_operand(statements, a, tys, depth + 1)?,
|
||||
))
|
||||
} else {
|
||||
convert_operation(statements, "sshr", &[a, b], None, tys, depth)
|
||||
}
|
||||
}
|
||||
ast::Instruction::Select { a, b, c } => {
|
||||
convert_operation(statements, "select", &[a, b, c], None, tys, depth)
|
||||
}
|
||||
ast::Instruction::Ctpop { a } => {
|
||||
convert_operation(statements, "popcnt", &[a], None, tys, depth)
|
||||
}
|
||||
ast::Instruction::BitReverse { a } => {
|
||||
convert_operation(statements, "bitrev", &[a], None, tys, depth)
|
||||
}
|
||||
ast::Instruction::Cttz { a } => {
|
||||
convert_operation(statements, "ctz", &[a], None, tys, depth)
|
||||
}
|
||||
ast::Instruction::Ctlz { a } => {
|
||||
convert_operation(statements, "clz", &[a], None, tys, depth)
|
||||
}
|
||||
ast::Instruction::SaddSat { a, b } => {
|
||||
convert_operation(statements, "sadd_sat", &[a, b], None, tys, depth)
|
||||
}
|
||||
ast::Instruction::UaddSat { a, b } => {
|
||||
convert_operation(statements, "uadd_sat", &[a, b], None, tys, depth)
|
||||
}
|
||||
ast::Instruction::SsubSat { a, b } => {
|
||||
convert_operation(statements, "ssub_sat", &[a, b], None, tys, depth)
|
||||
}
|
||||
ast::Instruction::UsubSat { a, b } => {
|
||||
convert_operation(statements, "usub_sat", &[a, b], None, tys, depth)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Try and convert `value` into an `icmp` comparison.
|
||||
///
|
||||
/// Returns `Some(Some(icmp))` if the conversion is successful.
|
||||
///
|
||||
/// Returns `Some(None)` if `value` is not a comparison.
|
||||
///
|
||||
/// Returns `None` in the case where `value` cannot be converted to Peepmatic
|
||||
/// DSL at all.
|
||||
fn try_convert_comparison(
|
||||
statements: &ast::Arena<ast::Statement>,
|
||||
value: ast::Operand,
|
||||
tys: &mut Vec<(String, u16)>,
|
||||
depth: u8,
|
||||
) -> Option<Option<String>> {
|
||||
let value = match value {
|
||||
ast::Operand::Value(v) => v,
|
||||
ast::Operand::Constant(_) => return None,
|
||||
};
|
||||
let assn = match &statements[value.into()] {
|
||||
ast::Statement::Assignment(a) => a,
|
||||
_ => return None,
|
||||
};
|
||||
Some(match assn.value {
|
||||
ast::AssignmentRhs::Instruction(ast::Instruction::Eq { a, b }) => Some(convert_operation(
|
||||
statements,
|
||||
"icmp eq",
|
||||
&[a, b],
|
||||
None,
|
||||
tys,
|
||||
depth,
|
||||
)?),
|
||||
ast::AssignmentRhs::Instruction(ast::Instruction::Ne { a, b }) => Some(convert_operation(
|
||||
statements,
|
||||
"icmp ne",
|
||||
&[a, b],
|
||||
None,
|
||||
tys,
|
||||
depth,
|
||||
)?),
|
||||
ast::AssignmentRhs::Instruction(ast::Instruction::Ult { a, b }) => Some(convert_operation(
|
||||
statements,
|
||||
"icmp ult",
|
||||
&[a, b],
|
||||
None,
|
||||
tys,
|
||||
depth,
|
||||
)?),
|
||||
ast::AssignmentRhs::Instruction(ast::Instruction::Slt { a, b }) => Some(convert_operation(
|
||||
statements,
|
||||
"icmp slt",
|
||||
&[a, b],
|
||||
None,
|
||||
tys,
|
||||
depth,
|
||||
)?),
|
||||
ast::AssignmentRhs::Instruction(ast::Instruction::Ule { a, b }) => Some(convert_operation(
|
||||
statements,
|
||||
"icmp ule",
|
||||
&[a, b],
|
||||
None,
|
||||
tys,
|
||||
depth,
|
||||
)?),
|
||||
ast::AssignmentRhs::Instruction(ast::Instruction::Sle { a, b }) => Some(convert_operation(
|
||||
statements,
|
||||
"icmp sle",
|
||||
&[a, b],
|
||||
None,
|
||||
tys,
|
||||
depth,
|
||||
)?),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn require_width(ty: Option<ast::Type>) -> Option<u16> {
|
||||
match ty {
|
||||
Some(ast::Type { width: w @ 8 })
|
||||
| Some(ast::Type { width: w @ 16 })
|
||||
| Some(ast::Type { width: w @ 32 })
|
||||
| Some(ast::Type { width: w @ 64 })
|
||||
| Some(ast::Type { width: w @ 128 }) => Some(w),
|
||||
Some(ty) => {
|
||||
log::warn!("unsupported bit width: {}", ty.width);
|
||||
None
|
||||
}
|
||||
None => {
|
||||
log::warn!("required bit width is missing");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_operation(
|
||||
statements: &ast::Arena<ast::Statement>,
|
||||
operator: &str,
|
||||
operands: &[ast::Operand],
|
||||
ty: Option<u16>,
|
||||
tys: &mut Vec<(String, u16)>,
|
||||
depth: u8,
|
||||
) -> Option<String> {
|
||||
let mut op = format!("({}", operator);
|
||||
|
||||
if let Some(width) = ty {
|
||||
op.push_str(&format!(" {{i{}}}", width));
|
||||
}
|
||||
|
||||
for operand in operands {
|
||||
op.push(' ');
|
||||
let operand = convert_operand(statements, *operand, tys, depth + 1)?;
|
||||
op.push_str(&operand);
|
||||
}
|
||||
op.push(')');
|
||||
Some(op)
|
||||
}
|
||||
|
||||
/// Convert a commutative operation, using the `_imm` form if any of its
|
||||
/// operands is a constant.
|
||||
fn convert_commutative_operation(
|
||||
statements: &ast::Arena<ast::Statement>,
|
||||
operator: &str,
|
||||
operator_imm: &str,
|
||||
a: ast::Operand,
|
||||
b: ast::Operand,
|
||||
tys: &mut Vec<(String, u16)>,
|
||||
depth: u8,
|
||||
) -> Option<String> {
|
||||
Some(match (a, b) {
|
||||
(ast::Operand::Constant(c), _) => format!(
|
||||
"({} {} {})",
|
||||
operator_imm,
|
||||
c.value,
|
||||
convert_operand(statements, b, tys, depth + 1)?,
|
||||
),
|
||||
(_, ast::Operand::Constant(c)) => format!(
|
||||
"({} {} {})",
|
||||
operator_imm,
|
||||
c.value,
|
||||
convert_operand(statements, a, tys, depth + 1)?,
|
||||
),
|
||||
_ => format!(
|
||||
"({} {} {})",
|
||||
operator,
|
||||
convert_operand(statements, a, tys, depth + 1)?,
|
||||
convert_operand(statements, b, tys, depth + 1)?,
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
fn convert_rhs(statements: &ast::Arena<ast::Statement>, rhs: ast::Operand) -> Option<String> {
|
||||
let mut tys = vec![];
|
||||
convert_operand(statements, rhs, &mut tys, 0)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use peepmatic_test_operator::TestOperator;
|
||||
|
||||
fn assert_converts(name: &str, souper: &str, expected: &str) {
|
||||
let expected = expected.trim();
|
||||
eprintln!("expected:\n{}", expected);
|
||||
|
||||
let actual = convert_str(souper, Some(Path::new(name))).unwrap();
|
||||
let actual = actual.trim();
|
||||
eprintln!("actual:\n{}", actual);
|
||||
|
||||
assert_eq!(expected, actual);
|
||||
|
||||
// Assert that the generated Peepmatic DSL parses and verifies.
|
||||
let buf = wast::parser::ParseBuffer::new(actual).expect("peepmatic DSL should lex OK");
|
||||
let opts = match wast::parser::parse::<peepmatic::Optimizations<TestOperator>>(&buf) {
|
||||
Ok(opts) => opts,
|
||||
Err(mut e) => {
|
||||
e.set_path(Path::new(name));
|
||||
e.set_text(actual);
|
||||
eprintln!("{}", e);
|
||||
panic!("peepmatic DSL should parse OK")
|
||||
}
|
||||
};
|
||||
if let Err(mut e) = peepmatic::verify(&opts) {
|
||||
e.set_path(Path::new(name));
|
||||
e.set_text(actual);
|
||||
eprintln!("{}", e);
|
||||
panic!("peepmatic DSL should verify OK")
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! test {
|
||||
( $(
|
||||
$name:ident => converts($souper:expr, $peepmatic:expr $(,)? );
|
||||
)* ) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $name() {
|
||||
assert_converts(stringify!($name), $souper, $peepmatic);
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
test! {
|
||||
simple_lhs_rhs => converts(
|
||||
"
|
||||
%0 = var
|
||||
%1 = mul %0, 2
|
||||
infer %1
|
||||
%2 = shl %0, 1
|
||||
result %2
|
||||
",
|
||||
"\
|
||||
(=> (imul_imm 2 $v0)
|
||||
(ishl_imm 1 $v0))",
|
||||
);
|
||||
|
||||
simple_cand => converts(
|
||||
"
|
||||
%0 = var
|
||||
%1 = mul %0, 2
|
||||
%2 = shl %0, 1
|
||||
cand %1 %2
|
||||
",
|
||||
"\
|
||||
(=> (imul_imm 2 $v0)
|
||||
(ishl_imm 1 $v0))",
|
||||
);
|
||||
|
||||
// These instructions require type ascriptions, so our conversion better
|
||||
// have them.
|
||||
trunc => converts(
|
||||
"
|
||||
%0:i64 = var
|
||||
%1:i32 = trunc %0
|
||||
cand %1 0
|
||||
",
|
||||
"\
|
||||
(=> (when (ireduce {i32} $v0)
|
||||
(bit-width $v0 64))
|
||||
0)",
|
||||
);
|
||||
sext => converts(
|
||||
"
|
||||
%0:i32 = var
|
||||
%1:i64 = sext %0
|
||||
cand %1 0
|
||||
",
|
||||
"\
|
||||
(=> (when (sextend {i64} $v0)
|
||||
(bit-width $v0 32))
|
||||
0)",
|
||||
);
|
||||
zext => converts(
|
||||
"
|
||||
%0:i32 = var
|
||||
%1:i64 = zext %0
|
||||
cand %1 0
|
||||
",
|
||||
"\
|
||||
(=> (when (uextend {i64} $v0)
|
||||
(bit-width $v0 32))
|
||||
0)",
|
||||
);
|
||||
|
||||
// Type annotations on intermediate values (e.g. on %1, %2, and %3) do
|
||||
// not turn into type ascriptions in the Peepmatic DSL.
|
||||
unnecessary_types => converts(
|
||||
"
|
||||
%0:i32 = var
|
||||
%1:i32 = add 1, %0
|
||||
%2:i32 = add 1, %1
|
||||
%3:i32 = add 1, %2
|
||||
%4:i32 = add 3, %0
|
||||
cand %3 %4
|
||||
",
|
||||
"\
|
||||
(=> (when (iadd_imm 1 (iadd_imm 1 (iadd_imm 1 $v0)))
|
||||
(bit-width $v0 32))
|
||||
(iadd_imm 3 $v0))",
|
||||
);
|
||||
|
||||
// Comparisons need to add a `bint` instruction in Peepmatic, since clif
|
||||
// has a separate `b1` type that needs to be extended into an integer.
|
||||
comparison_has_bint => converts(
|
||||
"
|
||||
%0:i32 = var
|
||||
%1:i32 = var
|
||||
%2:i1 = eq %0, %1
|
||||
%3:i32 = zext %2
|
||||
cand %3 0
|
||||
",
|
||||
"\
|
||||
(=> (when (bint (icmp eq $v0 $v1))
|
||||
(bit-width $v0 32)
|
||||
(bit-width $v1 32))
|
||||
0)",
|
||||
);
|
||||
|
||||
// We correctly introduce `_imm` variants of instructions, regardless of
|
||||
// which side the constant is on for commutative instructions.
|
||||
iadd_imm_right => converts(
|
||||
"
|
||||
%0:i32 = var
|
||||
%1:i32 = add %0, 1
|
||||
cand %1 0
|
||||
",
|
||||
"\
|
||||
(=> (when (iadd_imm 1 $v0)
|
||||
(bit-width $v0 32))
|
||||
0)"
|
||||
);
|
||||
iadd_imm_left => converts(
|
||||
"
|
||||
%0:i32 = var
|
||||
%1:i32 = add 1, %0
|
||||
cand %1 0
|
||||
",
|
||||
"\
|
||||
(=> (when (iadd_imm 1 $v0)
|
||||
(bit-width $v0 32))
|
||||
0)"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
[package]
|
||||
name = "peepmatic-test-operator"
|
||||
description = "Operator for usage in peepmatic tests"
|
||||
license = "Apache-2.0 WITH LLVM-exception"
|
||||
version = "0.78.0"
|
||||
authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
peepmatic-traits = { version = "=0.78.0", path = "../traits" }
|
||||
serde = { version = "1.0.105", features = ["derive"] }
|
||||
wast = "38.0.0"
|
||||
@@ -1,220 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
--- LLVM Exceptions to the Apache 2.0 License ----
|
||||
|
||||
As an exception, if, as a result of your compiling your source code, portions
|
||||
of this Software are embedded into an Object form of such source code, you
|
||||
may redistribute such embedded portions in such Object form without complying
|
||||
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
|
||||
|
||||
In addition, if you combine or link compiled forms of this Software with
|
||||
software that is licensed under the GPLv2 ("Combined Software") and if a
|
||||
court of competent jurisdiction determines that the patent provision (Section
|
||||
3), the indemnity provision (Section 9) or other Section of the License
|
||||
conflicts with the conditions of the GPLv2, you may retroactively and
|
||||
prospectively choose to deem waived or otherwise exclude such Section(s) of
|
||||
the License, but only in their entirety and only with respect to the Combined
|
||||
Software.
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
//! This crate defines `TestOperator`: a `TOperator` type for usage in tests.
|
||||
//!
|
||||
//! This allows us to write Peepmatic-specific tests that do not depend on
|
||||
//! building all of Cranelift.
|
||||
|
||||
peepmatic_traits::define_operator! {
|
||||
/// A `TOperator` type for use inside tests.
|
||||
TestOperator {
|
||||
band => Band {
|
||||
parameters(iNN, iNN);
|
||||
result(iNN);
|
||||
}
|
||||
band_imm => BandImm {
|
||||
immediates(iNN);
|
||||
parameters(iNN);
|
||||
result(iNN);
|
||||
}
|
||||
bconst => Bconst {
|
||||
immediates(b1);
|
||||
result(bNN);
|
||||
}
|
||||
bint => Bint {
|
||||
parameters(bNN);
|
||||
result(iNN);
|
||||
}
|
||||
bor => Bor {
|
||||
parameters(iNN, iNN);
|
||||
result(iNN);
|
||||
}
|
||||
bor_imm => BorImm {
|
||||
immediates(iNN);
|
||||
parameters(iNN);
|
||||
result(iNN);
|
||||
}
|
||||
brnz => Brnz {
|
||||
parameters(bool_or_int);
|
||||
result(void);
|
||||
}
|
||||
brz => Brz {
|
||||
parameters(bool_or_int);
|
||||
result(void);
|
||||
}
|
||||
bxor => Bxor {
|
||||
parameters(iNN, iNN);
|
||||
result(iNN);
|
||||
}
|
||||
bxor_imm => BxorImm {
|
||||
immediates(iNN);
|
||||
parameters(iNN);
|
||||
result(iNN);
|
||||
}
|
||||
iadd => Iadd {
|
||||
parameters(iNN, iNN);
|
||||
result(iNN);
|
||||
}
|
||||
iadd_imm => IaddImm {
|
||||
immediates(iNN);
|
||||
parameters(iNN);
|
||||
result(iNN);
|
||||
}
|
||||
icmp => Icmp {
|
||||
immediates(cc);
|
||||
parameters(iNN, iNN);
|
||||
result(b1);
|
||||
}
|
||||
icmp_imm => IcmpImm {
|
||||
immediates(cc, iNN);
|
||||
parameters(iNN);
|
||||
result(b1);
|
||||
}
|
||||
iconst => Iconst {
|
||||
immediates(iNN);
|
||||
result(iNN);
|
||||
}
|
||||
ifcmp => Ifcmp {
|
||||
parameters(iNN, iNN);
|
||||
result(cpu_flags);
|
||||
}
|
||||
ifcmp_imm => IfcmpImm {
|
||||
immediates(iNN);
|
||||
parameters(iNN);
|
||||
result(cpu_flags);
|
||||
}
|
||||
imul => Imul {
|
||||
parameters(iNN, iNN);
|
||||
result(iNN);
|
||||
}
|
||||
imul_imm => ImulImm {
|
||||
immediates(iNN);
|
||||
parameters(iNN);
|
||||
result(iNN);
|
||||
}
|
||||
ireduce => Ireduce {
|
||||
parameters(iNN);
|
||||
result(iMM);
|
||||
is_reduce(true);
|
||||
}
|
||||
irsub_imm => IrsubImm {
|
||||
immediates(iNN);
|
||||
parameters(iNN);
|
||||
result(iNN);
|
||||
}
|
||||
ishl => Ishl {
|
||||
parameters(iNN, iNN);
|
||||
result(iNN);
|
||||
}
|
||||
ishl_imm => IshlImm {
|
||||
immediates(iNN);
|
||||
parameters(iNN);
|
||||
result(iNN);
|
||||
}
|
||||
isub => Isub {
|
||||
parameters(iNN, iNN);
|
||||
result(iNN);
|
||||
}
|
||||
rotl => Rotl {
|
||||
parameters(iNN, iNN);
|
||||
result(iNN);
|
||||
}
|
||||
rotl_imm => RotlImm {
|
||||
immediates(iNN);
|
||||
parameters(iNN);
|
||||
result(iNN);
|
||||
}
|
||||
rotr => Rotr {
|
||||
parameters(iNN, iNN);
|
||||
result(iNN);
|
||||
}
|
||||
rotr_imm => RotrImm {
|
||||
immediates(iNN);
|
||||
parameters(iNN);
|
||||
result(iNN);
|
||||
}
|
||||
sdiv => Sdiv {
|
||||
parameters(iNN, iNN);
|
||||
result(iNN);
|
||||
}
|
||||
sdiv_imm => SdivImm {
|
||||
immediates(iNN);
|
||||
parameters(iNN);
|
||||
result(iNN);
|
||||
}
|
||||
select => Select {
|
||||
parameters(bool_or_int, any_t, any_t);
|
||||
result(any_t);
|
||||
}
|
||||
sextend => Sextend {
|
||||
parameters(iNN);
|
||||
result(iMM);
|
||||
is_extend(true);
|
||||
}
|
||||
srem => Srem {
|
||||
parameters(iNN, iNN);
|
||||
result(iNN);
|
||||
}
|
||||
srem_imm => SremImm {
|
||||
immediates(iNN);
|
||||
parameters(iNN);
|
||||
result(iNN);
|
||||
}
|
||||
sshr => Sshr {
|
||||
parameters(iNN, iNN);
|
||||
result(iNN);
|
||||
}
|
||||
sshr_imm => SshrImm {
|
||||
immediates(iNN);
|
||||
parameters(iNN);
|
||||
result(iNN);
|
||||
}
|
||||
trapnz => Trapnz {
|
||||
parameters(bool_or_int);
|
||||
result(void);
|
||||
}
|
||||
trapz => Trapz {
|
||||
parameters(bool_or_int);
|
||||
result(void);
|
||||
}
|
||||
udiv => Udiv {
|
||||
parameters(iNN, iNN);
|
||||
result(iNN);
|
||||
}
|
||||
udiv_imm => UdivImm {
|
||||
immediates(iNN);
|
||||
parameters(iNN);
|
||||
result(iNN);
|
||||
}
|
||||
uextend => Uextend {
|
||||
parameters(iNN);
|
||||
result(iMM);
|
||||
is_extend(true);
|
||||
}
|
||||
urem => Urem {
|
||||
parameters(iNN, iNN);
|
||||
result(iNN);
|
||||
}
|
||||
urem_imm => UremImm {
|
||||
immediates(iNN);
|
||||
parameters(iNN);
|
||||
result(iNN);
|
||||
}
|
||||
ushr => Ushr {
|
||||
parameters(iNN, iNN);
|
||||
result(iNN);
|
||||
}
|
||||
ushr_imm => UshrImm {
|
||||
immediates(iNN);
|
||||
parameters(iNN);
|
||||
result(iNN);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
[package]
|
||||
name = "peepmatic-test"
|
||||
version = "0.2.0"
|
||||
authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
env_logger = "0.8.1"
|
||||
log = "0.4.8"
|
||||
peepmatic = { path = "../.." }
|
||||
peepmatic-runtime = { path = "../runtime" }
|
||||
peepmatic-test-operator = { path = "../test-operator" }
|
||||
peepmatic-traits = { path = "../traits" }
|
||||
@@ -1,517 +0,0 @@
|
||||
//! Testing utilities and a testing-only instruction set for `peepmatic`.
|
||||
|
||||
#![deny(missing_debug_implementations)]
|
||||
|
||||
use peepmatic_runtime::{
|
||||
cc::ConditionCode,
|
||||
instruction_set::InstructionSet,
|
||||
part::{Constant, Part},
|
||||
r#type::{BitWidth, Kind, Type},
|
||||
};
|
||||
use peepmatic_test_operator::TestOperator;
|
||||
use peepmatic_traits::TypingRules;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::BTreeMap;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub struct Instruction(pub usize);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InstructionData {
|
||||
pub operator: TestOperator,
|
||||
pub r#type: Type,
|
||||
pub immediates: Vec<Immediate>,
|
||||
pub arguments: Vec<Instruction>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Immediate {
|
||||
Constant(Constant),
|
||||
ConditionCode(ConditionCode),
|
||||
}
|
||||
|
||||
impl Immediate {
|
||||
fn unwrap_constant(&self) -> Constant {
|
||||
match *self {
|
||||
Immediate::Constant(c) => c,
|
||||
_ => panic!("not a constant"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Constant> for Immediate {
|
||||
fn from(c: Constant) -> Immediate {
|
||||
Immediate::Constant(c)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConditionCode> for Immediate {
|
||||
fn from(cc: ConditionCode) -> Immediate {
|
||||
Immediate::ConditionCode(cc)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Immediate> for Part<Instruction> {
|
||||
fn from(imm: Immediate) -> Part<Instruction> {
|
||||
match imm {
|
||||
Immediate::Constant(c) => Part::Constant(c),
|
||||
Immediate::ConditionCode(cc) => Part::ConditionCode(cc),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Part<Instruction>> for Immediate {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(part: Part<Instruction>) -> Result<Immediate, Self::Error> {
|
||||
match part {
|
||||
Part::Constant(c) => Ok(Immediate::Constant(c)),
|
||||
Part::ConditionCode(c) => Ok(Immediate::ConditionCode(c)),
|
||||
Part::Instruction(_) => Err("instruction parts cannot be converted into immediates"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Program {
|
||||
instr_counter: usize,
|
||||
instruction_data: BTreeMap<Instruction, InstructionData>,
|
||||
replacements: RefCell<BTreeMap<Instruction, Instruction>>,
|
||||
}
|
||||
|
||||
impl Program {
|
||||
/// Are `a` and `b` structurally equivalent, even if they use different
|
||||
/// `Instruction`s for various arguments?
|
||||
pub fn structurally_eq(&mut self, a: Instruction, b: Instruction) -> bool {
|
||||
macro_rules! ensure_eq {
|
||||
($a:expr, $b:expr) => {{
|
||||
let a = &$a;
|
||||
let b = &$b;
|
||||
if a != b {
|
||||
log::debug!(
|
||||
"{} != {} ({:?} != {:?})",
|
||||
stringify!($a),
|
||||
stringify!($b),
|
||||
a,
|
||||
b
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
let a = self.resolve(a);
|
||||
let b = self.resolve(b);
|
||||
if a == b {
|
||||
return true;
|
||||
}
|
||||
|
||||
let a = self.data(a);
|
||||
let b = self.data(b);
|
||||
log::debug!("structurally_eq({:?}, {:?})", a, b);
|
||||
|
||||
ensure_eq!(a.operator, b.operator);
|
||||
ensure_eq!(a.r#type, b.r#type);
|
||||
ensure_eq!(a.immediates, b.immediates);
|
||||
ensure_eq!(a.arguments.len(), b.arguments.len());
|
||||
a.arguments
|
||||
.clone()
|
||||
.into_iter()
|
||||
.zip(b.arguments.clone().into_iter())
|
||||
.all(|(a, b)| self.structurally_eq(a, b))
|
||||
}
|
||||
|
||||
pub fn instructions(&self) -> impl Iterator<Item = (Instruction, &InstructionData)> {
|
||||
self.instruction_data.iter().map(|(k, v)| (*k, v))
|
||||
}
|
||||
|
||||
pub fn replace_instruction(&mut self, old: Instruction, new: Instruction) {
|
||||
log::debug!("replacing {:?} with {:?}", old, new);
|
||||
|
||||
let old = self.resolve(old);
|
||||
let new = self.resolve(new);
|
||||
if old == new {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut replacements = self.replacements.borrow_mut();
|
||||
let existing_replacement = replacements.insert(old, new);
|
||||
assert!(existing_replacement.is_none());
|
||||
|
||||
let old_data = self.instruction_data.remove(&old);
|
||||
assert!(old_data.is_some());
|
||||
}
|
||||
|
||||
pub fn resolve(&self, inst: Instruction) -> Instruction {
|
||||
let mut replacements = self.replacements.borrow_mut();
|
||||
let mut replacements_followed = 0;
|
||||
let mut resolved = inst;
|
||||
while let Some(i) = replacements.get(&resolved).cloned() {
|
||||
log::trace!("resolving replaced instruction: {:?} -> {:?}", resolved, i);
|
||||
replacements_followed += 1;
|
||||
assert!(
|
||||
replacements_followed <= replacements.len(),
|
||||
"cyclic replacements"
|
||||
);
|
||||
|
||||
resolved = i;
|
||||
continue;
|
||||
}
|
||||
|
||||
if inst != resolved {
|
||||
let old_replacement = replacements.insert(inst, resolved);
|
||||
assert!(old_replacement.is_some());
|
||||
}
|
||||
|
||||
resolved
|
||||
}
|
||||
|
||||
pub fn data(&self, inst: Instruction) -> &InstructionData {
|
||||
let inst = self.resolve(inst);
|
||||
&self.instruction_data[&inst]
|
||||
}
|
||||
|
||||
pub fn new_instruction(
|
||||
&mut self,
|
||||
operator: TestOperator,
|
||||
r#type: Type,
|
||||
immediates: Vec<Immediate>,
|
||||
arguments: Vec<Instruction>,
|
||||
) -> Instruction {
|
||||
assert_eq!(
|
||||
operator.immediates_arity() as usize,
|
||||
immediates.len(),
|
||||
"wrong number of immediates for {:?}: expected {}, found {}",
|
||||
operator,
|
||||
operator.immediates_arity(),
|
||||
immediates.len(),
|
||||
);
|
||||
assert_eq!(
|
||||
operator.parameters_arity() as usize,
|
||||
arguments.len(),
|
||||
"wrong number of arguments for {:?}: expected {}, found {}",
|
||||
operator,
|
||||
operator.parameters_arity(),
|
||||
arguments.len(),
|
||||
);
|
||||
|
||||
assert!(!r#type.bit_width.is_polymorphic());
|
||||
assert!(immediates.iter().all(|imm| match imm {
|
||||
Immediate::Constant(Constant::Bool(_, w))
|
||||
| Immediate::Constant(Constant::Int(_, w)) => !w.is_polymorphic(),
|
||||
Immediate::ConditionCode(_) => true,
|
||||
}));
|
||||
|
||||
let inst = Instruction(self.instr_counter);
|
||||
self.instr_counter += 1;
|
||||
|
||||
let data = InstructionData {
|
||||
operator,
|
||||
r#type,
|
||||
immediates,
|
||||
arguments,
|
||||
};
|
||||
|
||||
log::trace!("new instruction: {:?} = {:?}", inst, data);
|
||||
self.instruction_data.insert(inst, data);
|
||||
inst
|
||||
}
|
||||
|
||||
pub fn r#const(&mut self, c: Constant, root_bit_width: BitWidth) -> Instruction {
|
||||
assert!(!root_bit_width.is_polymorphic());
|
||||
match c {
|
||||
Constant::Bool(_, bit_width) => self.new_instruction(
|
||||
TestOperator::Bconst,
|
||||
if bit_width.is_polymorphic() {
|
||||
Type {
|
||||
kind: Kind::Bool,
|
||||
bit_width: root_bit_width,
|
||||
}
|
||||
} else {
|
||||
Type {
|
||||
kind: Kind::Bool,
|
||||
bit_width,
|
||||
}
|
||||
},
|
||||
vec![c.into()],
|
||||
vec![],
|
||||
),
|
||||
Constant::Int(_, bit_width) => self.new_instruction(
|
||||
TestOperator::Iconst,
|
||||
if bit_width.is_polymorphic() {
|
||||
Type {
|
||||
kind: Kind::Int,
|
||||
bit_width: root_bit_width,
|
||||
}
|
||||
} else {
|
||||
Type {
|
||||
kind: Kind::Int,
|
||||
bit_width,
|
||||
}
|
||||
},
|
||||
vec![c.into()],
|
||||
vec![],
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn instruction_to_constant(&mut self, inst: Instruction) -> Option<Constant> {
|
||||
match self.data(inst) {
|
||||
InstructionData {
|
||||
operator: TestOperator::Iconst,
|
||||
immediates,
|
||||
..
|
||||
} => Some(immediates[0].unwrap_constant()),
|
||||
InstructionData {
|
||||
operator: TestOperator::Bconst,
|
||||
immediates,
|
||||
..
|
||||
} => Some(immediates[0].unwrap_constant()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn part_to_immediate(&mut self, part: Part<Instruction>) -> Result<Immediate, &'static str> {
|
||||
match part {
|
||||
Part::Instruction(i) => self
|
||||
.instruction_to_constant(i)
|
||||
.map(|c| c.into())
|
||||
.ok_or("non-constant instructions cannot be converted into immediates"),
|
||||
Part::Constant(c) => Ok(c.into()),
|
||||
Part::ConditionCode(cc) => Ok(Immediate::ConditionCode(cc)),
|
||||
}
|
||||
}
|
||||
|
||||
fn part_to_instruction(
|
||||
&mut self,
|
||||
root: Instruction,
|
||||
part: Part<Instruction>,
|
||||
) -> Result<Instruction, &'static str> {
|
||||
match part {
|
||||
Part::Instruction(inst) => {
|
||||
let inst = self.resolve(inst);
|
||||
Ok(inst)
|
||||
}
|
||||
Part::Constant(c) => {
|
||||
let root_width = self.data(root).r#type.bit_width;
|
||||
Ok(self.r#const(c, root_width))
|
||||
}
|
||||
Part::ConditionCode(_) => Err("condition codes cannot be converted into instructions"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TestIsa {
|
||||
pub native_word_size_in_bits: u8,
|
||||
}
|
||||
|
||||
// Unsafe because we must ensure that `instruction_result_bit_width` never
|
||||
// returns zero.
|
||||
unsafe impl<'a> InstructionSet<'a> for TestIsa {
|
||||
type Operator = TestOperator;
|
||||
|
||||
type Context = Program;
|
||||
|
||||
type Instruction = Instruction;
|
||||
|
||||
fn replace_instruction(
|
||||
&self,
|
||||
program: &mut Program,
|
||||
old: Instruction,
|
||||
new: Part<Instruction>,
|
||||
) -> Instruction {
|
||||
log::debug!("replace_instruction({:?}, {:?})", old, new);
|
||||
let new = program.part_to_instruction(old, new).unwrap();
|
||||
program.replace_instruction(old, new);
|
||||
new
|
||||
}
|
||||
|
||||
fn operator<E>(
|
||||
&self,
|
||||
program: &mut Program,
|
||||
instr: Self::Instruction,
|
||||
operands: &mut E,
|
||||
) -> Option<Self::Operator>
|
||||
where
|
||||
E: Extend<Part<Instruction>>,
|
||||
{
|
||||
log::debug!("operator({:?})", instr);
|
||||
let data = program.data(instr);
|
||||
operands.extend(
|
||||
data.immediates
|
||||
.iter()
|
||||
.map(|imm| Part::from(*imm))
|
||||
.chain(data.arguments.iter().map(|inst| Part::Instruction(*inst))),
|
||||
);
|
||||
Some(data.operator)
|
||||
}
|
||||
|
||||
fn make_inst_1(
|
||||
&self,
|
||||
program: &mut Program,
|
||||
root: Instruction,
|
||||
operator: TestOperator,
|
||||
r#type: Type,
|
||||
a: Part<Instruction>,
|
||||
) -> Instruction {
|
||||
log::debug!(
|
||||
"make_inst_1(\n\toperator = {:?},\n\ttype = {},\n\ta = {:?},\n)",
|
||||
operator,
|
||||
r#type,
|
||||
a,
|
||||
);
|
||||
|
||||
let (imms, args) = match operator.immediates_arity() {
|
||||
0 => {
|
||||
assert_eq!(operator.parameters_arity(), 1);
|
||||
(vec![], vec![program.part_to_instruction(root, a).unwrap()])
|
||||
}
|
||||
1 => {
|
||||
assert_eq!(operator.parameters_arity(), 0);
|
||||
(vec![program.part_to_immediate(a).unwrap()], vec![])
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
program.new_instruction(operator, r#type, imms, args)
|
||||
}
|
||||
|
||||
fn make_inst_2(
|
||||
&self,
|
||||
program: &mut Program,
|
||||
root: Instruction,
|
||||
operator: TestOperator,
|
||||
r#type: Type,
|
||||
a: Part<Instruction>,
|
||||
b: Part<Instruction>,
|
||||
) -> Instruction {
|
||||
log::debug!(
|
||||
"make_inst_2(\n\toperator = {:?},\n\ttype = {},\n\ta = {:?},\n\tb = {:?},\n)",
|
||||
operator,
|
||||
r#type,
|
||||
a,
|
||||
b,
|
||||
);
|
||||
|
||||
let (imms, args) = match operator.immediates_arity() {
|
||||
0 => {
|
||||
assert_eq!(operator.parameters_arity(), 2);
|
||||
(
|
||||
vec![],
|
||||
vec![
|
||||
program.part_to_instruction(root, a).unwrap(),
|
||||
program.part_to_instruction(root, b).unwrap(),
|
||||
],
|
||||
)
|
||||
}
|
||||
1 => {
|
||||
assert_eq!(operator.parameters_arity(), 1);
|
||||
(
|
||||
vec![program.part_to_immediate(a).unwrap()],
|
||||
vec![program.part_to_instruction(root, b).unwrap()],
|
||||
)
|
||||
}
|
||||
2 => {
|
||||
assert_eq!(operator.parameters_arity(), 0);
|
||||
(
|
||||
vec![
|
||||
program.part_to_immediate(a).unwrap(),
|
||||
program.part_to_immediate(b).unwrap(),
|
||||
],
|
||||
vec![],
|
||||
)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
program.new_instruction(operator, r#type, imms, args)
|
||||
}
|
||||
|
||||
fn make_inst_3(
|
||||
&self,
|
||||
program: &mut Program,
|
||||
root: Instruction,
|
||||
operator: TestOperator,
|
||||
r#type: Type,
|
||||
a: Part<Instruction>,
|
||||
b: Part<Instruction>,
|
||||
c: Part<Instruction>,
|
||||
) -> Instruction {
|
||||
log::debug!(
|
||||
"make_inst_3(\n\toperator = {:?},\n\ttype = {},\n\ta = {:?},\n\tb = {:?},\n\tc = {:?},\n)",
|
||||
operator,
|
||||
r#type,
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
);
|
||||
let (imms, args) = match operator.immediates_arity() {
|
||||
0 => {
|
||||
assert_eq!(operator.parameters_arity(), 3);
|
||||
(
|
||||
vec![],
|
||||
vec![
|
||||
program.part_to_instruction(root, a).unwrap(),
|
||||
program.part_to_instruction(root, b).unwrap(),
|
||||
program.part_to_instruction(root, c).unwrap(),
|
||||
],
|
||||
)
|
||||
}
|
||||
1 => {
|
||||
assert_eq!(operator.parameters_arity(), 2);
|
||||
(
|
||||
vec![program.part_to_immediate(a).unwrap()],
|
||||
vec![
|
||||
program.part_to_instruction(root, b).unwrap(),
|
||||
program.part_to_instruction(root, c).unwrap(),
|
||||
],
|
||||
)
|
||||
}
|
||||
2 => {
|
||||
assert_eq!(operator.parameters_arity(), 1);
|
||||
(
|
||||
vec![
|
||||
program.part_to_immediate(a).unwrap(),
|
||||
program.part_to_immediate(b).unwrap(),
|
||||
],
|
||||
vec![program.part_to_instruction(root, c).unwrap()],
|
||||
)
|
||||
}
|
||||
3 => {
|
||||
assert_eq!(operator.parameters_arity(), 0);
|
||||
(
|
||||
vec![
|
||||
program.part_to_immediate(a).unwrap(),
|
||||
program.part_to_immediate(b).unwrap(),
|
||||
program.part_to_immediate(c).unwrap(),
|
||||
],
|
||||
vec![],
|
||||
)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
program.new_instruction(operator, r#type, imms, args)
|
||||
}
|
||||
|
||||
fn instruction_to_constant(
|
||||
&self,
|
||||
program: &mut Program,
|
||||
inst: Instruction,
|
||||
) -> Option<Constant> {
|
||||
log::debug!("instruction_to_constant({:?})", inst);
|
||||
program.instruction_to_constant(inst)
|
||||
}
|
||||
|
||||
fn instruction_result_bit_width(&self, program: &mut Program, inst: Instruction) -> u8 {
|
||||
log::debug!("instruction_result_bit_width({:?})", inst);
|
||||
let ty = program.data(inst).r#type;
|
||||
let width = ty.bit_width.fixed_width().unwrap();
|
||||
assert!(width != 0);
|
||||
width
|
||||
}
|
||||
|
||||
fn native_word_size_in_bits(&self, _program: &mut Program) -> u8 {
|
||||
log::debug!("native_word_size_in_bits");
|
||||
self.native_word_size_in_bits
|
||||
}
|
||||
}
|
||||
@@ -1,393 +0,0 @@
|
||||
use peepmatic_runtime::{
|
||||
cc::ConditionCode,
|
||||
part::Constant,
|
||||
r#type::{BitWidth, Type},
|
||||
};
|
||||
use peepmatic_test::*;
|
||||
use peepmatic_test_operator::TestOperator;
|
||||
|
||||
const TEST_ISA: TestIsa = TestIsa {
|
||||
native_word_size_in_bits: 32,
|
||||
};
|
||||
|
||||
macro_rules! optimizer {
|
||||
($opts:ident, $source:expr) => {{
|
||||
let _ = env_logger::try_init();
|
||||
$opts = peepmatic::compile_str($source, std::path::Path::new("peepmatic-test")).unwrap();
|
||||
$opts.optimizer(TEST_ISA)
|
||||
}};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opcode() {
|
||||
let opts;
|
||||
let mut optimizer = optimizer!(opts, "(=> (iadd $x 0) $x)");
|
||||
|
||||
let mut program = Program::default();
|
||||
let five = program.r#const(Constant::Int(5, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo);
|
||||
let zero = program.r#const(Constant::Int(0, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo);
|
||||
let add = program.new_instruction(TestOperator::Iadd, Type::i32(), vec![], vec![five, zero]);
|
||||
|
||||
let new = optimizer.apply_one(&mut program, add);
|
||||
let new = new.expect("optimization should have applied");
|
||||
assert!(program.structurally_eq(new, five));
|
||||
|
||||
let add = program.new_instruction(TestOperator::Iadd, Type::i32(), vec![], vec![five, five]);
|
||||
let replacement = optimizer.apply_one(&mut program, add);
|
||||
assert!(replacement.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constant() {
|
||||
let opts;
|
||||
let mut optimizer = optimizer!(opts, "(=> (iadd $C $x) (iadd_imm $C $x))");
|
||||
|
||||
let mut program = Program::default();
|
||||
let five = program.r#const(Constant::Int(5, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo);
|
||||
let zero = program.r#const(Constant::Int(0, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo);
|
||||
let add = program.new_instruction(TestOperator::Iadd, Type::i32(), vec![], vec![five, zero]);
|
||||
|
||||
let expected = program.new_instruction(
|
||||
TestOperator::IaddImm,
|
||||
Type::i32(),
|
||||
vec![Constant::Int(5, BitWidth::ThirtyTwo).into()],
|
||||
vec![zero],
|
||||
);
|
||||
|
||||
let new = optimizer.apply_one(&mut program, add);
|
||||
let new = new.expect("optimization should have applied");
|
||||
assert!(program.structurally_eq(new, expected));
|
||||
|
||||
let mul = program.new_instruction(TestOperator::Imul, Type::i32(), vec![], vec![five, zero]);
|
||||
let add = program.new_instruction(TestOperator::Imul, Type::i32(), vec![], vec![mul, five]);
|
||||
let replacement = optimizer.apply_one(&mut program, add);
|
||||
assert!(replacement.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boolean() {
|
||||
let opts;
|
||||
let mut optimizer = optimizer!(opts, "(=> (bint true) 1)");
|
||||
|
||||
let mut program = Program::default();
|
||||
let t = program.r#const(Constant::Bool(true, BitWidth::One), BitWidth::One);
|
||||
let bint = program.new_instruction(TestOperator::Bint, Type::i1(), vec![], vec![t]);
|
||||
let one = program.r#const(Constant::Int(1, BitWidth::One), BitWidth::ThirtyTwo);
|
||||
|
||||
let new = optimizer.apply_one(&mut program, bint);
|
||||
let new = new.expect("optimization should have applied");
|
||||
assert!(program.structurally_eq(new, one));
|
||||
|
||||
let f = program.r#const(Constant::Bool(false, BitWidth::One), BitWidth::One);
|
||||
let bint = program.new_instruction(TestOperator::Bint, Type::i1(), vec![], vec![f]);
|
||||
let replacement = optimizer.apply_one(&mut program, bint);
|
||||
assert!(replacement.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn condition_codes() {
|
||||
let opts;
|
||||
let mut optimizer = optimizer!(opts, "(=> (icmp eq $x $x) true)");
|
||||
|
||||
let mut program = Program::default();
|
||||
let five = program.r#const(Constant::Int(5, BitWidth::ThirtyTwo), BitWidth::One);
|
||||
let icmp_eq = program.new_instruction(
|
||||
TestOperator::Icmp,
|
||||
Type::b1(),
|
||||
vec![ConditionCode::Eq.into()],
|
||||
vec![five, five],
|
||||
);
|
||||
let t = program.r#const(Constant::Bool(true, BitWidth::One), BitWidth::One);
|
||||
|
||||
let new = optimizer.apply_one(&mut program, icmp_eq);
|
||||
let new = new.expect("optimization should have applied");
|
||||
assert!(program.structurally_eq(new, t));
|
||||
|
||||
let icmp_ne = program.new_instruction(
|
||||
TestOperator::Icmp,
|
||||
Type::b1(),
|
||||
vec![ConditionCode::Ne.into()],
|
||||
vec![five, five],
|
||||
);
|
||||
let replacement = optimizer.apply_one(&mut program, icmp_ne);
|
||||
assert!(replacement.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_power_of_two() {
|
||||
let opts;
|
||||
let mut optimizer = optimizer!(
|
||||
opts,
|
||||
"
|
||||
(=> (when (imul $x $C)
|
||||
(is-power-of-two $C))
|
||||
(ishl $x $(log2 $C)))
|
||||
"
|
||||
);
|
||||
|
||||
let mut program = Program::default();
|
||||
let five = program.r#const(Constant::Int(5, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo);
|
||||
let two = program.r#const(Constant::Int(2, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo);
|
||||
let imul = program.new_instruction(TestOperator::Imul, Type::i32(), vec![], vec![five, two]);
|
||||
|
||||
let one = program.r#const(Constant::Int(1, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo);
|
||||
let ishl = program.new_instruction(TestOperator::Ishl, Type::i32(), vec![], vec![five, one]);
|
||||
|
||||
let new = optimizer.apply_one(&mut program, imul);
|
||||
let new = new.expect("optimization should have applied");
|
||||
assert!(program.structurally_eq(new, ishl));
|
||||
|
||||
let three = program.r#const(Constant::Int(3, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo);
|
||||
let imul = program.new_instruction(TestOperator::Imul, Type::i32(), vec![], vec![five, three]);
|
||||
|
||||
let replacement = optimizer.apply_one(&mut program, imul);
|
||||
assert!(replacement.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bit_width() {
|
||||
let opts;
|
||||
let mut optimizer = optimizer!(
|
||||
opts,
|
||||
"
|
||||
(=> (when (imul $C $x)
|
||||
(bit-width $C 32))
|
||||
(imul_imm $C $x))
|
||||
"
|
||||
);
|
||||
|
||||
let mut program = Program::default();
|
||||
let five = program.r#const(Constant::Int(5, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo);
|
||||
let two = program.r#const(Constant::Int(2, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo);
|
||||
let imul = program.new_instruction(TestOperator::Imul, Type::i32(), vec![], vec![five, two]);
|
||||
|
||||
let imul_imm = program.new_instruction(
|
||||
TestOperator::ImulImm,
|
||||
Type::i32(),
|
||||
vec![Constant::Int(5, BitWidth::ThirtyTwo).into()],
|
||||
vec![two],
|
||||
);
|
||||
|
||||
let new = optimizer.apply_one(&mut program, imul);
|
||||
let new = new.expect("optimization should have applied");
|
||||
assert!(program.structurally_eq(new, imul_imm));
|
||||
|
||||
let five = program.r#const(Constant::Int(5, BitWidth::SixtyFour), BitWidth::SixtyFour);
|
||||
let two = program.r#const(Constant::Int(2, BitWidth::SixtyFour), BitWidth::SixtyFour);
|
||||
let imul = program.new_instruction(TestOperator::Imul, Type::i32(), vec![], vec![five, two]);
|
||||
|
||||
let replacement = optimizer.apply_one(&mut program, imul);
|
||||
assert!(replacement.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fits_in_native_word() {
|
||||
let opts;
|
||||
let mut optimizer = optimizer!(
|
||||
opts,
|
||||
"
|
||||
(=> (when (imul $C $x)
|
||||
(fits-in-native-word $C))
|
||||
(imul_imm $C $x))
|
||||
"
|
||||
);
|
||||
|
||||
let mut program = Program::default();
|
||||
let five = program.r#const(Constant::Int(5, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo);
|
||||
let two = program.r#const(Constant::Int(2, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo);
|
||||
let imul = program.new_instruction(TestOperator::Imul, Type::i32(), vec![], vec![five, two]);
|
||||
|
||||
let imul_imm = program.new_instruction(
|
||||
TestOperator::ImulImm,
|
||||
Type::i32(),
|
||||
vec![Constant::Int(5, BitWidth::ThirtyTwo).into()],
|
||||
vec![two],
|
||||
);
|
||||
|
||||
let new = optimizer.apply_one(&mut program, imul);
|
||||
let new = new.expect("optimization should have applied");
|
||||
assert!(program.structurally_eq(new, imul_imm));
|
||||
|
||||
let five = program.r#const(Constant::Int(5, BitWidth::SixtyFour), BitWidth::SixtyFour);
|
||||
let two = program.r#const(Constant::Int(2, BitWidth::SixtyFour), BitWidth::SixtyFour);
|
||||
let imul = program.new_instruction(TestOperator::Imul, Type::i64(), vec![], vec![five, two]);
|
||||
|
||||
let replacement = optimizer.apply_one(&mut program, imul);
|
||||
assert!(replacement.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unquote_neg() {
|
||||
let opts;
|
||||
let mut optimizer = optimizer!(
|
||||
opts,
|
||||
"
|
||||
(=> (isub $x $C)
|
||||
(iadd_imm $(neg $C) $x))
|
||||
"
|
||||
);
|
||||
|
||||
let mut program = Program::default();
|
||||
let five = program.r#const(Constant::Int(5, BitWidth::SixtyFour), BitWidth::SixtyFour);
|
||||
let two = program.r#const(Constant::Int(2, BitWidth::SixtyFour), BitWidth::SixtyFour);
|
||||
let isub = program.new_instruction(TestOperator::Isub, Type::i64(), vec![], vec![five, two]);
|
||||
|
||||
let iadd_imm = program.new_instruction(
|
||||
TestOperator::IaddImm,
|
||||
Type::i64(),
|
||||
vec![Constant::Int(-2 as _, BitWidth::SixtyFour).into()],
|
||||
vec![five],
|
||||
);
|
||||
|
||||
let new = optimizer.apply_one(&mut program, isub);
|
||||
let new = new.expect("optimization should have applied");
|
||||
assert!(program.structurally_eq(new, iadd_imm));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subsumption() {
|
||||
let opts;
|
||||
let mut optimizer = optimizer!(
|
||||
opts,
|
||||
"
|
||||
;; NB: the following optimizations are ordered from least to most general, so
|
||||
;; the first applicable optimization should be the one that is applied.
|
||||
|
||||
(=> (iadd (iadd (iadd $w $x) $y) $z)
|
||||
(iadd (iadd $w $x) (iadd $y $z)))
|
||||
|
||||
(=> (iadd $C1 $C2)
|
||||
$(iadd $C1 $C2))
|
||||
|
||||
(=> (iadd $C $x)
|
||||
(iadd_imm $C $x))
|
||||
|
||||
(=> (iadd $x $x)
|
||||
(ishl_imm 1 $x))
|
||||
"
|
||||
);
|
||||
|
||||
let mut program = Program::default();
|
||||
|
||||
let w = program.r#const(Constant::Int(11, BitWidth::SixtyFour), BitWidth::SixtyFour);
|
||||
let x = program.r#const(Constant::Int(22, BitWidth::SixtyFour), BitWidth::SixtyFour);
|
||||
let y = program.r#const(Constant::Int(33, BitWidth::SixtyFour), BitWidth::SixtyFour);
|
||||
let z = program.r#const(Constant::Int(44, BitWidth::SixtyFour), BitWidth::SixtyFour);
|
||||
|
||||
log::debug!("(iadd (iadd (iadd w x) y) z) => (iadd (iadd w x) (iadd y z))");
|
||||
|
||||
let iadd = program.new_instruction(TestOperator::Iadd, Type::i64(), vec![], vec![w, x]);
|
||||
let iadd = program.new_instruction(TestOperator::Iadd, Type::i64(), vec![], vec![iadd, y]);
|
||||
let iadd = program.new_instruction(TestOperator::Iadd, Type::i64(), vec![], vec![iadd, z]);
|
||||
let expected_lhs = program.new_instruction(TestOperator::Iadd, Type::i64(), vec![], vec![w, x]);
|
||||
let expected_rhs = program.new_instruction(TestOperator::Iadd, Type::i64(), vec![], vec![y, z]);
|
||||
let expected = program.new_instruction(
|
||||
TestOperator::Iadd,
|
||||
Type::i64(),
|
||||
vec![],
|
||||
vec![expected_lhs, expected_rhs],
|
||||
);
|
||||
|
||||
let new = optimizer.apply_one(&mut program, iadd);
|
||||
let new = new.expect("optimization should have applied");
|
||||
assert!(program.structurally_eq(new, expected));
|
||||
|
||||
log::debug!("(iadd w x) => y");
|
||||
|
||||
let iadd = program.new_instruction(TestOperator::Iadd, Type::i64(), vec![], vec![w, x]);
|
||||
let new = optimizer.apply_one(&mut program, iadd);
|
||||
let new = new.expect("optimization should have applied");
|
||||
assert!(program.structurally_eq(new, y));
|
||||
|
||||
log::debug!("(iadd x (iadd y z)) => (iadd_imm x (iadd y z))");
|
||||
|
||||
let iadd_y_z = program.new_instruction(TestOperator::Iadd, Type::i64(), vec![], vec![y, z]);
|
||||
let iadd = program.new_instruction(TestOperator::Iadd, Type::i64(), vec![], vec![x, iadd_y_z]);
|
||||
let iadd_imm = program.new_instruction(
|
||||
TestOperator::IaddImm,
|
||||
Type::i64(),
|
||||
vec![Constant::Int(22, BitWidth::SixtyFour).into()],
|
||||
vec![iadd_y_z],
|
||||
);
|
||||
let new = optimizer.apply_one(&mut program, iadd);
|
||||
let new = new.expect("optimization should have applied");
|
||||
assert!(program.structurally_eq(new, iadd_imm));
|
||||
|
||||
log::debug!("(iadd (imul_imm x 1) (imul_imm x 1)) => (ishl_imm 1 (imul_imm x 1))");
|
||||
|
||||
let imul_imm = program.new_instruction(
|
||||
TestOperator::ImulImm,
|
||||
Type::i64(),
|
||||
vec![Constant::Int(1, BitWidth::SixtyFour).into()],
|
||||
vec![x],
|
||||
);
|
||||
let iadd = program.new_instruction(
|
||||
TestOperator::Iadd,
|
||||
Type::i64(),
|
||||
vec![],
|
||||
vec![imul_imm, imul_imm],
|
||||
);
|
||||
let ishl_imm = program.new_instruction(
|
||||
TestOperator::IshlImm,
|
||||
Type::i64(),
|
||||
vec![Constant::Int(1, BitWidth::SixtyFour).into()],
|
||||
vec![imul_imm],
|
||||
);
|
||||
let new = optimizer.apply_one(&mut program, iadd);
|
||||
let new = new.expect("optimization should have applied");
|
||||
assert!(program.structurally_eq(new, ishl_imm));
|
||||
|
||||
log::debug!("(iadd (imul w x) (imul y z)) does not match any optimization.");
|
||||
|
||||
let imul_w_x = program.new_instruction(TestOperator::Imul, Type::i64(), vec![], vec![w, x]);
|
||||
let imul_y_z = program.new_instruction(TestOperator::Imul, Type::i64(), vec![], vec![y, z]);
|
||||
let iadd = program.new_instruction(
|
||||
TestOperator::Iadd,
|
||||
Type::i64(),
|
||||
vec![],
|
||||
vec![imul_w_x, imul_y_z],
|
||||
);
|
||||
|
||||
let replacement = optimizer.apply_one(&mut program, iadd);
|
||||
assert!(replacement.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn polymorphic_bit_widths() {
|
||||
let opts;
|
||||
let mut optimizer = optimizer!(opts, "(=> (iadd $C $x) (iadd_imm $C $x))");
|
||||
|
||||
let mut program = Program::default();
|
||||
|
||||
// Applies to 32 bit adds.
|
||||
|
||||
let x = program.r#const(Constant::Int(42, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo);
|
||||
let y = program.r#const(Constant::Int(420, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo);
|
||||
let iadd = program.new_instruction(TestOperator::Iadd, Type::i32(), vec![], vec![x, y]);
|
||||
let iadd_imm = program.new_instruction(
|
||||
TestOperator::IaddImm,
|
||||
Type::i32(),
|
||||
vec![Constant::Int(42, BitWidth::ThirtyTwo).into()],
|
||||
vec![y],
|
||||
);
|
||||
|
||||
let new = optimizer.apply_one(&mut program, iadd);
|
||||
let new = new.expect("optimization should have applied");
|
||||
assert!(program.structurally_eq(new, iadd_imm));
|
||||
|
||||
// Applies to 16 bit adds.
|
||||
|
||||
let x = program.r#const(Constant::Int(42, BitWidth::Sixteen), BitWidth::Sixteen);
|
||||
let y = program.r#const(Constant::Int(420, BitWidth::Sixteen), BitWidth::Sixteen);
|
||||
let iadd = program.new_instruction(TestOperator::Iadd, Type::i16(), vec![], vec![x, y]);
|
||||
let iadd_imm = program.new_instruction(
|
||||
TestOperator::IaddImm,
|
||||
Type::i16(),
|
||||
vec![Constant::Int(42, BitWidth::Sixteen).into()],
|
||||
vec![y],
|
||||
);
|
||||
|
||||
let new = optimizer.apply_one(&mut program, iadd);
|
||||
let new = new.expect("optimization should have applied");
|
||||
assert!(program.structurally_eq(new, iadd_imm));
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
[package]
|
||||
name = "peepmatic-traits"
|
||||
version = "0.78.0"
|
||||
authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
|
||||
edition = "2018"
|
||||
license = "Apache-2.0 WITH LLVM-exception"
|
||||
description = 'Common traits for peepmatic'
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
@@ -1,26 +0,0 @@
|
||||
//! Shared traits, types, and macros for Peepmatic.
|
||||
//!
|
||||
//! This crate is used both at build time when constructing peephole optimizers
|
||||
//! (i.e. in the `peepmatic` crate), and at run time when using pre-built
|
||||
//! peephole optimizers (i.e. in the `peepmatic-runtime` crate and in
|
||||
//! Cranelift's Peepmatic integration at `cranelift/codegen/src/peepmatic.rs`).
|
||||
//!
|
||||
//! This crate is similar to a header file: it should generally only contain
|
||||
//! trait/type/macro definitions, not any code.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![deny(missing_debug_implementations)]
|
||||
|
||||
#[macro_use]
|
||||
mod operator;
|
||||
pub use operator::*;
|
||||
|
||||
mod typing;
|
||||
pub use typing::*;
|
||||
|
||||
/// Raise a panic about an unsupported operation.
|
||||
#[cold]
|
||||
#[inline(never)]
|
||||
pub fn unsupported(msg: &str) -> ! {
|
||||
panic!("unsupported: {}", msg)
|
||||
}
|
||||
@@ -1,317 +0,0 @@
|
||||
/// Define a `wast::parser::Parse` implementation for an operator type.
|
||||
#[macro_export]
|
||||
macro_rules! define_parse_impl_for_operator {
|
||||
(
|
||||
$operator:ident {
|
||||
$(
|
||||
$keyword:ident => $variant:ident;
|
||||
)*
|
||||
}
|
||||
) => {
|
||||
impl<'a> wast::parser::Parse<'a> for $operator {
|
||||
fn parse(p: wast::parser::Parser<'a>) -> wast::parser::Result<$operator> {
|
||||
/// Token definitions for our `Opcode` keywords.
|
||||
mod tok {
|
||||
$(
|
||||
wast::custom_keyword!($keyword);
|
||||
)*
|
||||
}
|
||||
|
||||
// Peek at the next token, and if it is the variant's
|
||||
// keyword, then consume it with `parse`, and finally return
|
||||
// the `Opcode` variant.
|
||||
$(
|
||||
if p.peek::<tok::$keyword>() {
|
||||
p.parse::<tok::$keyword>()?;
|
||||
return Ok(Self::$variant);
|
||||
}
|
||||
)*
|
||||
|
||||
// If none of the keywords matched, then we get a parse error.
|
||||
Err(p.error(concat!("expected `", stringify!($operator), "`")))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Define a `peepmatic_traits::TypingRules` implementation for the given
|
||||
/// operator type.
|
||||
#[macro_export]
|
||||
macro_rules! define_typing_rules_impl_for_operator {
|
||||
(
|
||||
$operator:ident {
|
||||
$(
|
||||
$variant:ident {
|
||||
$( immediates( $($immediate:ident),* ); )?
|
||||
$( parameters( $($parameter:ident),* ); )?
|
||||
result( $result:ident );
|
||||
$( is_reduce($is_reduce:expr); )?
|
||||
$( is_extend($is_extend:expr); )?
|
||||
}
|
||||
)*
|
||||
}
|
||||
) => {
|
||||
impl $crate::TypingRules for $operator {
|
||||
fn result_type<'a, C>(
|
||||
&self,
|
||||
span: C::Span,
|
||||
typing_context: &mut C,
|
||||
) -> C::TypeVariable
|
||||
where
|
||||
C: $crate::TypingContext<'a> {
|
||||
match self {
|
||||
$(
|
||||
Self::$variant => typing_context.$result(span),
|
||||
)*
|
||||
|
||||
#[allow(dead_code)]
|
||||
_ => $crate::unsupported("no typing rules defined for variant"),
|
||||
}
|
||||
}
|
||||
|
||||
fn immediates_arity(&self) -> u8 {
|
||||
match self {
|
||||
$(
|
||||
Self::$variant => $crate::define_typing_rules_impl_for_operator!(
|
||||
@arity;
|
||||
$( $( $immediate, )* )?
|
||||
),
|
||||
)*
|
||||
|
||||
#[allow(dead_code)]
|
||||
_ => $crate::unsupported("no typing rules defined for variant"),
|
||||
}
|
||||
}
|
||||
|
||||
fn immediate_types<'a, C>(
|
||||
&self,
|
||||
span: C::Span,
|
||||
typing_context: &mut C,
|
||||
types: &mut impl Extend<C::TypeVariable>,
|
||||
)
|
||||
where
|
||||
C: $crate::TypingContext<'a>
|
||||
{
|
||||
match self {
|
||||
$(
|
||||
Self::$variant => types.extend(
|
||||
None.into_iter()
|
||||
$(
|
||||
$(
|
||||
.chain(Some(typing_context.$immediate(span)))
|
||||
)*
|
||||
)?
|
||||
),
|
||||
)*
|
||||
|
||||
#[allow(dead_code)]
|
||||
_ => $crate::unsupported("no typing rules defined for variant"),
|
||||
}
|
||||
}
|
||||
|
||||
fn parameters_arity(&self) -> u8 {
|
||||
match self {
|
||||
$(
|
||||
Self::$variant => $crate::define_typing_rules_impl_for_operator!(
|
||||
@arity;
|
||||
$( $( $parameter, )* )?
|
||||
),
|
||||
)*
|
||||
|
||||
#[allow(dead_code)]
|
||||
_ => $crate::unsupported("no typing rules defined for variant"),
|
||||
}
|
||||
}
|
||||
|
||||
fn parameter_types<'a, C>(
|
||||
&self,
|
||||
span: C::Span,
|
||||
typing_context: &mut C,
|
||||
types: &mut impl Extend<C::TypeVariable>,
|
||||
)
|
||||
where
|
||||
C: $crate::TypingContext<'a>
|
||||
{
|
||||
match self {
|
||||
$(
|
||||
Self::$variant => types.extend(
|
||||
None.into_iter()
|
||||
$(
|
||||
$(
|
||||
.chain(Some(typing_context.$parameter(span)))
|
||||
)*
|
||||
)?
|
||||
),
|
||||
)*
|
||||
|
||||
#[allow(dead_code)]
|
||||
_ => $crate::unsupported("no typing rules defined for variant"),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_reduce(&self) -> bool {
|
||||
match self {
|
||||
$(
|
||||
Self::$variant if false $( || $is_reduce )? => false $( || $is_reduce )?,
|
||||
)*
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_extend(&self) -> bool {
|
||||
match self {
|
||||
$(
|
||||
Self::$variant if false $( || $is_extend )? => false $( || $is_extend )?,
|
||||
)*
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Base case: zero arity.
|
||||
(
|
||||
@arity;
|
||||
) => {
|
||||
0
|
||||
};
|
||||
|
||||
// Recursive case: count one for the head and add that to the arity of the
|
||||
// rest.
|
||||
(
|
||||
@arity;
|
||||
$head:ident,
|
||||
$( $rest:ident, )*
|
||||
) => {
|
||||
1 + $crate::define_typing_rules_impl_for_operator!(
|
||||
@arity;
|
||||
$( $rest, )*
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Define both a `wast::parser::Parse` implementation and a
|
||||
/// `peepmatic_traits::TypingRules` implementation for the given operator type.
|
||||
#[macro_export]
|
||||
macro_rules! define_parse_and_typing_rules_for_operator {
|
||||
(
|
||||
$operator:ident {
|
||||
$(
|
||||
$keyword:ident => $variant:ident {
|
||||
$( immediates( $($immediate:ident),* ); )?
|
||||
$( parameters( $($parameter:ident),* ); )?
|
||||
result( $result:ident );
|
||||
$( is_reduce($is_reduce:expr); )?
|
||||
$( is_extend($is_extend:expr); )?
|
||||
}
|
||||
)*
|
||||
}
|
||||
$( parse_cfg($parse_cfg:meta); )?
|
||||
) => {
|
||||
$( #[cfg($parse_cfg)] )?
|
||||
$crate::define_parse_impl_for_operator! {
|
||||
$operator {
|
||||
$(
|
||||
$keyword => $variant;
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
$crate::define_typing_rules_impl_for_operator! {
|
||||
$operator {
|
||||
$(
|
||||
$variant {
|
||||
$( immediates( $($immediate),* ); )?
|
||||
$( parameters( $($parameter),* ); )?
|
||||
result( $result );
|
||||
$( is_reduce($is_reduce); )?
|
||||
$( is_extend($is_extend); )?
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Define an operator type, as well as its parsing and typing rules.
|
||||
#[macro_export]
|
||||
macro_rules! define_operator {
|
||||
(
|
||||
$( #[$attr:meta] )*
|
||||
$operator:ident {
|
||||
$(
|
||||
$keywrord:ident => $variant:ident {
|
||||
$( immediates( $($immediate:ident),* ); )?
|
||||
$( parameters( $($parameter:ident),* ); )?
|
||||
result( $result:ident );
|
||||
$( is_reduce($is_reduce:expr); )?
|
||||
$( is_extend($is_extend:expr); )?
|
||||
}
|
||||
)*
|
||||
}
|
||||
$( parse_cfg($parse_cfg:meta); )?
|
||||
) => {
|
||||
$( #[$attr] )*
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
||||
#[repr(u32)]
|
||||
pub enum $operator {
|
||||
$(
|
||||
$variant,
|
||||
)*
|
||||
}
|
||||
|
||||
impl From<$operator> for u32 {
|
||||
#[inline]
|
||||
fn from(x: $operator) -> u32 {
|
||||
x as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$operator> for core::num::NonZeroU32 {
|
||||
#[inline]
|
||||
fn from(x: $operator) -> core::num::NonZeroU32 {
|
||||
let x: u32 = x.into();
|
||||
core::num::NonZeroU32::new(x.checked_add(1).unwrap()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl core::convert::TryFrom<u32> for $operator {
|
||||
type Error = ();
|
||||
|
||||
#[inline]
|
||||
fn try_from(x: u32) -> Result<Self, ()> {
|
||||
match x {
|
||||
$(
|
||||
x if x == Self::$variant.into() => Ok(Self::$variant),
|
||||
)*
|
||||
_ => Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::convert::TryFrom<core::num::NonZeroU32> for $operator {
|
||||
type Error = ();
|
||||
|
||||
#[inline]
|
||||
fn try_from(x: core::num::NonZeroU32) -> Result<Self, ()> {
|
||||
let x = x.get().checked_sub(1).ok_or(())?;
|
||||
Self::try_from(x)
|
||||
}
|
||||
}
|
||||
|
||||
$crate::define_parse_and_typing_rules_for_operator! {
|
||||
$operator {
|
||||
$(
|
||||
$keywrord => $variant {
|
||||
$( immediates( $($immediate),* ); )?
|
||||
$( parameters( $($parameter),* ); )?
|
||||
result( $result );
|
||||
$( is_reduce($is_reduce); )?
|
||||
$( is_extend($is_extend); )?
|
||||
}
|
||||
)*
|
||||
}
|
||||
$( parse_cfg($parse_cfg); )?
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
/// A trait to represent a typing context.
|
||||
///
|
||||
/// This is used by the macro-generated operator methods that create the type
|
||||
/// variables for their immediates, parameters, and results. This trait is
|
||||
/// implemented by the concrete typing context in `peepmatic/src/verify.rs`.
|
||||
pub trait TypingContext<'a> {
|
||||
/// A source span.
|
||||
type Span: Copy;
|
||||
|
||||
/// A type variable.
|
||||
type TypeVariable;
|
||||
|
||||
/// Create a condition code type.
|
||||
fn cc(&mut self, span: Self::Span) -> Self::TypeVariable;
|
||||
|
||||
/// Create a boolean type with a polymorphic bit width.
|
||||
///
|
||||
/// Each use of `bNN` by the same operator refers to the same type variable.
|
||||
#[allow(non_snake_case)]
|
||||
fn bNN(&mut self, span: Self::Span) -> Self::TypeVariable;
|
||||
|
||||
/// Create an integer type with a polymorphic bit width.
|
||||
///
|
||||
/// Each use of `iNN` by the same operator refers to the same type variable.
|
||||
#[allow(non_snake_case)]
|
||||
fn iNN(&mut self, span: Self::Span) -> Self::TypeVariable;
|
||||
|
||||
/// Create an integer type with a polymorphic bit width.
|
||||
///
|
||||
/// Each use of `iMM` by the same operator refers to the same type variable.
|
||||
#[allow(non_snake_case)]
|
||||
fn iMM(&mut self, span: Self::Span) -> Self::TypeVariable;
|
||||
|
||||
/// Create the CPU flags type variable.
|
||||
fn cpu_flags(&mut self, span: Self::Span) -> Self::TypeVariable;
|
||||
|
||||
/// Create a boolean type of size one bit.
|
||||
fn b1(&mut self, span: Self::Span) -> Self::TypeVariable;
|
||||
|
||||
/// Create the void type, used as the result of operators that branch away,
|
||||
/// or do not return anything.
|
||||
fn void(&mut self, span: Self::Span) -> Self::TypeVariable;
|
||||
|
||||
/// Create a type variable that may be either a boolean or an integer.
|
||||
fn bool_or_int(&mut self, span: Self::Span) -> Self::TypeVariable;
|
||||
|
||||
/// Create a type variable that can be any type T.
|
||||
///
|
||||
/// Each use of `any_t` by the same operator refers to the same type
|
||||
/// variable.
|
||||
fn any_t(&mut self, span: Self::Span) -> Self::TypeVariable;
|
||||
}
|
||||
|
||||
/// The typing rules for a `TOperator` type.
|
||||
///
|
||||
/// This trait describes the types of immediates, parameters, and results of an
|
||||
/// operator type, as well as their arity.
|
||||
pub trait TypingRules {
|
||||
/// Get the result type of this operator.
|
||||
fn result_type<'a, C>(&self, span: C::Span, typing_context: &mut C) -> C::TypeVariable
|
||||
where
|
||||
C: TypingContext<'a>;
|
||||
|
||||
/// Get the number of immediates this operator has.
|
||||
fn immediates_arity(&self) -> u8;
|
||||
|
||||
/// Get the types of this operator's immediates.
|
||||
fn immediate_types<'a, C>(
|
||||
&self,
|
||||
span: C::Span,
|
||||
typing_context: &mut C,
|
||||
types: &mut impl Extend<C::TypeVariable>,
|
||||
) where
|
||||
C: TypingContext<'a>;
|
||||
|
||||
/// Get the number of parameters this operator has.
|
||||
fn parameters_arity(&self) -> u8;
|
||||
|
||||
/// Get the types of this operator's parameters.
|
||||
fn parameter_types<'a, C>(
|
||||
&self,
|
||||
span: C::Span,
|
||||
typing_context: &mut C,
|
||||
types: &mut impl Extend<C::TypeVariable>,
|
||||
) where
|
||||
C: TypingContext<'a>;
|
||||
|
||||
/// Is this a bit width reducing instruction?
|
||||
///
|
||||
/// E.g. Cranelift's `ireduce` instruction.
|
||||
fn is_reduce(&self) -> bool;
|
||||
|
||||
/// Is this a bit width extending instruction?
|
||||
///
|
||||
/// E.g. Cranelift's `uextend` and `sextend` instructions.
|
||||
fn is_extend(&self) -> bool;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
(=> (when (imul $x $C)
|
||||
(is-power-of-two $C))
|
||||
(ishl $x $(log2 $C)))
|
||||
@@ -1,13 +0,0 @@
|
||||
;; Remove redundant bitwise OR instructions that are no-ops.
|
||||
|
||||
(=> (bor $x (bor $x $y))
|
||||
(bor $x $y))
|
||||
|
||||
(=> (bor $y (bor $x $y))
|
||||
(bor $x $y))
|
||||
|
||||
(=> (bor (bor $x $y) $x)
|
||||
(bor $x $y))
|
||||
|
||||
(=> (bor (bor $x $y) $y)
|
||||
(bor $x $y))
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 252 KiB |
@@ -1,11 +0,0 @@
|
||||
(=> (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))
|
||||
@@ -1,534 +0,0 @@
|
||||
//! Abstract syntax tree type definitions.
|
||||
//!
|
||||
//! This file makes fairly heavy use of macros, which are defined in the
|
||||
//! `peepmatic_macro` crate that lives at `crates/macro`. Notably, the following
|
||||
//! traits are all derived via `derive(Ast)`:
|
||||
//!
|
||||
//! * `Span` -- access the `wast::Span` where an AST node was parsed from. For
|
||||
//! `struct`s, there must be a `span: wast::Span` field, because the macro
|
||||
//! always generates an implementation that returns `self.span` for
|
||||
//! `struct`s. For `enum`s, every variant must have a single, unnamed field
|
||||
//! which implements the `Span` trait. The macro will generate code to return
|
||||
//! the span of whatever variant it is.
|
||||
//!
|
||||
//! * `ChildNodes` -- get each of the child AST nodes that a given node
|
||||
//! references. Some fields in an AST type aren't actually considered an AST
|
||||
//! node (like spans) and these are ignored via the `#[peepmatic(skip_child)]`
|
||||
//! attribute. Some fields contain multiple AST nodes (like vectors of
|
||||
//! operands) and these are flattened with `#[peepmatic(flatten)]`.
|
||||
//!
|
||||
//! * `From<&'a Self> for DynAstRef<'a>` -- convert a particular AST type into
|
||||
//! `DynAstRef`, which is an `enum` of all the different kinds of AST nodes.
|
||||
|
||||
use peepmatic_macro::Ast;
|
||||
use peepmatic_runtime::{
|
||||
r#type::{BitWidth, Type},
|
||||
unquote::UnquoteOperator,
|
||||
};
|
||||
use std::cell::Cell;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::marker::PhantomData;
|
||||
use wast::Id;
|
||||
|
||||
/// A reference to any AST node.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum DynAstRef<'a, TOperator> {
|
||||
/// A reference to an `Optimizations`.
|
||||
Optimizations(&'a Optimizations<'a, TOperator>),
|
||||
|
||||
/// A reference to an `Optimization`.
|
||||
Optimization(&'a Optimization<'a, TOperator>),
|
||||
|
||||
/// A reference to an `Lhs`.
|
||||
Lhs(&'a Lhs<'a, TOperator>),
|
||||
|
||||
/// A reference to an `Rhs`.
|
||||
Rhs(&'a Rhs<'a, TOperator>),
|
||||
|
||||
/// A reference to a `Pattern`.
|
||||
Pattern(&'a Pattern<'a, TOperator>),
|
||||
|
||||
/// A reference to a `Precondition`.
|
||||
Precondition(&'a Precondition<'a, TOperator>),
|
||||
|
||||
/// A reference to a `ConstraintOperand`.
|
||||
ConstraintOperand(&'a ConstraintOperand<'a, TOperator>),
|
||||
|
||||
/// A reference to a `ValueLiteral`.
|
||||
ValueLiteral(&'a ValueLiteral<'a, TOperator>),
|
||||
|
||||
/// A reference to a `Constant`.
|
||||
Constant(&'a Constant<'a, TOperator>),
|
||||
|
||||
/// A reference to a `PatternOperation`.
|
||||
PatternOperation(&'a Operation<'a, TOperator, Pattern<'a, TOperator>>),
|
||||
|
||||
/// A reference to a `Variable`.
|
||||
Variable(&'a Variable<'a, TOperator>),
|
||||
|
||||
/// A reference to an `Integer`.
|
||||
Integer(&'a Integer<'a, TOperator>),
|
||||
|
||||
/// A reference to a `Boolean`.
|
||||
Boolean(&'a Boolean<'a, TOperator>),
|
||||
|
||||
/// A reference to a `ConditionCode`.
|
||||
ConditionCode(&'a ConditionCode<'a, TOperator>),
|
||||
|
||||
/// A reference to an `Unquote`.
|
||||
Unquote(&'a Unquote<'a, TOperator>),
|
||||
|
||||
/// A reference to an `RhsOperation`.
|
||||
RhsOperation(&'a Operation<'a, TOperator, Rhs<'a, TOperator>>),
|
||||
}
|
||||
|
||||
impl<'a, 'b, TOperator> ChildNodes<'a, 'b, TOperator> for DynAstRef<'a, TOperator> {
|
||||
fn child_nodes(&'b self, sink: &mut impl Extend<DynAstRef<'a, TOperator>>) {
|
||||
match self {
|
||||
Self::Optimizations(x) => x.child_nodes(sink),
|
||||
Self::Optimization(x) => x.child_nodes(sink),
|
||||
Self::Lhs(x) => x.child_nodes(sink),
|
||||
Self::Rhs(x) => x.child_nodes(sink),
|
||||
Self::Pattern(x) => x.child_nodes(sink),
|
||||
Self::Precondition(x) => x.child_nodes(sink),
|
||||
Self::ConstraintOperand(x) => x.child_nodes(sink),
|
||||
Self::ValueLiteral(x) => x.child_nodes(sink),
|
||||
Self::Constant(x) => x.child_nodes(sink),
|
||||
Self::PatternOperation(x) => x.child_nodes(sink),
|
||||
Self::Variable(x) => x.child_nodes(sink),
|
||||
Self::Integer(x) => x.child_nodes(sink),
|
||||
Self::Boolean(x) => x.child_nodes(sink),
|
||||
Self::ConditionCode(x) => x.child_nodes(sink),
|
||||
Self::Unquote(x) => x.child_nodes(sink),
|
||||
Self::RhsOperation(x) => x.child_nodes(sink),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait implemented by all AST nodes.
|
||||
///
|
||||
/// All AST nodes can:
|
||||
///
|
||||
/// * Enumerate their children via `ChildNodes`.
|
||||
///
|
||||
/// * Give you the `wast::Span` where they were defined.
|
||||
///
|
||||
/// * Be converted into a `DynAstRef`.
|
||||
///
|
||||
/// This trait is blanked implemented for everything that does those three
|
||||
/// things, and in practice those three thrings are all implemented by the
|
||||
/// `derive(Ast)` macro.
|
||||
pub trait Ast<'a, TOperator>: 'a + ChildNodes<'a, 'a, TOperator> + Span
|
||||
where
|
||||
DynAstRef<'a, TOperator>: From<&'a Self>,
|
||||
TOperator: 'a,
|
||||
{
|
||||
}
|
||||
|
||||
impl<'a, T, TOperator> Ast<'a, TOperator> for T
|
||||
where
|
||||
T: 'a + ?Sized + ChildNodes<'a, 'a, TOperator> + Span,
|
||||
DynAstRef<'a, TOperator>: From<&'a Self>,
|
||||
TOperator: 'a,
|
||||
{
|
||||
}
|
||||
|
||||
/// Enumerate the child AST nodes of a given node.
|
||||
pub trait ChildNodes<'a, 'b, TOperator>
|
||||
where
|
||||
TOperator: 'a,
|
||||
{
|
||||
/// Get each of this AST node's children, in order.
|
||||
fn child_nodes(&'b self, sink: &mut impl Extend<DynAstRef<'a, TOperator>>);
|
||||
}
|
||||
|
||||
/// A trait for getting the span where an AST node was defined.
|
||||
pub trait Span {
|
||||
/// Get the span where this AST node was defined.
|
||||
fn span(&self) -> wast::Span;
|
||||
}
|
||||
|
||||
/// A set of optimizations.
|
||||
///
|
||||
/// This is the root AST node.
|
||||
#[derive(Debug, Ast)]
|
||||
pub struct Optimizations<'a, TOperator> {
|
||||
/// Where these `Optimizations` were defined.
|
||||
#[peepmatic(skip_child)]
|
||||
pub span: wast::Span,
|
||||
|
||||
/// The optimizations.
|
||||
#[peepmatic(flatten)]
|
||||
pub optimizations: Vec<Optimization<'a, TOperator>>,
|
||||
}
|
||||
|
||||
/// A complete optimization: a left-hand side to match against and a right-hand
|
||||
/// side replacement.
|
||||
#[derive(Debug, Ast)]
|
||||
pub struct Optimization<'a, TOperator> {
|
||||
/// Where this `Optimization` was defined.
|
||||
#[peepmatic(skip_child)]
|
||||
pub span: wast::Span,
|
||||
|
||||
/// The left-hand side that matches when this optimization applies.
|
||||
pub lhs: Lhs<'a, TOperator>,
|
||||
|
||||
/// The new sequence of instructions to replace an old sequence that matches
|
||||
/// the left-hand side with.
|
||||
pub rhs: Rhs<'a, TOperator>,
|
||||
}
|
||||
|
||||
/// A left-hand side describes what is required for a particular optimization to
|
||||
/// apply.
|
||||
///
|
||||
/// A left-hand side has two parts: a structural pattern for describing
|
||||
/// candidate instruction sequences, and zero or more preconditions that add
|
||||
/// additional constraints upon instruction sequences matched by the pattern.
|
||||
#[derive(Debug, Ast)]
|
||||
pub struct Lhs<'a, TOperator> {
|
||||
/// Where this `Lhs` was defined.
|
||||
#[peepmatic(skip_child)]
|
||||
pub span: wast::Span,
|
||||
|
||||
/// A pattern that describes sequences of instructions to match.
|
||||
pub pattern: Pattern<'a, TOperator>,
|
||||
|
||||
/// Additional constraints that a match must satisfy in addition to
|
||||
/// structually matching the pattern, e.g. some constant must be a power of
|
||||
/// two.
|
||||
#[peepmatic(flatten)]
|
||||
pub preconditions: Vec<Precondition<'a, TOperator>>,
|
||||
}
|
||||
|
||||
/// A structural pattern, potentially with wildcard variables for matching whole
|
||||
/// subtrees.
|
||||
#[derive(Debug, Ast)]
|
||||
pub enum Pattern<'a, TOperator> {
|
||||
/// A specific value. These are written as `1234` or `0x1234` or `true` or
|
||||
/// `false`.
|
||||
ValueLiteral(ValueLiteral<'a, TOperator>),
|
||||
|
||||
/// A constant that matches any constant value. This subsumes value
|
||||
/// patterns. These are upper-case identifiers like `$C`.
|
||||
Constant(Constant<'a, TOperator>),
|
||||
|
||||
/// An operation pattern with zero or more operand patterns. These are
|
||||
/// s-expressions like `(iadd $x $y)`.
|
||||
Operation(Operation<'a, TOperator, Pattern<'a, TOperator>>),
|
||||
|
||||
/// A variable that matches any kind of subexpression. This subsumes all
|
||||
/// other patterns. These are lower-case identifiers like `$x`.
|
||||
Variable(Variable<'a, TOperator>),
|
||||
}
|
||||
|
||||
/// An integer or boolean value literal.
|
||||
#[derive(Debug, Ast)]
|
||||
pub enum ValueLiteral<'a, TOperator> {
|
||||
/// An integer value.
|
||||
Integer(Integer<'a, TOperator>),
|
||||
|
||||
/// A boolean value: `true` or `false`.
|
||||
Boolean(Boolean<'a, TOperator>),
|
||||
|
||||
/// A condition code: `eq`, `ne`, etc...
|
||||
ConditionCode(ConditionCode<'a, TOperator>),
|
||||
}
|
||||
|
||||
/// An integer literal.
|
||||
#[derive(Debug, PartialEq, Eq, Ast)]
|
||||
pub struct Integer<'a, TOperator> {
|
||||
/// Where this `Integer` was defined.
|
||||
#[peepmatic(skip_child)]
|
||||
pub span: wast::Span,
|
||||
|
||||
/// The integer value.
|
||||
///
|
||||
/// Note that although Cranelift allows 128 bits wide values, the widest
|
||||
/// supported constants as immediates are 64 bits.
|
||||
#[peepmatic(skip_child)]
|
||||
pub value: i64,
|
||||
|
||||
/// The bit width of this integer.
|
||||
///
|
||||
/// This is either a fixed bit width, or polymorphic over the width of the
|
||||
/// optimization.
|
||||
///
|
||||
/// This field is initialized from `None` to `Some` by the type checking
|
||||
/// pass in `src/verify.rs`.
|
||||
#[peepmatic(skip_child)]
|
||||
pub bit_width: Cell<Option<BitWidth>>,
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[peepmatic(skip_child)]
|
||||
pub marker: PhantomData<&'a TOperator>,
|
||||
}
|
||||
|
||||
impl<TOperator> Hash for Integer<'_, TOperator> {
|
||||
fn hash<H>(&self, state: &mut H)
|
||||
where
|
||||
H: Hasher,
|
||||
{
|
||||
let Integer {
|
||||
span,
|
||||
value,
|
||||
bit_width,
|
||||
marker: _,
|
||||
} = self;
|
||||
span.hash(state);
|
||||
value.hash(state);
|
||||
let bit_width = bit_width.get();
|
||||
bit_width.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// A boolean literal.
|
||||
#[derive(Debug, PartialEq, Eq, Ast)]
|
||||
pub struct Boolean<'a, TOperator> {
|
||||
/// Where this `Boolean` was defined.
|
||||
#[peepmatic(skip_child)]
|
||||
pub span: wast::Span,
|
||||
|
||||
/// The boolean value.
|
||||
#[peepmatic(skip_child)]
|
||||
pub value: bool,
|
||||
|
||||
/// The bit width of this boolean.
|
||||
///
|
||||
/// This is either a fixed bit width, or polymorphic over the width of the
|
||||
/// optimization.
|
||||
///
|
||||
/// This field is initialized from `None` to `Some` by the type checking
|
||||
/// pass in `src/verify.rs`.
|
||||
#[peepmatic(skip_child)]
|
||||
pub bit_width: Cell<Option<BitWidth>>,
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[peepmatic(skip_child)]
|
||||
pub marker: PhantomData<&'a TOperator>,
|
||||
}
|
||||
|
||||
impl<TOperator> Hash for Boolean<'_, TOperator> {
|
||||
fn hash<H>(&self, state: &mut H)
|
||||
where
|
||||
H: Hasher,
|
||||
{
|
||||
let Boolean {
|
||||
span,
|
||||
value,
|
||||
bit_width,
|
||||
marker: _,
|
||||
} = self;
|
||||
span.hash(state);
|
||||
value.hash(state);
|
||||
let bit_width = bit_width.get();
|
||||
bit_width.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// A condition code.
|
||||
#[derive(Debug, Ast)]
|
||||
pub struct ConditionCode<'a, TOperator> {
|
||||
/// Where this `ConditionCode` was defined.
|
||||
#[peepmatic(skip_child)]
|
||||
pub span: wast::Span,
|
||||
|
||||
/// The actual condition code.
|
||||
#[peepmatic(skip_child)]
|
||||
pub cc: peepmatic_runtime::cc::ConditionCode,
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[peepmatic(skip_child)]
|
||||
pub marker: PhantomData<&'a TOperator>,
|
||||
}
|
||||
|
||||
/// A symbolic constant.
|
||||
///
|
||||
/// These are identifiers containing uppercase letters: `$C`, `$MY-CONST`,
|
||||
/// `$CONSTANT1`.
|
||||
#[derive(Debug, Ast)]
|
||||
pub struct Constant<'a, TOperator> {
|
||||
/// Where this `Constant` was defined.
|
||||
#[peepmatic(skip_child)]
|
||||
pub span: wast::Span,
|
||||
|
||||
/// This constant's identifier.
|
||||
#[peepmatic(skip_child)]
|
||||
pub id: Id<'a>,
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[peepmatic(skip_child)]
|
||||
pub marker: PhantomData<&'a TOperator>,
|
||||
}
|
||||
|
||||
/// A variable that matches any subtree.
|
||||
///
|
||||
/// Duplicate uses of the same variable constrain each occurrence's match to
|
||||
/// being the same as each other occurrence as well, e.g. `(iadd $x $x)` matches
|
||||
/// `(iadd 5 5)` but not `(iadd 1 2)`.
|
||||
#[derive(Debug, Ast)]
|
||||
pub struct Variable<'a, TOperator> {
|
||||
/// Where this `Variable` was defined.
|
||||
#[peepmatic(skip_child)]
|
||||
pub span: wast::Span,
|
||||
|
||||
/// This variable's identifier.
|
||||
#[peepmatic(skip_child)]
|
||||
pub id: Id<'a>,
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[peepmatic(skip_child)]
|
||||
pub marker: PhantomData<&'a TOperator>,
|
||||
}
|
||||
|
||||
/// An operation with an operator, and operands of type `T`.
|
||||
#[derive(Debug, Ast)]
|
||||
#[peepmatic(no_into_dyn_node)]
|
||||
pub struct Operation<'a, TOperator, TOperand>
|
||||
where
|
||||
TOperator: 'a,
|
||||
TOperand: 'a + Ast<'a, TOperator>,
|
||||
DynAstRef<'a, TOperator>: From<&'a TOperand>,
|
||||
{
|
||||
/// The span where this operation was written.
|
||||
#[peepmatic(skip_child)]
|
||||
pub span: wast::Span,
|
||||
|
||||
/// The operator for this operation, e.g. `imul` or `iadd`.
|
||||
#[peepmatic(skip_child)]
|
||||
pub operator: TOperator,
|
||||
|
||||
/// An optional ascribed or inferred type for the operator.
|
||||
#[peepmatic(skip_child)]
|
||||
pub r#type: Cell<Option<Type>>,
|
||||
|
||||
/// 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<TOperand>,
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[peepmatic(skip_child)]
|
||||
pub marker: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl<'a, TOperator> From<&'a Operation<'a, TOperator, Pattern<'a, TOperator>>>
|
||||
for DynAstRef<'a, TOperator>
|
||||
{
|
||||
#[inline]
|
||||
fn from(o: &'a Operation<'a, TOperator, Pattern<'a, TOperator>>) -> DynAstRef<'a, TOperator> {
|
||||
DynAstRef::PatternOperation(o)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TOperator> From<&'a Operation<'a, TOperator, Rhs<'a, TOperator>>>
|
||||
for DynAstRef<'a, TOperator>
|
||||
{
|
||||
#[inline]
|
||||
fn from(o: &'a Operation<'a, TOperator, Rhs<'a, TOperator>>) -> DynAstRef<'a, TOperator> {
|
||||
DynAstRef::RhsOperation(o)
|
||||
}
|
||||
}
|
||||
|
||||
/// A precondition adds additional constraints to a pattern, such as "$C must be
|
||||
/// a power of two".
|
||||
#[derive(Debug, Ast)]
|
||||
pub struct Precondition<'a, TOperator> {
|
||||
/// Where this `Precondition` was defined.
|
||||
#[peepmatic(skip_child)]
|
||||
pub span: wast::Span,
|
||||
|
||||
/// The constraint operator.
|
||||
#[peepmatic(skip_child)]
|
||||
pub constraint: Constraint,
|
||||
|
||||
/// The operands of the constraint.
|
||||
#[peepmatic(flatten)]
|
||||
pub operands: Vec<ConstraintOperand<'a, TOperator>>,
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[peepmatic(skip_child)]
|
||||
pub marker: PhantomData<&'a TOperator>,
|
||||
}
|
||||
|
||||
/// Contraint operators.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
pub enum Constraint {
|
||||
/// Is the operand a power of two?
|
||||
IsPowerOfTwo,
|
||||
|
||||
/// Check the bit width of a value.
|
||||
BitWidth,
|
||||
|
||||
/// Does the argument fit within our target architecture's native word size?
|
||||
FitsInNativeWord,
|
||||
}
|
||||
|
||||
/// An operand of a precondition's constraint.
|
||||
#[derive(Debug, Ast)]
|
||||
pub enum ConstraintOperand<'a, TOperator> {
|
||||
/// A value literal operand.
|
||||
ValueLiteral(ValueLiteral<'a, TOperator>),
|
||||
|
||||
/// A constant operand.
|
||||
Constant(Constant<'a, TOperator>),
|
||||
|
||||
/// A variable operand.
|
||||
Variable(Variable<'a, TOperator>),
|
||||
}
|
||||
|
||||
/// The right-hand side of an optimization that contains the instructions to
|
||||
/// replace any matched left-hand side with.
|
||||
#[derive(Debug, Ast)]
|
||||
pub enum Rhs<'a, TOperator> {
|
||||
/// A value literal right-hand side.
|
||||
ValueLiteral(ValueLiteral<'a, TOperator>),
|
||||
|
||||
/// A constant right-hand side (the constant must have been matched and
|
||||
/// bound in the left-hand side's pattern).
|
||||
Constant(Constant<'a, TOperator>),
|
||||
|
||||
/// A variable right-hand side (the variable must have been matched and
|
||||
/// bound in the left-hand side's pattern).
|
||||
Variable(Variable<'a, TOperator>),
|
||||
|
||||
/// An unquote expression that is evaluated while replacing the left-hand
|
||||
/// side with the right-hand side. The result of the evaluation is used in
|
||||
/// the replacement.
|
||||
Unquote(Unquote<'a, TOperator>),
|
||||
|
||||
/// A compound right-hand side consisting of an operation and subsequent
|
||||
/// right-hand side operands.
|
||||
Operation(Operation<'a, TOperator, Rhs<'a, TOperator>>),
|
||||
}
|
||||
|
||||
/// An unquote operation.
|
||||
///
|
||||
/// Rather than replaciong a left-hand side, these are evaluated and then the
|
||||
/// result of the evaluation replaces the left-hand side. This allows for
|
||||
/// compile-time computation while replacing a matched left-hand side with a
|
||||
/// right-hand side.
|
||||
///
|
||||
/// For example, given the unqouted right-hand side `$(log2 $C)`, we replace any
|
||||
/// instructions that match its left-hand side with the compile-time result of
|
||||
/// `log2($C)` (the left-hand side must match and bind the constant `$C`).
|
||||
#[derive(Debug, Ast)]
|
||||
pub struct Unquote<'a, TOperator> {
|
||||
/// Where this `Unquote` was defined.
|
||||
#[peepmatic(skip_child)]
|
||||
pub span: wast::Span,
|
||||
|
||||
/// The operator for this unquote operation.
|
||||
#[peepmatic(skip_child)]
|
||||
pub operator: UnquoteOperator,
|
||||
|
||||
/// The operands for this unquote operation.
|
||||
#[peepmatic(flatten)]
|
||||
pub operands: Vec<Rhs<'a, TOperator>>,
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[peepmatic(skip_child)]
|
||||
pub marker: PhantomData<&'a TOperator>,
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
//! Compile a set of linear optimizations into an automaton.
|
||||
|
||||
use peepmatic_automata::{Automaton, Builder};
|
||||
use peepmatic_runtime::linear;
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
|
||||
/// Construct an automaton from a set of linear optimizations.
|
||||
pub fn automatize<TOperator>(
|
||||
opts: &linear::Optimizations<TOperator>,
|
||||
) -> Automaton<linear::MatchResult, linear::MatchOp, Box<[linear::Action<TOperator>]>>
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
debug_assert!(crate::linear_passes::is_sorted_lexicographically(opts));
|
||||
|
||||
let mut builder =
|
||||
Builder::<linear::MatchResult, linear::MatchOp, Box<[linear::Action<TOperator>]>>::new();
|
||||
|
||||
for opt in &opts.optimizations {
|
||||
let mut insertion = builder.insert();
|
||||
let mut is_first = true;
|
||||
for m in &opt.matches {
|
||||
// Ensure that this state's associated data is this match's
|
||||
// operation.
|
||||
if let Some(op) = insertion.get_state_data() {
|
||||
assert_eq!(*op, m.operation);
|
||||
} else {
|
||||
insertion.set_state_data(m.operation);
|
||||
}
|
||||
|
||||
let actions = if is_first {
|
||||
is_first = false;
|
||||
opt.actions.clone().into_boxed_slice()
|
||||
} else {
|
||||
vec![].into_boxed_slice()
|
||||
};
|
||||
insertion.next(m.expected, actions);
|
||||
}
|
||||
insertion.finish();
|
||||
}
|
||||
|
||||
builder.finish()
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
//! Formatting a peephole optimizer's automata for GraphViz Dot.
|
||||
//!
|
||||
//! See also `crates/automata/src/dot.rs`.
|
||||
|
||||
use peepmatic_automata::dot::DotFmt;
|
||||
use peepmatic_runtime::{
|
||||
cc::ConditionCode,
|
||||
integer_interner::{IntegerId, IntegerInterner},
|
||||
linear,
|
||||
};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::fmt::Debug;
|
||||
use std::io::{self, Write};
|
||||
use std::num::{NonZeroU16, NonZeroU32};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PeepholeDotFmt<'a>(pub(crate) &'a IntegerInterner);
|
||||
|
||||
impl<TOperator> DotFmt<linear::MatchResult, linear::MatchOp, Box<[linear::Action<TOperator>]>>
|
||||
for PeepholeDotFmt<'_>
|
||||
where
|
||||
TOperator: Debug + TryFrom<NonZeroU32>,
|
||||
{
|
||||
fn fmt_transition(
|
||||
&self,
|
||||
w: &mut impl Write,
|
||||
from: Option<&linear::MatchOp>,
|
||||
input: &linear::MatchResult,
|
||||
_to: Option<&linear::MatchOp>,
|
||||
) -> io::Result<()> {
|
||||
let from = from.expect("we should have match op for every state");
|
||||
if let Some(x) = input.ok() {
|
||||
match from {
|
||||
linear::MatchOp::Opcode { .. } => {
|
||||
let opcode = TOperator::try_from(x)
|
||||
.map_err(|_| ())
|
||||
.expect("we shouldn't generate non-opcode edges");
|
||||
write!(w, "{:?}", opcode)
|
||||
}
|
||||
linear::MatchOp::ConditionCode { .. } => {
|
||||
let cc = ConditionCode::try_from(x.get())
|
||||
.expect("we shouldn't generate non-CC edges");
|
||||
write!(w, "{}", cc)
|
||||
}
|
||||
linear::MatchOp::IntegerValue { .. } => {
|
||||
let x = self.0.lookup(IntegerId(
|
||||
NonZeroU16::new(x.get().try_into().unwrap()).unwrap(),
|
||||
));
|
||||
write!(w, "{}", x)
|
||||
}
|
||||
_ => write!(w, "Ok({})", x),
|
||||
}
|
||||
} else {
|
||||
write!(w, "(else)")
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_state(&self, w: &mut impl Write, op: &linear::MatchOp) -> io::Result<()> {
|
||||
use linear::MatchOp::*;
|
||||
|
||||
write!(w, r#"<font face="monospace">"#)?;
|
||||
|
||||
match op {
|
||||
Opcode(id) => write!(w, "opcode $lhs{}", id.0)?,
|
||||
IsConst(id) => write!(w, "is-const? $lhs{}", id.0)?,
|
||||
IsPowerOfTwo(id) => write!(w, "is-power-of-two? $lhs{}", id.0)?,
|
||||
BitWidth(id) => write!(w, "bit-width $lhs{}", id.0)?,
|
||||
FitsInNativeWord(id) => write!(w, "fits-in-native-word $lhs{}", id.0)?,
|
||||
Eq(a, b) => write!(w, "$lhs{} == $lhs{}", a.0, b.0)?,
|
||||
IntegerValue(id) => write!(w, "integer-value $lhs{}", id.0)?,
|
||||
BooleanValue(id) => write!(w, "boolean-value $lhs{}", id.0)?,
|
||||
ConditionCode(id) => write!(w, "condition-code $lhs{}", id.0)?,
|
||||
Nop => write!(w, "nop")?,
|
||||
}
|
||||
|
||||
writeln!(w, "</font>")
|
||||
}
|
||||
|
||||
fn fmt_output(
|
||||
&self,
|
||||
w: &mut impl Write,
|
||||
actions: &Box<[linear::Action<TOperator>]>,
|
||||
) -> io::Result<()> {
|
||||
use linear::Action::*;
|
||||
|
||||
if actions.is_empty() {
|
||||
return writeln!(w, "(no output)");
|
||||
}
|
||||
|
||||
write!(w, r#"<font face="monospace">"#)?;
|
||||
|
||||
for a in actions.iter() {
|
||||
match a {
|
||||
GetLhs { lhs } => write!(w, "get-lhs $lhs{}<br/>", lhs.0)?,
|
||||
UnaryUnquote { operator, operand } => {
|
||||
write!(w, "eval {:?} $rhs{}<br/>", operator, operand.0)?
|
||||
}
|
||||
BinaryUnquote { operator, operands } => write!(
|
||||
w,
|
||||
"eval {:?} $rhs{}, $rhs{}<br/>",
|
||||
operator, operands[0].0, operands[1].0,
|
||||
)?,
|
||||
MakeIntegerConst {
|
||||
value,
|
||||
bit_width: _,
|
||||
} => write!(w, "make {}<br/>", self.0.lookup(*value))?,
|
||||
MakeBooleanConst {
|
||||
value,
|
||||
bit_width: _,
|
||||
} => write!(w, "make {}<br/>", value)?,
|
||||
MakeConditionCode { cc } => write!(w, "{}<br/>", cc)?,
|
||||
MakeUnaryInst {
|
||||
operand,
|
||||
operator,
|
||||
r#type: _,
|
||||
} => write!(w, "make {:?} $rhs{}<br/>", operator, operand.0,)?,
|
||||
MakeBinaryInst {
|
||||
operator,
|
||||
operands,
|
||||
r#type: _,
|
||||
} => write!(
|
||||
w,
|
||||
"make {:?} $rhs{}, $rhs{}<br/>",
|
||||
operator, operands[0].0, operands[1].0,
|
||||
)?,
|
||||
MakeTernaryInst {
|
||||
operator,
|
||||
operands,
|
||||
r#type: _,
|
||||
} => write!(
|
||||
w,
|
||||
"make {:?} $rhs{}, $rhs{}, $rhs{}<br/>",
|
||||
operator, operands[0].0, operands[1].0, operands[2].0,
|
||||
)?,
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(w, "</font>")
|
||||
}
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
/*!
|
||||
|
||||
`peepmatic` is a DSL and compiler for generating peephole optimizers.
|
||||
|
||||
The user writes a set of optimizations in the DSL, and then `peepmatic` compiles
|
||||
the set of optimizations into an efficient peephole optimizer.
|
||||
|
||||
*/
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![deny(missing_debug_implementations)]
|
||||
|
||||
mod ast;
|
||||
mod automatize;
|
||||
mod dot_fmt;
|
||||
mod linear_passes;
|
||||
mod linearize;
|
||||
mod parser;
|
||||
mod traversals;
|
||||
mod verify;
|
||||
pub use self::{
|
||||
ast::*, automatize::*, linear_passes::*, linearize::*, parser::*, traversals::*, verify::*,
|
||||
};
|
||||
|
||||
use peepmatic_runtime::PeepholeOptimizations;
|
||||
use peepmatic_traits::TypingRules;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::Debug;
|
||||
use std::fs;
|
||||
use std::hash::Hash;
|
||||
use std::num::NonZeroU32;
|
||||
use std::path::Path;
|
||||
|
||||
/// Compile the given DSL file into a compact peephole optimizations automaton!
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// use std::path::Path;
|
||||
///
|
||||
/// let peep_opts = peepmatic::compile_file::<cranelift_codegen::ir::Opcode>(
|
||||
/// 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<TOperator>(filename: &Path) -> anyhow::Result<PeepholeOptimizations<TOperator>>
|
||||
where
|
||||
TOperator: Copy
|
||||
+ Debug
|
||||
+ Eq
|
||||
+ Hash
|
||||
+ for<'a> wast::parser::Parse<'a>
|
||||
+ TypingRules
|
||||
+ Into<NonZeroU32>
|
||||
+ TryFrom<NonZeroU32>,
|
||||
{
|
||||
let source = fs::read_to_string(filename)?;
|
||||
compile_str::<TOperator>(&source, filename)
|
||||
}
|
||||
|
||||
/// Compile the given DSL source text down into a compact peephole optimizations
|
||||
/// automaton.
|
||||
///
|
||||
/// This is like [compile_file][crate::compile_file] but you bring your own file
|
||||
/// I/O.
|
||||
///
|
||||
/// The `filename` parameter is used to provide better error messages.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// use std::path::Path;
|
||||
///
|
||||
/// let peep_opts = peepmatic::compile_str::<cranelift_codegen::ir::Opcode>(
|
||||
/// "
|
||||
/// (=> (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<TOperator>(
|
||||
source: &str,
|
||||
filename: &Path,
|
||||
) -> anyhow::Result<PeepholeOptimizations<TOperator>>
|
||||
where
|
||||
TOperator: Copy
|
||||
+ Debug
|
||||
+ Eq
|
||||
+ Hash
|
||||
+ for<'a> wast::parser::Parse<'a>
|
||||
+ TypingRules
|
||||
+ Into<NonZeroU32>
|
||||
+ TryFrom<NonZeroU32>,
|
||||
{
|
||||
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::<Optimizations<'_, TOperator>>(&buf).map_err(|mut e| {
|
||||
e.set_path(filename);
|
||||
e.set_text(source);
|
||||
e
|
||||
})?;
|
||||
|
||||
verify(&opts).map_err(|mut e| {
|
||||
e.set_path(filename);
|
||||
e.set_text(source);
|
||||
e
|
||||
})?;
|
||||
|
||||
let mut opts = crate::linearize(&opts);
|
||||
sort_least_to_most_general(&mut opts);
|
||||
remove_unnecessary_nops(&mut opts);
|
||||
match_in_same_order(&mut opts);
|
||||
sort_lexicographically(&mut opts);
|
||||
|
||||
let automata = automatize(&opts);
|
||||
let integers = opts.integers;
|
||||
|
||||
if let Ok(path) = std::env::var("PEEPMATIC_DOT") {
|
||||
let f = dot_fmt::PeepholeDotFmt(&integers);
|
||||
if let Err(e) = automata.write_dot_file(&f, &path) {
|
||||
panic!(
|
||||
"failed to write GraphViz Dot file to PEEPMATIC_DOT={}; error: {}",
|
||||
path, e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(PeepholeOptimizations { integers, automata })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use peepmatic_test_operator::TestOperator;
|
||||
|
||||
fn assert_compiles(path: &str) {
|
||||
match compile_file::<TestOperator>(Path::new(path)) {
|
||||
Ok(_) => return,
|
||||
Err(e) => {
|
||||
eprintln!("error: {}", e);
|
||||
panic!("error: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compile_redundant_bor() {
|
||||
assert_compiles("examples/redundant-bor.peepmatic");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mul_by_pow2() {
|
||||
assert_compiles("examples/mul-by-pow2.peepmatic");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compile_preopt() {
|
||||
assert_compiles("../codegen/src/preopt.peepmatic");
|
||||
}
|
||||
}
|
||||
@@ -1,594 +0,0 @@
|
||||
//! Passes over the linear IR.
|
||||
|
||||
use peepmatic_runtime::linear;
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
|
||||
/// Sort a set of optimizations from least to most general.
|
||||
///
|
||||
/// This helps us ensure that we always match the least-general (aka
|
||||
/// most-specific) optimization that we can for a particular instruction
|
||||
/// sequence.
|
||||
///
|
||||
/// For example, if we have both of these optimizations:
|
||||
///
|
||||
/// ```lisp
|
||||
/// (=> (imul $C $x)
|
||||
/// (imul_imm $C $x))
|
||||
///
|
||||
/// (=> (when (imul $C $x))
|
||||
/// (is-power-of-two $C))
|
||||
/// (ishl $x $C))
|
||||
/// ```
|
||||
///
|
||||
/// and we are matching `(imul 4 (..))`, then we want to apply the second
|
||||
/// optimization, because it is more specific than the first.
|
||||
pub fn sort_least_to_most_general<TOperator>(opts: &mut linear::Optimizations<TOperator>)
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
let linear::Optimizations {
|
||||
ref mut optimizations,
|
||||
..
|
||||
} = opts;
|
||||
|
||||
// NB: we *cannot* use an unstable sort here, because we want deterministic
|
||||
// compilation of optimizations to automata.
|
||||
optimizations.sort_by(compare_optimization_generality);
|
||||
debug_assert!(is_sorted_by_generality(opts));
|
||||
}
|
||||
|
||||
/// Sort the linear optimizations lexicographically.
|
||||
///
|
||||
/// This sort order is required for automata construction.
|
||||
pub fn sort_lexicographically<TOperator>(opts: &mut linear::Optimizations<TOperator>)
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
let linear::Optimizations {
|
||||
ref mut optimizations,
|
||||
..
|
||||
} = opts;
|
||||
|
||||
// NB: we *cannot* use an unstable sort here, same as above.
|
||||
optimizations.sort_by(|a, b| compare_optimizations(a, b, |a_len, b_len| a_len.cmp(&b_len)));
|
||||
}
|
||||
|
||||
fn compare_optimizations<TOperator>(
|
||||
a: &linear::Optimization<TOperator>,
|
||||
b: &linear::Optimization<TOperator>,
|
||||
compare_lengths: impl Fn(usize, usize) -> Ordering,
|
||||
) -> Ordering
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
for (a, b) in a.matches.iter().zip(b.matches.iter()) {
|
||||
let c = compare_match_op_generality(a.operation, b.operation);
|
||||
if c != Ordering::Equal {
|
||||
return c;
|
||||
}
|
||||
|
||||
let c = match (a.expected, b.expected) {
|
||||
(Ok(a), Ok(b)) => a.cmp(&b).reverse(),
|
||||
(Err(_), Ok(_)) => Ordering::Greater,
|
||||
(Ok(_), Err(_)) => Ordering::Less,
|
||||
(Err(linear::Else), Err(linear::Else)) => Ordering::Equal,
|
||||
};
|
||||
if c != Ordering::Equal {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
compare_lengths(a.matches.len(), b.matches.len())
|
||||
}
|
||||
|
||||
fn compare_optimization_generality<TOperator>(
|
||||
a: &linear::Optimization<TOperator>,
|
||||
b: &linear::Optimization<TOperator>,
|
||||
) -> Ordering
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
compare_optimizations(a, b, |a_len, b_len| {
|
||||
// If they shared equivalent prefixes, then compare lengths and invert the
|
||||
// result because longer patterns are less general than shorter patterns.
|
||||
a_len.cmp(&b_len).reverse()
|
||||
})
|
||||
}
|
||||
|
||||
fn compare_match_op_generality(a: linear::MatchOp, b: linear::MatchOp) -> Ordering {
|
||||
use linear::MatchOp::*;
|
||||
match (a, b) {
|
||||
(Opcode(a), Opcode(b)) => a.cmp(&b),
|
||||
(Opcode(_), _) => Ordering::Less,
|
||||
(_, Opcode(_)) => Ordering::Greater,
|
||||
|
||||
(IntegerValue(a), IntegerValue(b)) => a.cmp(&b),
|
||||
(IntegerValue(_), _) => Ordering::Less,
|
||||
(_, IntegerValue(_)) => Ordering::Greater,
|
||||
|
||||
(BooleanValue(a), BooleanValue(b)) => a.cmp(&b),
|
||||
(BooleanValue(_), _) => Ordering::Less,
|
||||
(_, BooleanValue(_)) => Ordering::Greater,
|
||||
|
||||
(ConditionCode(a), ConditionCode(b)) => a.cmp(&b),
|
||||
(ConditionCode(_), _) => Ordering::Less,
|
||||
(_, ConditionCode(_)) => Ordering::Greater,
|
||||
|
||||
(IsConst(a), IsConst(b)) => a.cmp(&b),
|
||||
(IsConst(_), _) => Ordering::Less,
|
||||
(_, IsConst(_)) => Ordering::Greater,
|
||||
|
||||
(Eq(a1, b1), Eq(a2, b2)) => a1.cmp(&a2).then(b1.cmp(&b2)),
|
||||
(Eq(..), _) => Ordering::Less,
|
||||
(_, Eq(..)) => Ordering::Greater,
|
||||
|
||||
(IsPowerOfTwo(a), IsPowerOfTwo(b)) => a.cmp(&b),
|
||||
(IsPowerOfTwo(_), _) => Ordering::Less,
|
||||
(_, IsPowerOfTwo(_)) => Ordering::Greater,
|
||||
|
||||
(BitWidth(a), BitWidth(b)) => a.cmp(&b),
|
||||
(BitWidth(_), _) => Ordering::Less,
|
||||
(_, BitWidth(_)) => Ordering::Greater,
|
||||
|
||||
(FitsInNativeWord(a), FitsInNativeWord(b)) => a.cmp(&b),
|
||||
(FitsInNativeWord(_), _) => Ordering::Less,
|
||||
(_, FitsInNativeWord(_)) => Ordering::Greater,
|
||||
|
||||
(Nop, Nop) => Ordering::Equal,
|
||||
}
|
||||
}
|
||||
|
||||
/// Are the given optimizations sorted from least to most general?
|
||||
pub(crate) fn is_sorted_by_generality<TOperator>(opts: &linear::Optimizations<TOperator>) -> bool
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
opts.optimizations
|
||||
.windows(2)
|
||||
.all(|w| compare_optimization_generality(&w[0], &w[1]) <= Ordering::Equal)
|
||||
}
|
||||
|
||||
/// Are the given optimizations sorted lexicographically?
|
||||
pub(crate) fn is_sorted_lexicographically<TOperator>(
|
||||
opts: &linear::Optimizations<TOperator>,
|
||||
) -> bool
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
opts.optimizations.windows(2).all(|w| {
|
||||
compare_optimizations(&w[0], &w[1], |a_len, b_len| a_len.cmp(&b_len)) <= Ordering::Equal
|
||||
})
|
||||
}
|
||||
|
||||
/// Ensure that we emit match operations in a consistent order.
|
||||
///
|
||||
/// There are many linear optimizations, each of which have their own sequence
|
||||
/// of match operations that need to be tested. But when interpreting the
|
||||
/// automata against some instructions, we only perform a single sequence of
|
||||
/// match operations, and at any given moment, we only want one match operation
|
||||
/// to interpret next. This means that two optimizations that are next to each
|
||||
/// other in the sorting must have their shared prefixes diverge on an
|
||||
/// **expected result edge**, not on which match operation to preform next. And
|
||||
/// if they have zero shared prefix, then we need to create one, that
|
||||
/// immediately divereges on the expected result.
|
||||
///
|
||||
/// For example, consider these two patterns that don't have any shared prefix:
|
||||
///
|
||||
/// ```lisp
|
||||
/// (=> (iadd $x $y) ...)
|
||||
/// (=> $C ...)
|
||||
/// ```
|
||||
///
|
||||
/// These produce the following linear match operations and expected results:
|
||||
///
|
||||
/// ```text
|
||||
/// opcode @ 0 --iadd-->
|
||||
/// is-const? @ 0 --true-->
|
||||
/// ```
|
||||
///
|
||||
/// In order to ensure that we only have one match operation to interpret at any
|
||||
/// given time when evaluating the automata, this pass transforms the second
|
||||
/// optimization so that it shares a prefix match operation, but diverges on the
|
||||
/// expected result:
|
||||
///
|
||||
/// ```text
|
||||
/// opcode @ 0 --iadd-->
|
||||
/// opcode @ 0 --(else)--> is-const? @ 0 --true-->
|
||||
/// ```
|
||||
pub fn match_in_same_order<TOperator>(opts: &mut linear::Optimizations<TOperator>)
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
assert!(!opts.optimizations.is_empty());
|
||||
|
||||
let mut prefix = vec![];
|
||||
|
||||
for opt in &mut opts.optimizations {
|
||||
assert!(!opt.matches.is_empty());
|
||||
|
||||
let mut old_matches = opt.matches.iter().peekable();
|
||||
let mut new_matches = vec![];
|
||||
|
||||
for (last_op, last_expected) in &prefix {
|
||||
match old_matches.peek() {
|
||||
None => {
|
||||
break;
|
||||
}
|
||||
Some(inc) if *last_op == inc.operation => {
|
||||
let inc = old_matches.next().unwrap();
|
||||
new_matches.push(inc.clone());
|
||||
if inc.expected != *last_expected {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Some(_) => {
|
||||
new_matches.push(linear::Match {
|
||||
operation: *last_op,
|
||||
expected: Err(linear::Else),
|
||||
});
|
||||
if last_expected.is_ok() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new_matches.extend(old_matches.cloned());
|
||||
assert!(new_matches.len() >= opt.matches.len());
|
||||
opt.matches = new_matches;
|
||||
|
||||
prefix.clear();
|
||||
prefix.extend(opt.matches.iter().map(|inc| (inc.operation, inc.expected)));
|
||||
}
|
||||
|
||||
// Should still be sorted after this pass.
|
||||
debug_assert!(is_sorted_by_generality(&opts));
|
||||
}
|
||||
|
||||
/// 99.99% of nops are unnecessary; remove them.
|
||||
///
|
||||
/// They're only needed for when a LHS pattern is just a variable, and that's
|
||||
/// it. However, it is easier to have basically unused nop matching operations
|
||||
/// for the DSL's edge-cases than it is to try and statically eliminate their
|
||||
/// existence completely. So we just emit nop match operations for all variable
|
||||
/// patterns, and then in this post-processing pass, we fuse them and their
|
||||
/// actions with their preceding match.
|
||||
pub fn remove_unnecessary_nops<TOperator>(opts: &mut linear::Optimizations<TOperator>)
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
for opt in &mut opts.optimizations {
|
||||
if opt.matches.len() < 2 {
|
||||
debug_assert!(!opt.matches.is_empty());
|
||||
continue;
|
||||
}
|
||||
|
||||
for i in (1..opt.matches.len()).rev() {
|
||||
if let linear::MatchOp::Nop = opt.matches[i].operation {
|
||||
opt.matches.remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ast::*;
|
||||
use peepmatic_runtime::linear::{bool_to_match_result, Else, LhsId, MatchOp::*, MatchResult};
|
||||
use peepmatic_test_operator::TestOperator;
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
#[test]
|
||||
fn ok_non_zero_less_than_err_else() {
|
||||
assert!(Ok(NonZeroU32::new(1).unwrap()) < Err(Else));
|
||||
}
|
||||
|
||||
macro_rules! sorts_to {
|
||||
($test_name:ident, $source:expr, $make_expected:expr) => {
|
||||
#[test]
|
||||
#[allow(unused_variables)]
|
||||
fn $test_name() {
|
||||
let buf = wast::parser::ParseBuffer::new($source).expect("should lex OK");
|
||||
|
||||
let opts = match wast::parser::parse::<Optimizations<TestOperator>>(&buf) {
|
||||
Ok(opts) => opts,
|
||||
Err(mut e) => {
|
||||
e.set_path(std::path::Path::new(stringify!($test_name)));
|
||||
e.set_text($source);
|
||||
eprintln!("{}", e);
|
||||
panic!("should parse OK")
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(mut e) = crate::verify(&opts) {
|
||||
e.set_path(std::path::Path::new(stringify!($test_name)));
|
||||
e.set_text($source);
|
||||
eprintln!("{}", e);
|
||||
panic!("should verify OK")
|
||||
}
|
||||
|
||||
let mut opts = crate::linearize(&opts);
|
||||
|
||||
let before = opts
|
||||
.optimizations
|
||||
.iter()
|
||||
.map(|o| {
|
||||
o.matches
|
||||
.iter()
|
||||
.map(|i| format!("{:?} == {:?}", i.operation, i.expected))
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
eprintln!("before = {:#?}", before);
|
||||
|
||||
sort_least_to_most_general(&mut opts);
|
||||
|
||||
let after = opts
|
||||
.optimizations
|
||||
.iter()
|
||||
.map(|o| {
|
||||
o.matches
|
||||
.iter()
|
||||
.map(|i| format!("{:?} == {:?}", i.operation, i.expected))
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
eprintln!("after = {:#?}", before);
|
||||
|
||||
let linear::Optimizations {
|
||||
mut integers,
|
||||
optimizations,
|
||||
} = opts;
|
||||
|
||||
let actual: Vec<Vec<_>> = optimizations
|
||||
.iter()
|
||||
.map(|o| {
|
||||
o.matches
|
||||
.iter()
|
||||
.map(|i| (i.operation, i.expected))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut i = |i: u64| Ok(integers.intern(i).into());
|
||||
let expected = $make_expected(&mut i);
|
||||
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! match_in_same_order {
|
||||
($test_name:ident, $source:expr, $make_expected:expr) => {
|
||||
#[test]
|
||||
#[allow(unused_variables)]
|
||||
fn $test_name() {
|
||||
let buf = wast::parser::ParseBuffer::new($source).expect("should lex OK");
|
||||
|
||||
let opts = match wast::parser::parse::<Optimizations<TestOperator>>(&buf) {
|
||||
Ok(opts) => opts,
|
||||
Err(mut e) => {
|
||||
e.set_path(std::path::Path::new(stringify!($test_name)));
|
||||
e.set_text($source);
|
||||
eprintln!("{}", e);
|
||||
panic!("should parse OK")
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(mut e) = crate::verify(&opts) {
|
||||
e.set_path(std::path::Path::new(stringify!($test_name)));
|
||||
e.set_text($source);
|
||||
eprintln!("{}", e);
|
||||
panic!("should verify OK")
|
||||
}
|
||||
|
||||
let mut opts = crate::linearize(&opts);
|
||||
sort_least_to_most_general(&mut opts);
|
||||
|
||||
let before = opts
|
||||
.optimizations
|
||||
.iter()
|
||||
.map(|o| {
|
||||
o.matches
|
||||
.iter()
|
||||
.map(|i| format!("{:?} == {:?}", i.operation, i.expected))
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
eprintln!("before = {:#?}", before);
|
||||
|
||||
match_in_same_order(&mut opts);
|
||||
|
||||
let after = opts
|
||||
.optimizations
|
||||
.iter()
|
||||
.map(|o| {
|
||||
o.matches
|
||||
.iter()
|
||||
.map(|i| format!("{:?} == {:?}", i.operation, i.expected))
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
eprintln!("after = {:#?}", before);
|
||||
|
||||
let linear::Optimizations {
|
||||
mut integers,
|
||||
optimizations,
|
||||
} = opts;
|
||||
|
||||
let actual: Vec<Vec<_>> = optimizations
|
||||
.iter()
|
||||
.map(|o| {
|
||||
o.matches
|
||||
.iter()
|
||||
.map(|i| (i.operation, i.expected))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut i = |i: u64| Ok(integers.intern(i).into());
|
||||
let expected = $make_expected(&mut i);
|
||||
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
sorts_to!(
|
||||
test_sort_least_to_most_general,
|
||||
"
|
||||
(=> $x 0)
|
||||
(=> (iadd $x $y) 0)
|
||||
(=> (iadd $x $x) 0)
|
||||
(=> (iadd $x $C) 0)
|
||||
(=> (when (iadd $x $C) (is-power-of-two $C)) 0)
|
||||
(=> (when (iadd $x $C) (bit-width $x 32)) 0)
|
||||
(=> (iadd $x 42) 0)
|
||||
(=> (iadd $x (iadd $y $z)) 0)
|
||||
",
|
||||
|i: &mut dyn FnMut(u64) -> MatchResult| vec![
|
||||
vec![
|
||||
(Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())),
|
||||
(Nop, Err(Else)),
|
||||
(Opcode(LhsId(2)), Ok(TestOperator::Iadd.into())),
|
||||
(Nop, Err(Else)),
|
||||
(Nop, Err(Else)),
|
||||
],
|
||||
vec![
|
||||
(Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())),
|
||||
(Nop, Err(Else)),
|
||||
(IntegerValue(LhsId(2)), i(42))
|
||||
],
|
||||
vec![
|
||||
(Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())),
|
||||
(Nop, Err(Else)),
|
||||
(IsConst(LhsId(2)), bool_to_match_result(true)),
|
||||
(IsPowerOfTwo(LhsId(2)), bool_to_match_result(true))
|
||||
],
|
||||
vec![
|
||||
(Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())),
|
||||
(Nop, Err(Else)),
|
||||
(IsConst(LhsId(2)), bool_to_match_result(true)),
|
||||
(BitWidth(LhsId(1)), Ok(NonZeroU32::new(32).unwrap()))
|
||||
],
|
||||
vec![
|
||||
(Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())),
|
||||
(Nop, Err(Else)),
|
||||
(IsConst(LhsId(2)), bool_to_match_result(true))
|
||||
],
|
||||
vec![
|
||||
(Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())),
|
||||
(Nop, Err(Else)),
|
||||
(Eq(LhsId(2), LhsId(1)), bool_to_match_result(true))
|
||||
],
|
||||
vec![
|
||||
(Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())),
|
||||
(Nop, Err(Else)),
|
||||
(Nop, Err(Else)),
|
||||
],
|
||||
vec![(Nop, Err(Else))]
|
||||
]
|
||||
);
|
||||
|
||||
sorts_to!(
|
||||
expected_edges_are_sorted,
|
||||
"
|
||||
(=> (iadd 0 $x) $x)
|
||||
(=> (iadd $x 0) $x)
|
||||
(=> (imul 1 $x) $x)
|
||||
(=> (imul $x 1) $x)
|
||||
(=> (imul 2 $x) (ishl $x 1))
|
||||
(=> (imul $x 2) (ishl $x 1))
|
||||
",
|
||||
|i: &mut dyn FnMut(u64) -> MatchResult| vec![
|
||||
vec![
|
||||
(Opcode(LhsId(0)), Ok(TestOperator::Imul.into())),
|
||||
(IntegerValue(LhsId(1)), i(2)),
|
||||
(Nop, Err(Else))
|
||||
],
|
||||
vec![
|
||||
(Opcode(LhsId(0)), Ok(TestOperator::Imul.into())),
|
||||
(IntegerValue(LhsId(1)), i(1)),
|
||||
(Nop, Err(Else))
|
||||
],
|
||||
vec![
|
||||
(Opcode(LhsId(0)), Ok(TestOperator::Imul.into())),
|
||||
(Nop, Err(Else)),
|
||||
(IntegerValue(LhsId(2)), i(2))
|
||||
],
|
||||
vec![
|
||||
(Opcode(LhsId(0)), Ok(TestOperator::Imul.into())),
|
||||
(Nop, Err(Else)),
|
||||
(IntegerValue(LhsId(2)), i(1))
|
||||
],
|
||||
vec![
|
||||
(Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())),
|
||||
(IntegerValue(LhsId(1)), i(0)),
|
||||
(Nop, Err(Else))
|
||||
],
|
||||
vec![
|
||||
(Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())),
|
||||
(Nop, Err(Else)),
|
||||
(IntegerValue(LhsId(2)), i(0))
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
sorts_to!(
|
||||
sort_redundant_bor,
|
||||
"
|
||||
(=> (bor (bor $x $y) $x)
|
||||
(bor $x $y))
|
||||
|
||||
(=> (bor (bor $x $y) $y)
|
||||
(bor $x $y))
|
||||
",
|
||||
|i: &mut dyn FnMut(u64) -> MatchResult| vec![
|
||||
vec![
|
||||
(Opcode(LhsId(0)), Ok(TestOperator::Bor.into())),
|
||||
(Opcode(LhsId(1)), Ok(TestOperator::Bor.into())),
|
||||
(Nop, Err(Else)),
|
||||
(Eq(LhsId(3), LhsId(2)), bool_to_match_result(true)),
|
||||
(Nop, Err(Else)),
|
||||
],
|
||||
vec![
|
||||
(Opcode(LhsId(0)), Ok(TestOperator::Bor.into())),
|
||||
(Opcode(LhsId(1)), Ok(TestOperator::Bor.into())),
|
||||
(Nop, Err(Else)),
|
||||
(Nop, Err(Else)),
|
||||
(Eq(LhsId(4), LhsId(2)), bool_to_match_result(true)),
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
match_in_same_order!(
|
||||
match_in_same_order_redundant_bor,
|
||||
"
|
||||
(=> (bor (bor $x $y) $x)
|
||||
(bor $x $y))
|
||||
|
||||
(=> (bor (bor $x $y) $y)
|
||||
(bor $x $y))
|
||||
",
|
||||
|i: &mut dyn FnMut(u64) -> MatchResult| vec![
|
||||
vec![
|
||||
(Opcode(LhsId(0)), Ok(TestOperator::Bor.into())),
|
||||
(Opcode(LhsId(1)), Ok(TestOperator::Bor.into())),
|
||||
(Nop, Err(Else)),
|
||||
(Eq(LhsId(3), LhsId(2)), bool_to_match_result(true)),
|
||||
(Nop, Err(Else)),
|
||||
],
|
||||
vec![
|
||||
(Opcode(LhsId(0)), Ok(TestOperator::Bor.into())),
|
||||
(Opcode(LhsId(1)), Ok(TestOperator::Bor.into())),
|
||||
(Nop, Err(Else)),
|
||||
(Eq(LhsId(3), LhsId(2)), Err(Else)),
|
||||
(Nop, Err(Else)),
|
||||
(Eq(LhsId(4), LhsId(2)), bool_to_match_result(true)),
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -1,807 +0,0 @@
|
||||
//! Convert an AST into its linear equivalent.
|
||||
//!
|
||||
//! Convert each optimization's left-hand side into a linear series of match
|
||||
//! operations. This makes it easy to create an automaton, because automatas
|
||||
//! typically deal with a linear sequence of inputs. The optimization's
|
||||
//! right-hand side is built incrementally inside actions that are taken on
|
||||
//! transitions between match operations.
|
||||
//!
|
||||
//! See `crates/runtime/src/linear.rs` for the linear datatype definitions.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! As an example, if we linearize this optimization:
|
||||
//!
|
||||
//! ```lisp
|
||||
//! (=> (when (imul $x $C)
|
||||
//! (is-power-of-two $C))
|
||||
//! (ishl $x $(log2 C)))
|
||||
//! ```
|
||||
//!
|
||||
//! Then the left-hand side becomes the following linear chain of "matches":
|
||||
//!
|
||||
//! ```ignore
|
||||
//! [
|
||||
//! // ( Match Operation, Expected Value )
|
||||
//! ( Opcode@0, imul ),
|
||||
//! ( IsConst(C), true ),
|
||||
//! ( IsPowerOfTwo(C), true ),
|
||||
//! ]
|
||||
//! ```
|
||||
//!
|
||||
//! And the right-hand side becomes this linear chain of "actions":
|
||||
//!
|
||||
//! ```ignore
|
||||
//! [
|
||||
//! $rhs0 = get lhs @ 0.0 // $x
|
||||
//! $rhs1 = get lhs @ 0.1 // $C
|
||||
//! $rhs2 = eval log2 $rhs1
|
||||
//! $rhs3 = make ishl $rhs0, $rhs2
|
||||
//! ]
|
||||
//! ```
|
||||
//!
|
||||
//! Each match will essentially become a state and a transition out of that
|
||||
//! state in the final automata. The actions record the scope of matches from
|
||||
//! the left-hand side and also incrementally build the right-hand side's
|
||||
//! instructions.
|
||||
//!
|
||||
//! ## General Principles
|
||||
//!
|
||||
//! Here are the general principles that linearization should adhere to:
|
||||
//!
|
||||
//! * Don't match on a subtree until we know it exists. That is, match on
|
||||
//! parents before matching on children.
|
||||
//!
|
||||
//! * Shorter match chains are better! This means fewer tests when matching
|
||||
//! left-hand sides, and a more-compact, more-cache-friendly automata, and
|
||||
//! ultimately, a faster automata.
|
||||
//!
|
||||
//! * An match operation should be a switch rather than a predicate that returns
|
||||
//! a boolean. For example, we switch on an instruction's opcode, rather than
|
||||
//! ask whether this operation is an `imul`. This allows for more prefix
|
||||
//! sharing in the automata, which (again) makes it more compact and more
|
||||
//! cache friendly.
|
||||
//!
|
||||
//! ## Implementation Overview
|
||||
//!
|
||||
//! We emit match operations for a left-hand side's pattern structure, followed
|
||||
//! by match operations for its preconditions on that structure. This ensures
|
||||
//! that anything bound in the pattern is defined before it is used in
|
||||
//! precondition.
|
||||
//!
|
||||
//! Within matching the pattern structure, we emit matching operations in a
|
||||
//! breadth-first traversal of the pattern. This ensures that we've already
|
||||
//! matched an operation before we consider its operands, and therefore we
|
||||
//! already know the operands exist. It also lets us fuse "what opcode does this
|
||||
//! instruction have?" and "define temporary variables for this instruction's
|
||||
//! operands" into a single operation. See `PatternBfs` for details.
|
||||
//!
|
||||
//! As we define the match operations for a pattern, we remember the path where
|
||||
//! each LHS id first occurred. These will later be reused when building the RHS
|
||||
//! actions. See `LhsCanonicalizer` for details.
|
||||
//!
|
||||
//! After we've generated the match operations and expected result of those
|
||||
//! match operations, then we generate the right-hand side actions. The
|
||||
//! right-hand side is built up a post-order traversal, so that operands are
|
||||
//! defined before they are used. See `RhsPostOrder` and `RhsBuilder` for
|
||||
//! details.
|
||||
//!
|
||||
//! Finally, see `linearize_optimization` for the the main AST optimization into
|
||||
//! linear optimization translation function.
|
||||
|
||||
use crate::ast::*;
|
||||
use crate::traversals::{Bfs, Dfs};
|
||||
use peepmatic_runtime::{integer_interner::IntegerInterner, linear};
|
||||
use std::collections::BTreeMap;
|
||||
use std::convert::TryInto;
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
use std::marker::PhantomData;
|
||||
use std::num::NonZeroU32;
|
||||
use wast::Id;
|
||||
|
||||
/// Translate the given AST optimizations into linear optimizations.
|
||||
pub fn linearize<TOperator>(opts: &Optimizations<TOperator>) -> linear::Optimizations<TOperator>
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash + Into<NonZeroU32>,
|
||||
{
|
||||
let mut optimizations = vec![];
|
||||
let mut integers = IntegerInterner::new();
|
||||
for opt in &opts.optimizations {
|
||||
let lin_opt = linearize_optimization(&mut integers, opt);
|
||||
optimizations.push(lin_opt);
|
||||
}
|
||||
linear::Optimizations {
|
||||
optimizations,
|
||||
integers,
|
||||
}
|
||||
}
|
||||
|
||||
/// Translate an AST optimization into a linear optimization!
|
||||
fn linearize_optimization<TOperator>(
|
||||
integers: &mut IntegerInterner,
|
||||
opt: &Optimization<TOperator>,
|
||||
) -> linear::Optimization<TOperator>
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash + Into<NonZeroU32>,
|
||||
{
|
||||
let mut matches: Vec<linear::Match> = vec![];
|
||||
|
||||
let mut lhs_canonicalizer = LhsCanonicalizer::new();
|
||||
|
||||
// We do a breadth-first traversal of the LHS because we don't know whether
|
||||
// a child actually exists to match on until we've matched its parent, and
|
||||
// we don't want to emit matching operations on things that might not exist!
|
||||
for (id, pattern) in PatternBfs::new(&opt.lhs.pattern) {
|
||||
// Create the matching parts of an `Match` for this part of the
|
||||
// pattern.
|
||||
let (operation, expected) = pattern.to_linear_match_op(integers, &lhs_canonicalizer, id);
|
||||
matches.push(linear::Match {
|
||||
operation,
|
||||
expected,
|
||||
});
|
||||
|
||||
lhs_canonicalizer.remember_linear_id(pattern, id);
|
||||
|
||||
// Some operations require type ascriptions for us to infer the correct
|
||||
// bit width of their results: `ireduce`, `sextend`, `uextend`, etc.
|
||||
// When there is such a type ascription in the pattern, insert another
|
||||
// match that checks the instruction-being-matched's bit width.
|
||||
if let Pattern::Operation(Operation { r#type, .. }) = pattern {
|
||||
if let Some(w) = r#type.get().and_then(|ty| ty.bit_width.fixed_width()) {
|
||||
debug_assert!(w != 0, "All fixed-width bit widths are non-zero");
|
||||
let expected = Ok(unsafe { NonZeroU32::new_unchecked(w as u32) });
|
||||
|
||||
matches.push(linear::Match {
|
||||
operation: linear::MatchOp::BitWidth(id),
|
||||
expected,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we've added all the matches for the LHS pattern, add the
|
||||
// matches for its preconditions.
|
||||
for pre in &opt.lhs.preconditions {
|
||||
matches.push(pre.to_linear_match(&lhs_canonicalizer));
|
||||
}
|
||||
|
||||
assert!(!matches.is_empty());
|
||||
|
||||
// Finally, generate the RHS-building actions and attach them to the first match.
|
||||
let mut rhs_builder = RhsBuilder::new(&opt.rhs);
|
||||
let mut actions = vec![];
|
||||
rhs_builder.add_rhs_build_actions(integers, &lhs_canonicalizer, &mut actions);
|
||||
|
||||
linear::Optimization { matches, actions }
|
||||
}
|
||||
|
||||
/// A post-order, depth-first traversal of right-hand sides.
|
||||
///
|
||||
/// Does not maintain any extra state about the traversal, such as where in the
|
||||
/// tree each yielded node comes from.
|
||||
struct RhsPostOrder<'a, TOperator> {
|
||||
dfs: Dfs<'a, TOperator>,
|
||||
}
|
||||
|
||||
impl<'a, TOperator> RhsPostOrder<'a, TOperator>
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
fn new(rhs: &'a Rhs<'a, TOperator>) -> Self {
|
||||
Self { dfs: Dfs::new(rhs) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TOperator> Iterator for RhsPostOrder<'a, TOperator>
|
||||
where
|
||||
TOperator: Copy,
|
||||
{
|
||||
type Item = &'a Rhs<'a, TOperator>;
|
||||
|
||||
fn next(&mut self) -> Option<&'a Rhs<'a, TOperator>> {
|
||||
use crate::traversals::TraversalEvent as TE;
|
||||
loop {
|
||||
match self.dfs.next()? {
|
||||
(TE::Exit, DynAstRef::Rhs(rhs)) => return Some(rhs),
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A breadth-first traversal of left-hand side patterns.
|
||||
///
|
||||
/// Keeps track of the `LhsId` of each pattern, and yields it along side the
|
||||
/// pattern AST node.
|
||||
///
|
||||
/// We use a breadth-first traversal because we fuse "which opcode is this?" and
|
||||
/// "assign operands to temporaries" into a single linear match operation. A
|
||||
/// breadth-first traversal aligns with "match this opcode, and on success bind
|
||||
/// all of its operands to temporaries". Fusing these operations into one is
|
||||
/// important for attaining similar performance as an open-coded Rust `match`
|
||||
/// expression, which would also fuse these operations via pattern matching.
|
||||
struct PatternBfs<'a, TOperator> {
|
||||
next_id: u16,
|
||||
bfs: Bfs<'a, TOperator>,
|
||||
}
|
||||
|
||||
impl<'a, TOperator> PatternBfs<'a, TOperator>
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
fn new(pattern: &'a Pattern<'a, TOperator>) -> Self {
|
||||
Self {
|
||||
next_id: 0,
|
||||
bfs: Bfs::new(pattern),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TOperator> Iterator for PatternBfs<'a, TOperator>
|
||||
where
|
||||
TOperator: 'a + Copy + Debug + Eq + Hash,
|
||||
{
|
||||
type Item = (linear::LhsId, &'a Pattern<'a, TOperator>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
if let DynAstRef::Pattern(pattern) = self.bfs.next()? {
|
||||
let id = linear::LhsId(self.next_id);
|
||||
self.next_id = self.next_id.checked_add(1).unwrap();
|
||||
return Some((id, pattern));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A map from left-hand side identifiers to the path in the left-hand side
|
||||
/// where they first occurred.
|
||||
struct LhsCanonicalizer<'a, TOperator> {
|
||||
id_to_linear: BTreeMap<&'a str, linear::LhsId>,
|
||||
_marker: PhantomData<&'a TOperator>,
|
||||
}
|
||||
|
||||
impl<'a, TOperator> LhsCanonicalizer<'a, TOperator> {
|
||||
/// Construct a new, empty `LhsCanonicalizer`.
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
id_to_linear: Default::default(),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the canonical `linear::LhsId` for the given variable, if any.
|
||||
fn get(&self, id: &Id) -> Option<linear::LhsId> {
|
||||
self.id_to_linear.get(id.name()).copied()
|
||||
}
|
||||
|
||||
/// Remember the canonical `linear::LhsId` for any variables or constants
|
||||
/// used in the given pattern.
|
||||
fn remember_linear_id(
|
||||
&mut self,
|
||||
pattern: &'a Pattern<'a, TOperator>,
|
||||
linear_id: linear::LhsId,
|
||||
) {
|
||||
match pattern {
|
||||
// If this is the first time we've seen an identifier defined on the
|
||||
// left-hand side, remember it.
|
||||
Pattern::Variable(Variable { id, .. }) | Pattern::Constant(Constant { id, .. }) => {
|
||||
self.id_to_linear.entry(id.name()).or_insert(linear_id);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An `RhsBuilder` emits the actions for building the right-hand side
|
||||
/// instructions.
|
||||
struct RhsBuilder<'a, TOperator> {
|
||||
// We do a post order traversal of the RHS because an RHS instruction cannot
|
||||
// be created until after all of its operands are created.
|
||||
rhs_post_order: RhsPostOrder<'a, TOperator>,
|
||||
|
||||
// A map from a right-hand side's span to its `linear::RhsId`. This is used
|
||||
// by RHS-construction actions to reference operands. In practice the
|
||||
// `RhsId` is roughly equivalent to its index in the post-order traversal of
|
||||
// the RHS.
|
||||
rhs_span_to_id: BTreeMap<wast::Span, linear::RhsId>,
|
||||
}
|
||||
|
||||
impl<'a, TOperator> RhsBuilder<'a, TOperator>
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
/// Create a new builder for the given right-hand side.
|
||||
fn new(rhs: &'a Rhs<'a, TOperator>) -> Self {
|
||||
let rhs_post_order = RhsPostOrder::new(rhs);
|
||||
let rhs_span_to_id = Default::default();
|
||||
Self {
|
||||
rhs_post_order,
|
||||
rhs_span_to_id,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the `linear::RhsId` for the given right-hand side.
|
||||
///
|
||||
/// ## Panics
|
||||
///
|
||||
/// Panics if we haven't already emitted the action for building this RHS's
|
||||
/// instruction.
|
||||
fn get_rhs_id(&self, rhs: &Rhs<TOperator>) -> linear::RhsId {
|
||||
self.rhs_span_to_id[&rhs.span()]
|
||||
}
|
||||
|
||||
/// Create actions for building up this right-hand side of an optimization.
|
||||
///
|
||||
/// Because we are walking the right-hand side with a post-order traversal,
|
||||
/// we know that we already created an instruction's operands that are
|
||||
/// defined in the right-hand side, before we get to the parent instruction.
|
||||
fn add_rhs_build_actions(
|
||||
&mut self,
|
||||
integers: &mut IntegerInterner,
|
||||
lhs_canonicalizer: &LhsCanonicalizer<TOperator>,
|
||||
actions: &mut Vec<linear::Action<TOperator>>,
|
||||
) {
|
||||
while let Some(rhs) = self.rhs_post_order.next() {
|
||||
actions.push(self.rhs_to_linear_action(integers, lhs_canonicalizer, rhs));
|
||||
let id = linear::RhsId(self.rhs_span_to_id.len().try_into().unwrap());
|
||||
self.rhs_span_to_id.insert(rhs.span(), id);
|
||||
}
|
||||
}
|
||||
|
||||
fn rhs_to_linear_action(
|
||||
&self,
|
||||
integers: &mut IntegerInterner,
|
||||
lhs_canonicalizer: &LhsCanonicalizer<TOperator>,
|
||||
rhs: &Rhs<TOperator>,
|
||||
) -> linear::Action<TOperator> {
|
||||
match rhs {
|
||||
Rhs::ValueLiteral(ValueLiteral::Integer(i)) => linear::Action::MakeIntegerConst {
|
||||
value: integers.intern(i.value as u64),
|
||||
bit_width: i
|
||||
.bit_width
|
||||
.get()
|
||||
.expect("should be initialized after type checking"),
|
||||
},
|
||||
Rhs::ValueLiteral(ValueLiteral::Boolean(b)) => linear::Action::MakeBooleanConst {
|
||||
value: b.value,
|
||||
bit_width: b
|
||||
.bit_width
|
||||
.get()
|
||||
.expect("should be initialized after type checking"),
|
||||
},
|
||||
Rhs::ValueLiteral(ValueLiteral::ConditionCode(ConditionCode { cc, .. })) => {
|
||||
linear::Action::MakeConditionCode { cc: *cc }
|
||||
}
|
||||
Rhs::Variable(Variable { id, .. }) | Rhs::Constant(Constant { id, .. }) => {
|
||||
let lhs = lhs_canonicalizer.get(id).unwrap();
|
||||
linear::Action::GetLhs { lhs }
|
||||
}
|
||||
Rhs::Unquote(unq) => match unq.operands.len() {
|
||||
1 => linear::Action::UnaryUnquote {
|
||||
operator: unq.operator,
|
||||
operand: self.get_rhs_id(&unq.operands[0]),
|
||||
},
|
||||
2 => linear::Action::BinaryUnquote {
|
||||
operator: unq.operator,
|
||||
operands: [
|
||||
self.get_rhs_id(&unq.operands[0]),
|
||||
self.get_rhs_id(&unq.operands[1]),
|
||||
],
|
||||
},
|
||||
n => unreachable!("no unquote operators of arity {}", n),
|
||||
},
|
||||
Rhs::Operation(op) => match op.operands.len() {
|
||||
1 => linear::Action::MakeUnaryInst {
|
||||
operator: op.operator,
|
||||
r#type: op
|
||||
.r#type
|
||||
.get()
|
||||
.expect("should be initialized after type checking"),
|
||||
operand: self.get_rhs_id(&op.operands[0]),
|
||||
},
|
||||
2 => linear::Action::MakeBinaryInst {
|
||||
operator: op.operator,
|
||||
r#type: op
|
||||
.r#type
|
||||
.get()
|
||||
.expect("should be initialized after type checking"),
|
||||
operands: [
|
||||
self.get_rhs_id(&op.operands[0]),
|
||||
self.get_rhs_id(&op.operands[1]),
|
||||
],
|
||||
},
|
||||
3 => linear::Action::MakeTernaryInst {
|
||||
operator: op.operator,
|
||||
r#type: op
|
||||
.r#type
|
||||
.get()
|
||||
.expect("should be initialized after type checking"),
|
||||
operands: [
|
||||
self.get_rhs_id(&op.operands[0]),
|
||||
self.get_rhs_id(&op.operands[1]),
|
||||
self.get_rhs_id(&op.operands[2]),
|
||||
],
|
||||
},
|
||||
n => unreachable!("no instructions of arity {}", n),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<TOperator> Precondition<'_, TOperator>
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash + Into<NonZeroU32>,
|
||||
{
|
||||
/// Convert this precondition into a `linear::Match`.
|
||||
fn to_linear_match(&self, lhs_canonicalizer: &LhsCanonicalizer<TOperator>) -> linear::Match {
|
||||
match self.constraint {
|
||||
Constraint::IsPowerOfTwo => {
|
||||
let id = match &self.operands[0] {
|
||||
ConstraintOperand::Constant(Constant { id, .. }) => id,
|
||||
_ => unreachable!("checked in verification"),
|
||||
};
|
||||
let id = lhs_canonicalizer.get(&id).unwrap();
|
||||
linear::Match {
|
||||
operation: linear::MatchOp::IsPowerOfTwo(id),
|
||||
expected: linear::bool_to_match_result(true),
|
||||
}
|
||||
}
|
||||
Constraint::BitWidth => {
|
||||
let id = match &self.operands[0] {
|
||||
ConstraintOperand::Constant(Constant { id, .. })
|
||||
| ConstraintOperand::Variable(Variable { id, .. }) => id,
|
||||
_ => unreachable!("checked in verification"),
|
||||
};
|
||||
let id = lhs_canonicalizer.get(&id).unwrap();
|
||||
|
||||
let width = match &self.operands[1] {
|
||||
ConstraintOperand::ValueLiteral(ValueLiteral::Integer(Integer {
|
||||
value,
|
||||
..
|
||||
})) => *value,
|
||||
_ => unreachable!("checked in verification"),
|
||||
};
|
||||
|
||||
assert!(0 < width && width <= 128);
|
||||
assert!((width as u8).is_power_of_two());
|
||||
let expected = Ok(unsafe { NonZeroU32::new_unchecked(width as u32) });
|
||||
|
||||
linear::Match {
|
||||
operation: linear::MatchOp::BitWidth(id),
|
||||
expected,
|
||||
}
|
||||
}
|
||||
Constraint::FitsInNativeWord => {
|
||||
let id = match &self.operands[0] {
|
||||
ConstraintOperand::Constant(Constant { id, .. })
|
||||
| ConstraintOperand::Variable(Variable { id, .. }) => id,
|
||||
_ => unreachable!("checked in verification"),
|
||||
};
|
||||
let id = lhs_canonicalizer.get(&id).unwrap();
|
||||
linear::Match {
|
||||
operation: linear::MatchOp::FitsInNativeWord(id),
|
||||
expected: linear::bool_to_match_result(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<TOperator> Pattern<'_, TOperator>
|
||||
where
|
||||
TOperator: Copy,
|
||||
{
|
||||
/// Convert this pattern into its linear match operation and the expected
|
||||
/// result of that operation.
|
||||
///
|
||||
/// NB: these mappings to expected values need to stay sync'd with the
|
||||
/// runtime!
|
||||
fn to_linear_match_op(
|
||||
&self,
|
||||
integers: &mut IntegerInterner,
|
||||
lhs_canonicalizer: &LhsCanonicalizer<TOperator>,
|
||||
linear_lhs_id: linear::LhsId,
|
||||
) -> (linear::MatchOp, linear::MatchResult)
|
||||
where
|
||||
TOperator: Into<NonZeroU32>,
|
||||
{
|
||||
match self {
|
||||
Pattern::ValueLiteral(ValueLiteral::Integer(Integer { value, .. })) => (
|
||||
linear::MatchOp::IntegerValue(linear_lhs_id),
|
||||
Ok(integers.intern(*value as u64).into()),
|
||||
),
|
||||
Pattern::ValueLiteral(ValueLiteral::Boolean(Boolean { value, .. })) => (
|
||||
linear::MatchOp::BooleanValue(linear_lhs_id),
|
||||
linear::bool_to_match_result(*value),
|
||||
),
|
||||
Pattern::ValueLiteral(ValueLiteral::ConditionCode(ConditionCode { cc, .. })) => {
|
||||
let cc = *cc as u32;
|
||||
debug_assert!(cc != 0, "no `ConditionCode` variants are zero");
|
||||
let expected = Ok(unsafe { NonZeroU32::new_unchecked(cc) });
|
||||
(linear::MatchOp::ConditionCode(linear_lhs_id), expected)
|
||||
}
|
||||
Pattern::Constant(Constant { id, .. }) => {
|
||||
if let Some(linear_lhs_id2) = lhs_canonicalizer.get(id) {
|
||||
debug_assert!(linear_lhs_id != linear_lhs_id2);
|
||||
(
|
||||
linear::MatchOp::Eq(linear_lhs_id, linear_lhs_id2),
|
||||
linear::bool_to_match_result(true),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
linear::MatchOp::IsConst(linear_lhs_id),
|
||||
linear::bool_to_match_result(true),
|
||||
)
|
||||
}
|
||||
}
|
||||
Pattern::Variable(Variable { id, .. }) => {
|
||||
if let Some(linear_lhs_id2) = lhs_canonicalizer.get(id) {
|
||||
debug_assert!(linear_lhs_id != linear_lhs_id2);
|
||||
(
|
||||
linear::MatchOp::Eq(linear_lhs_id, linear_lhs_id2),
|
||||
linear::bool_to_match_result(true),
|
||||
)
|
||||
} else {
|
||||
(linear::MatchOp::Nop, Err(linear::Else))
|
||||
}
|
||||
}
|
||||
Pattern::Operation(op) => {
|
||||
let expected = Ok(op.operator.into());
|
||||
(linear::MatchOp::Opcode(linear_lhs_id), expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use peepmatic_runtime::{
|
||||
integer_interner::IntegerId,
|
||||
linear::{bool_to_match_result, Action::*, Else, LhsId, MatchOp::*, RhsId},
|
||||
r#type::{BitWidth, Kind, Type},
|
||||
unquote::UnquoteOperator,
|
||||
};
|
||||
use peepmatic_test_operator::TestOperator;
|
||||
|
||||
macro_rules! linearizes_to {
|
||||
($name:ident, $source:expr, $make_expected:expr $(,)* ) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let buf = wast::parser::ParseBuffer::new($source).expect("should lex OK");
|
||||
|
||||
let opts = match wast::parser::parse::<Optimizations<TestOperator>>(&buf) {
|
||||
Ok(opts) => opts,
|
||||
Err(mut e) => {
|
||||
e.set_path(std::path::Path::new(stringify!($name)));
|
||||
e.set_text($source);
|
||||
eprintln!("{}", e);
|
||||
panic!("should parse OK")
|
||||
}
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
opts.optimizations.len(),
|
||||
1,
|
||||
"`linearizes_to!` only supports a single optimization; split the big test into \
|
||||
multiple small tests"
|
||||
);
|
||||
|
||||
if let Err(mut e) = crate::verify(&opts) {
|
||||
e.set_path(std::path::Path::new(stringify!($name)));
|
||||
e.set_text($source);
|
||||
eprintln!("{}", e);
|
||||
panic!("should verify OK")
|
||||
}
|
||||
|
||||
let mut integers = IntegerInterner::new();
|
||||
let mut i = |i: u64| integers.intern(i);
|
||||
|
||||
#[allow(unused_variables)]
|
||||
let make_expected: fn(
|
||||
&mut dyn FnMut(u64) -> IntegerId,
|
||||
) -> (Vec<linear::Match>, Vec<linear::Action<_>>) = $make_expected;
|
||||
|
||||
let expected = make_expected(&mut i);
|
||||
let actual = linearize_optimization(&mut integers, &opts.optimizations[0]);
|
||||
assert_eq!(expected.0, actual.matches);
|
||||
assert_eq!(expected.1, actual.actions);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
linearizes_to!(
|
||||
mul_by_pow2_into_shift,
|
||||
"
|
||||
(=> (when (imul $x $C)
|
||||
(is-power-of-two $C))
|
||||
(ishl $x $(log2 $C)))
|
||||
",
|
||||
|i| (
|
||||
vec![
|
||||
linear::Match {
|
||||
operation: Opcode(LhsId(0)),
|
||||
expected: Ok(TestOperator::Imul.into()),
|
||||
},
|
||||
linear::Match {
|
||||
operation: Nop,
|
||||
expected: Err(Else),
|
||||
},
|
||||
linear::Match {
|
||||
operation: IsConst(LhsId(2)),
|
||||
expected: bool_to_match_result(true),
|
||||
},
|
||||
linear::Match {
|
||||
operation: IsPowerOfTwo(LhsId(2)),
|
||||
expected: bool_to_match_result(true),
|
||||
},
|
||||
],
|
||||
vec![
|
||||
GetLhs { lhs: LhsId(1) },
|
||||
GetLhs { lhs: LhsId(2) },
|
||||
UnaryUnquote {
|
||||
operator: UnquoteOperator::Log2,
|
||||
operand: RhsId(1)
|
||||
},
|
||||
MakeBinaryInst {
|
||||
operator: TestOperator::Ishl,
|
||||
r#type: Type {
|
||||
kind: Kind::Int,
|
||||
bit_width: BitWidth::Polymorphic
|
||||
},
|
||||
operands: [RhsId(0), RhsId(2)]
|
||||
}
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
linearizes_to!(variable_pattern_id_optimization, "(=> $x $x)", |i| (
|
||||
vec![linear::Match {
|
||||
operation: Nop,
|
||||
expected: Err(Else),
|
||||
}],
|
||||
vec![GetLhs { lhs: LhsId(0) }],
|
||||
));
|
||||
|
||||
linearizes_to!(constant_pattern_id_optimization, "(=> $C $C)", |i| (
|
||||
vec![linear::Match {
|
||||
operation: IsConst(LhsId(0)),
|
||||
expected: bool_to_match_result(true),
|
||||
}],
|
||||
vec![GetLhs { lhs: LhsId(0) }],
|
||||
));
|
||||
|
||||
linearizes_to!(boolean_literal_id_optimization, "(=> true true)", |i| (
|
||||
vec![linear::Match {
|
||||
operation: BooleanValue(LhsId(0)),
|
||||
expected: bool_to_match_result(true),
|
||||
}],
|
||||
vec![MakeBooleanConst {
|
||||
value: true,
|
||||
bit_width: BitWidth::Polymorphic,
|
||||
}],
|
||||
));
|
||||
|
||||
linearizes_to!(number_literal_id_optimization, "(=> 5 5)", |i| (
|
||||
vec![linear::Match {
|
||||
operation: IntegerValue(LhsId(0)),
|
||||
expected: Ok(i(5).into()),
|
||||
}],
|
||||
vec![MakeIntegerConst {
|
||||
value: i(5),
|
||||
bit_width: BitWidth::Polymorphic,
|
||||
}],
|
||||
));
|
||||
|
||||
linearizes_to!(
|
||||
operation_id_optimization,
|
||||
"(=> (iconst $C) (iconst $C))",
|
||||
|i| (
|
||||
vec![
|
||||
linear::Match {
|
||||
operation: Opcode(LhsId(0)),
|
||||
expected: Ok(TestOperator::Iconst.into()),
|
||||
},
|
||||
linear::Match {
|
||||
operation: IsConst(LhsId(1)),
|
||||
expected: bool_to_match_result(true),
|
||||
},
|
||||
],
|
||||
vec![
|
||||
GetLhs { lhs: LhsId(1) },
|
||||
MakeUnaryInst {
|
||||
operator: TestOperator::Iconst,
|
||||
r#type: Type {
|
||||
kind: Kind::Int,
|
||||
bit_width: BitWidth::Polymorphic,
|
||||
},
|
||||
operand: RhsId(0),
|
||||
},
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
linearizes_to!(
|
||||
redundant_bor,
|
||||
"(=> (bor $x (bor $x $y)) (bor $x $y))",
|
||||
|i| (
|
||||
vec![
|
||||
linear::Match {
|
||||
operation: Opcode(LhsId(0)),
|
||||
expected: Ok(TestOperator::Bor.into()),
|
||||
},
|
||||
linear::Match {
|
||||
operation: Nop,
|
||||
expected: Err(Else),
|
||||
},
|
||||
linear::Match {
|
||||
operation: Opcode(LhsId(2)),
|
||||
expected: Ok(TestOperator::Bor.into()),
|
||||
},
|
||||
linear::Match {
|
||||
operation: Eq(LhsId(3), LhsId(1)),
|
||||
expected: bool_to_match_result(true),
|
||||
},
|
||||
linear::Match {
|
||||
operation: Nop,
|
||||
expected: Err(Else),
|
||||
},
|
||||
],
|
||||
vec![
|
||||
GetLhs { lhs: LhsId(1) },
|
||||
GetLhs { lhs: LhsId(4) },
|
||||
MakeBinaryInst {
|
||||
operator: TestOperator::Bor,
|
||||
r#type: Type {
|
||||
kind: Kind::Int,
|
||||
bit_width: BitWidth::Polymorphic,
|
||||
},
|
||||
operands: [RhsId(0), RhsId(1)],
|
||||
},
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
linearizes_to!(
|
||||
large_integers,
|
||||
// u64::MAX
|
||||
"(=> 18446744073709551615 0)",
|
||||
|i| (
|
||||
vec![linear::Match {
|
||||
operation: IntegerValue(LhsId(0)),
|
||||
expected: Ok(i(std::u64::MAX).into()),
|
||||
}],
|
||||
vec![MakeIntegerConst {
|
||||
value: i(0),
|
||||
bit_width: BitWidth::Polymorphic,
|
||||
}],
|
||||
),
|
||||
);
|
||||
|
||||
linearizes_to!(
|
||||
ireduce_with_type_ascription,
|
||||
"(=> (ireduce{i32} $x) 0)",
|
||||
|i| (
|
||||
vec![
|
||||
linear::Match {
|
||||
operation: Opcode(LhsId(0)),
|
||||
expected: Ok(TestOperator::Ireduce.into()),
|
||||
},
|
||||
linear::Match {
|
||||
operation: linear::MatchOp::BitWidth(LhsId(0)),
|
||||
expected: Ok(NonZeroU32::new(32).unwrap()),
|
||||
},
|
||||
linear::Match {
|
||||
operation: Nop,
|
||||
expected: Err(Else),
|
||||
},
|
||||
],
|
||||
vec![MakeIntegerConst {
|
||||
value: i(0),
|
||||
bit_width: BitWidth::ThirtyTwo,
|
||||
}],
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1,981 +0,0 @@
|
||||
/*!
|
||||
|
||||
This module implements parsing the DSL text format. It implements the
|
||||
`wast::Parse` trait for all of our AST types.
|
||||
|
||||
The grammar for the DSL is given below:
|
||||
|
||||
```ebnf
|
||||
<optimizations> ::= <optimization>*
|
||||
|
||||
<optimization> ::= '(' '=>' <lhs> <rhs> ')'
|
||||
|
||||
<left-hand-side> ::= <pattern>
|
||||
| '(' 'when' <pattern> <precondition>* ')'
|
||||
|
||||
<pattern> ::= <value-literal>
|
||||
| <constant>
|
||||
| <operation<pattern>>
|
||||
| <variable>
|
||||
|
||||
<value-literal> ::= <integer>
|
||||
| <boolean>
|
||||
|
||||
<boolean> ::= 'true' | 'false'
|
||||
|
||||
<operation<T>> ::= '(' <operator> [<type-ascription>] <T>* ')'
|
||||
|
||||
<precondition> ::= '(' <constraint> <constraint-operands>* ')'
|
||||
|
||||
<constraint-operand> ::= <value-literal>
|
||||
| <constant>
|
||||
| <variable>
|
||||
|
||||
<rhs> ::= <value-literal>
|
||||
| <constant>
|
||||
| <variable>
|
||||
| <unquote>
|
||||
| <operation<rhs>>
|
||||
|
||||
<unquote> ::= '$' '(' <unquote-operator> <unquote-operand>* ')'
|
||||
|
||||
<unquote-operand> ::= <value-literal>
|
||||
| <constant>
|
||||
```
|
||||
|
||||
*/
|
||||
|
||||
use crate::ast::*;
|
||||
use peepmatic_runtime::r#type::Type;
|
||||
use std::cell::Cell;
|
||||
use std::marker::PhantomData;
|
||||
use wast::{
|
||||
parser::{Cursor, Parse, Parser, Peek, Result as ParseResult},
|
||||
Id, LParen,
|
||||
};
|
||||
|
||||
mod tok {
|
||||
use wast::{custom_keyword, custom_reserved};
|
||||
|
||||
custom_keyword!(bit_width = "bit-width");
|
||||
custom_reserved!(dollar = "$");
|
||||
custom_keyword!(r#false = "false");
|
||||
custom_keyword!(fits_in_native_word = "fits-in-native-word");
|
||||
custom_keyword!(is_power_of_two = "is-power-of-two");
|
||||
custom_reserved!(left_curly = "{");
|
||||
custom_keyword!(log2);
|
||||
custom_keyword!(neg);
|
||||
custom_reserved!(replace = "=>");
|
||||
custom_reserved!(right_curly = "}");
|
||||
custom_keyword!(r#true = "true");
|
||||
custom_keyword!(when);
|
||||
|
||||
custom_keyword!(eq);
|
||||
custom_keyword!(ne);
|
||||
custom_keyword!(slt);
|
||||
custom_keyword!(ult);
|
||||
custom_keyword!(sge);
|
||||
custom_keyword!(uge);
|
||||
custom_keyword!(sgt);
|
||||
custom_keyword!(ugt);
|
||||
custom_keyword!(sle);
|
||||
custom_keyword!(ule);
|
||||
custom_keyword!(of);
|
||||
custom_keyword!(nof);
|
||||
}
|
||||
|
||||
impl<'a, TOperator> Parse<'a> for Optimizations<'a, TOperator>
|
||||
where
|
||||
TOperator: Parse<'a>,
|
||||
{
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
let span = p.cur_span();
|
||||
let mut optimizations = vec![];
|
||||
while !p.is_empty() {
|
||||
optimizations.push(p.parse()?);
|
||||
}
|
||||
Ok(Optimizations {
|
||||
span,
|
||||
optimizations,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TOperator> Parse<'a> for Optimization<'a, TOperator>
|
||||
where
|
||||
TOperator: Parse<'a>,
|
||||
{
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
let span = p.cur_span();
|
||||
p.parens(|p| {
|
||||
p.parse::<tok::replace>()?;
|
||||
let lhs = p.parse()?;
|
||||
let rhs = p.parse()?;
|
||||
Ok(Optimization { span, lhs, rhs })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TOperator> Parse<'a> for Lhs<'a, TOperator>
|
||||
where
|
||||
TOperator: Parse<'a>,
|
||||
{
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
let span = p.cur_span();
|
||||
let mut preconditions = vec![];
|
||||
if p.peek::<wast::LParen>() && p.peek2::<tok::when>() {
|
||||
p.parens(|p| {
|
||||
p.parse::<tok::when>()?;
|
||||
let pattern = p.parse()?;
|
||||
while p.peek::<LParen>() {
|
||||
preconditions.push(p.parse()?);
|
||||
}
|
||||
Ok(Lhs {
|
||||
span,
|
||||
pattern,
|
||||
preconditions,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
let span = p.cur_span();
|
||||
let pattern = p.parse()?;
|
||||
Ok(Lhs {
|
||||
span,
|
||||
pattern,
|
||||
preconditions,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TOperator> Parse<'a> for Pattern<'a, TOperator>
|
||||
where
|
||||
TOperator: Parse<'a>,
|
||||
{
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
if p.peek::<ValueLiteral<TOperator>>() {
|
||||
return Ok(Pattern::ValueLiteral(p.parse()?));
|
||||
}
|
||||
if p.peek::<Constant<TOperator>>() {
|
||||
return Ok(Pattern::Constant(p.parse()?));
|
||||
}
|
||||
if p.peek::<Operation<TOperator, Self>>() {
|
||||
return Ok(Pattern::Operation(p.parse()?));
|
||||
}
|
||||
if p.peek::<Variable<TOperator>>() {
|
||||
return Ok(Pattern::Variable(p.parse()?));
|
||||
}
|
||||
Err(p.error("expected a left-hand side pattern"))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TOperator> Peek for Pattern<'a, TOperator>
|
||||
where
|
||||
TOperator: 'a,
|
||||
{
|
||||
fn peek(c: Cursor) -> bool {
|
||||
ValueLiteral::<TOperator>::peek(c)
|
||||
|| Constant::<TOperator>::peek(c)
|
||||
|| Variable::<TOperator>::peek(c)
|
||||
|| Operation::<TOperator, Self>::peek(c)
|
||||
}
|
||||
|
||||
fn display() -> &'static str {
|
||||
"left-hand side pattern"
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TOperator> Parse<'a> for ValueLiteral<'a, TOperator> {
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
if let Ok(b) = p.parse::<Boolean<TOperator>>() {
|
||||
return Ok(ValueLiteral::Boolean(b));
|
||||
}
|
||||
if let Ok(i) = p.parse::<Integer<TOperator>>() {
|
||||
return Ok(ValueLiteral::Integer(i));
|
||||
}
|
||||
if let Ok(cc) = p.parse::<ConditionCode<TOperator>>() {
|
||||
return Ok(ValueLiteral::ConditionCode(cc));
|
||||
}
|
||||
Err(p.error("expected an integer or boolean or condition code literal"))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TOperator> Peek for ValueLiteral<'a, TOperator> {
|
||||
fn peek(c: Cursor) -> bool {
|
||||
c.integer().is_some()
|
||||
|| Boolean::<TOperator>::peek(c)
|
||||
|| ConditionCode::<TOperator>::peek(c)
|
||||
}
|
||||
|
||||
fn display() -> &'static str {
|
||||
"value literal"
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TOperator> Parse<'a> for Integer<'a, TOperator> {
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
let span = p.cur_span();
|
||||
p.step(|c| {
|
||||
if let Some((i, rest)) = c.integer() {
|
||||
let (s, base) = i.val();
|
||||
let val = i64::from_str_radix(s, base)
|
||||
.or_else(|_| u128::from_str_radix(s, base).map(|i| i as i64));
|
||||
return match val {
|
||||
Ok(value) => Ok((
|
||||
Integer {
|
||||
span,
|
||||
value,
|
||||
bit_width: Default::default(),
|
||||
marker: PhantomData,
|
||||
},
|
||||
rest,
|
||||
)),
|
||||
Err(_) => Err(c.error("invalid integer: out of range")),
|
||||
};
|
||||
}
|
||||
Err(c.error("expected an integer"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TOperator> Parse<'a> for Boolean<'a, TOperator> {
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
let span = p.cur_span();
|
||||
if p.parse::<tok::r#true>().is_ok() {
|
||||
return Ok(Boolean {
|
||||
span,
|
||||
value: true,
|
||||
bit_width: Default::default(),
|
||||
marker: PhantomData,
|
||||
});
|
||||
}
|
||||
if p.parse::<tok::r#false>().is_ok() {
|
||||
return Ok(Boolean {
|
||||
span,
|
||||
value: false,
|
||||
bit_width: Default::default(),
|
||||
marker: PhantomData,
|
||||
});
|
||||
}
|
||||
Err(p.error("expected `true` or `false`"))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TOperator> Peek for Boolean<'a, TOperator> {
|
||||
fn peek(c: Cursor) -> bool {
|
||||
<tok::r#true as Peek>::peek(c) || <tok::r#false as Peek>::peek(c)
|
||||
}
|
||||
|
||||
fn display() -> &'static str {
|
||||
"boolean `true` or `false`"
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TOperator> Parse<'a> for ConditionCode<'a, TOperator> {
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
let span = p.cur_span();
|
||||
|
||||
macro_rules! parse_cc {
|
||||
( $( $token:ident => $cc:ident, )* ) => {
|
||||
$(
|
||||
if p.peek::<tok::$token>() {
|
||||
p.parse::<tok::$token>()?;
|
||||
return Ok(Self {
|
||||
span,
|
||||
cc: peepmatic_runtime::cc::ConditionCode::$cc,
|
||||
marker: PhantomData,
|
||||
});
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
parse_cc! {
|
||||
eq => Eq,
|
||||
ne => Ne,
|
||||
slt => Slt,
|
||||
ult => Ult,
|
||||
sge => Sge,
|
||||
uge => Uge,
|
||||
sgt => Sgt,
|
||||
ugt => Ugt,
|
||||
sle => Sle,
|
||||
ule => Ule,
|
||||
of => Of,
|
||||
nof => Nof,
|
||||
}
|
||||
|
||||
Err(p.error("expected a condition code"))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TOperator> Peek for ConditionCode<'a, TOperator> {
|
||||
fn peek(c: Cursor) -> bool {
|
||||
macro_rules! peek_cc {
|
||||
( $( $token:ident, )* ) => {
|
||||
false $( || <tok::$token as Peek>::peek(c) )*
|
||||
}
|
||||
}
|
||||
|
||||
peek_cc! {
|
||||
eq,
|
||||
ne,
|
||||
slt,
|
||||
ult,
|
||||
sge,
|
||||
uge,
|
||||
sgt,
|
||||
ugt,
|
||||
sle,
|
||||
ule,
|
||||
of,
|
||||
nof,
|
||||
}
|
||||
}
|
||||
|
||||
fn display() -> &'static str {
|
||||
"condition code"
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TOperator> Parse<'a> for Constant<'a, TOperator> {
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
let span = p.cur_span();
|
||||
let id = Id::parse(p)?;
|
||||
if id
|
||||
.name()
|
||||
.chars()
|
||||
.all(|c| !c.is_alphabetic() || c.is_uppercase())
|
||||
{
|
||||
Ok(Constant {
|
||||
span,
|
||||
id,
|
||||
marker: PhantomData,
|
||||
})
|
||||
} else {
|
||||
let upper = id
|
||||
.name()
|
||||
.chars()
|
||||
.flat_map(|c| c.to_uppercase())
|
||||
.collect::<String>();
|
||||
Err(p.error(format!(
|
||||
"symbolic constants must start with an upper-case letter like ${}",
|
||||
upper
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TOperator> Peek for Constant<'a, TOperator> {
|
||||
fn peek(c: Cursor) -> bool {
|
||||
if let Some((id, _rest)) = c.id() {
|
||||
id.chars().all(|c| !c.is_alphabetic() || c.is_uppercase())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn display() -> &'static str {
|
||||
"symbolic constant"
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TOperator> Parse<'a> for Variable<'a, TOperator> {
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
let span = p.cur_span();
|
||||
let id = Id::parse(p)?;
|
||||
if id
|
||||
.name()
|
||||
.chars()
|
||||
.all(|c| !c.is_alphabetic() || c.is_lowercase())
|
||||
{
|
||||
Ok(Variable {
|
||||
span,
|
||||
id,
|
||||
marker: PhantomData,
|
||||
})
|
||||
} else {
|
||||
let lower = id
|
||||
.name()
|
||||
.chars()
|
||||
.flat_map(|c| c.to_lowercase())
|
||||
.collect::<String>();
|
||||
Err(p.error(format!(
|
||||
"variables must start with an lower-case letter like ${}",
|
||||
lower
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TOperator> Peek for Variable<'a, TOperator> {
|
||||
fn peek(c: Cursor) -> bool {
|
||||
if let Some((id, _rest)) = c.id() {
|
||||
id.chars().all(|c| !c.is_alphabetic() || c.is_lowercase())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn display() -> &'static str {
|
||||
"variable"
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TOperator, TOperand> Parse<'a> for Operation<'a, TOperator, TOperand>
|
||||
where
|
||||
TOperator: Parse<'a>,
|
||||
TOperand: 'a + Ast<'a, TOperator> + Peek + Parse<'a>,
|
||||
DynAstRef<'a, TOperator>: From<&'a TOperand>,
|
||||
{
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
// Don't blow the stack with this recursive parser. We don't expect
|
||||
// nesting to ever get very deep, so it isn't worth refactoring this
|
||||
// code to be non-recursive.
|
||||
if p.parens_depth() > 25 {
|
||||
return Err(p.error("module nesting too deep"));
|
||||
}
|
||||
|
||||
let span = p.cur_span();
|
||||
p.parens(|p| {
|
||||
let operator = p.parse()?;
|
||||
|
||||
let r#type = Cell::new(if p.peek::<tok::left_curly>() {
|
||||
p.parse::<tok::left_curly>()?;
|
||||
let ty = p.parse::<Type>()?;
|
||||
p.parse::<tok::right_curly>()?;
|
||||
Some(ty)
|
||||
} else {
|
||||
None
|
||||
});
|
||||
|
||||
let mut operands = vec![];
|
||||
while p.peek::<TOperand>() {
|
||||
operands.push(p.parse()?);
|
||||
}
|
||||
Ok(Operation {
|
||||
span,
|
||||
operator,
|
||||
r#type,
|
||||
operands,
|
||||
marker: PhantomData,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TOperator, TOperand> Peek for Operation<'a, TOperator, TOperand>
|
||||
where
|
||||
TOperand: 'a + Ast<'a, TOperator>,
|
||||
DynAstRef<'a, TOperator>: From<&'a TOperand>,
|
||||
{
|
||||
fn peek(c: Cursor) -> bool {
|
||||
wast::LParen::peek(c)
|
||||
}
|
||||
|
||||
fn display() -> &'static str {
|
||||
"operation"
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TOperator> Parse<'a> for Precondition<'a, TOperator> {
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
let span = p.cur_span();
|
||||
p.parens(|p| {
|
||||
let constraint = p.parse()?;
|
||||
let mut operands = vec![];
|
||||
while p.peek::<ConstraintOperand<TOperator>>() {
|
||||
operands.push(p.parse()?);
|
||||
}
|
||||
Ok(Precondition {
|
||||
span,
|
||||
constraint,
|
||||
operands,
|
||||
marker: PhantomData,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parse<'a> for Constraint {
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
if p.peek::<tok::is_power_of_two>() {
|
||||
p.parse::<tok::is_power_of_two>()?;
|
||||
return Ok(Constraint::IsPowerOfTwo);
|
||||
}
|
||||
if p.peek::<tok::bit_width>() {
|
||||
p.parse::<tok::bit_width>()?;
|
||||
return Ok(Constraint::BitWidth);
|
||||
}
|
||||
if p.peek::<tok::fits_in_native_word>() {
|
||||
p.parse::<tok::fits_in_native_word>()?;
|
||||
return Ok(Constraint::FitsInNativeWord);
|
||||
}
|
||||
Err(p.error("expected a precondition constraint"))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TOperator> Parse<'a> for ConstraintOperand<'a, TOperator> {
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
if p.peek::<ValueLiteral<TOperator>>() {
|
||||
return Ok(ConstraintOperand::ValueLiteral(p.parse()?));
|
||||
}
|
||||
if p.peek::<Constant<TOperator>>() {
|
||||
return Ok(ConstraintOperand::Constant(p.parse()?));
|
||||
}
|
||||
if p.peek::<Variable<TOperator>>() {
|
||||
return Ok(ConstraintOperand::Variable(p.parse()?));
|
||||
}
|
||||
Err(p.error("expected an operand for precondition constraint"))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TOperator> Peek for ConstraintOperand<'a, TOperator> {
|
||||
fn peek(c: Cursor) -> bool {
|
||||
ValueLiteral::<TOperator>::peek(c)
|
||||
|| Constant::<TOperator>::peek(c)
|
||||
|| Variable::<TOperator>::peek(c)
|
||||
}
|
||||
|
||||
fn display() -> &'static str {
|
||||
"operand for a precondition constraint"
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TOperator> Parse<'a> for Rhs<'a, TOperator>
|
||||
where
|
||||
TOperator: Parse<'a>,
|
||||
{
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
if p.peek::<ValueLiteral<TOperator>>() {
|
||||
return Ok(Rhs::ValueLiteral(p.parse()?));
|
||||
}
|
||||
if p.peek::<Constant<TOperator>>() {
|
||||
return Ok(Rhs::Constant(p.parse()?));
|
||||
}
|
||||
if p.peek::<Variable<TOperator>>() {
|
||||
return Ok(Rhs::Variable(p.parse()?));
|
||||
}
|
||||
if p.peek::<Unquote<TOperator>>() {
|
||||
return Ok(Rhs::Unquote(p.parse()?));
|
||||
}
|
||||
if p.peek::<Operation<TOperator, Self>>() {
|
||||
return Ok(Rhs::Operation(p.parse()?));
|
||||
}
|
||||
Err(p.error("expected a right-hand side replacement"))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TOperator> Peek for Rhs<'a, TOperator>
|
||||
where
|
||||
TOperator: 'a,
|
||||
{
|
||||
fn peek(c: Cursor) -> bool {
|
||||
ValueLiteral::<TOperator>::peek(c)
|
||||
|| Constant::<TOperator>::peek(c)
|
||||
|| Variable::<TOperator>::peek(c)
|
||||
|| Unquote::<TOperator>::peek(c)
|
||||
|| Operation::<TOperator, Self>::peek(c)
|
||||
}
|
||||
|
||||
fn display() -> &'static str {
|
||||
"right-hand side replacement"
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TOperator> Parse<'a> for Unquote<'a, TOperator>
|
||||
where
|
||||
TOperator: Parse<'a>,
|
||||
{
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
let span = p.cur_span();
|
||||
p.parse::<tok::dollar>()?;
|
||||
p.parens(|p| {
|
||||
let operator = p.parse()?;
|
||||
let mut operands = vec![];
|
||||
while p.peek::<Rhs<TOperator>>() {
|
||||
operands.push(p.parse()?);
|
||||
}
|
||||
Ok(Unquote {
|
||||
span,
|
||||
operator,
|
||||
operands,
|
||||
marker: PhantomData,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TOperator> Peek for Unquote<'a, TOperator> {
|
||||
fn peek(c: Cursor) -> bool {
|
||||
tok::dollar::peek(c)
|
||||
}
|
||||
|
||||
fn display() -> &'static str {
|
||||
"unquote expression"
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use peepmatic_test_operator::TestOperator;
|
||||
|
||||
macro_rules! test_parse {
|
||||
(
|
||||
$(
|
||||
$name:ident < $ast:ty > {
|
||||
$( ok { $( $ok:expr , )* } )*
|
||||
$( err { $( $err:expr , )* } )*
|
||||
}
|
||||
)*
|
||||
) => {
|
||||
$(
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn $name() {
|
||||
$(
|
||||
$({
|
||||
let input = $ok;
|
||||
let buf = wast::parser::ParseBuffer::new(input).unwrap_or_else(|e| {
|
||||
panic!("should lex OK, got error:\n\n{}\n\nInput:\n\n{}", e, input)
|
||||
});
|
||||
if let Err(e) = wast::parser::parse::<$ast>(&buf) {
|
||||
panic!(
|
||||
"expected to parse OK, got error:\n\n{}\n\nInput:\n\n{}",
|
||||
e, input
|
||||
);
|
||||
}
|
||||
})*
|
||||
)*
|
||||
|
||||
$(
|
||||
$({
|
||||
let input = $err;
|
||||
let buf = wast::parser::ParseBuffer::new(input).unwrap_or_else(|e| {
|
||||
panic!("should lex OK, got error:\n\n{}\n\nInput:\n\n{}", e, input)
|
||||
});
|
||||
if let Ok(ast) = wast::parser::parse::<$ast>(&buf) {
|
||||
panic!(
|
||||
"expected a parse error, got:\n\n{:?}\n\nInput:\n\n{}",
|
||||
ast, input
|
||||
);
|
||||
}
|
||||
})*
|
||||
)*
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
test_parse! {
|
||||
parse_boolean<Boolean<TestOperator>> {
|
||||
ok {
|
||||
"true",
|
||||
"false",
|
||||
}
|
||||
err {
|
||||
"",
|
||||
"t",
|
||||
"tr",
|
||||
"tru",
|
||||
"truezzz",
|
||||
"f",
|
||||
"fa",
|
||||
"fal",
|
||||
"fals",
|
||||
"falsezzz",
|
||||
}
|
||||
}
|
||||
parse_cc<ConditionCode<TestOperator>> {
|
||||
ok {
|
||||
"eq",
|
||||
"ne",
|
||||
"slt",
|
||||
"ult",
|
||||
"sge",
|
||||
"uge",
|
||||
"sgt",
|
||||
"ugt",
|
||||
"sle",
|
||||
"ule",
|
||||
"of",
|
||||
"nof",
|
||||
}
|
||||
err {
|
||||
"",
|
||||
"neq",
|
||||
}
|
||||
}
|
||||
parse_constant<Constant<TestOperator>> {
|
||||
ok {
|
||||
"$C",
|
||||
"$C1",
|
||||
"$C2",
|
||||
"$X",
|
||||
"$Y",
|
||||
"$SOME-CONSTANT",
|
||||
"$SOME_CONSTANT",
|
||||
}
|
||||
err {
|
||||
"",
|
||||
"zzz",
|
||||
"$",
|
||||
"$variable",
|
||||
"$Some-Constant",
|
||||
"$Some_Constant",
|
||||
"$Some_constant",
|
||||
}
|
||||
}
|
||||
parse_constraint<Constraint> {
|
||||
ok {
|
||||
"is-power-of-two",
|
||||
"bit-width",
|
||||
"fits-in-native-word",
|
||||
}
|
||||
err {
|
||||
"",
|
||||
"iadd",
|
||||
"imul",
|
||||
}
|
||||
}
|
||||
parse_constraint_operand<ConstraintOperand<TestOperator>> {
|
||||
ok {
|
||||
"1234",
|
||||
"true",
|
||||
"$CONSTANT",
|
||||
"$variable",
|
||||
}
|
||||
err {
|
||||
"",
|
||||
"is-power-of-two",
|
||||
"(is-power-of-two $C)",
|
||||
"(iadd 1 2)",
|
||||
}
|
||||
}
|
||||
parse_integer<Integer<TestOperator>> {
|
||||
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<Lhs<TestOperator>> {
|
||||
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<Operation<TestOperator, Pattern<TestOperator>>> {
|
||||
ok {
|
||||
"(iadd)",
|
||||
"(iadd 1)",
|
||||
"(iadd 1 2)",
|
||||
"(iadd $x $C)",
|
||||
"(iadd{i32} $x $y)",
|
||||
"(icmp eq $x $y)",
|
||||
}
|
||||
err {
|
||||
"",
|
||||
"()",
|
||||
"$var",
|
||||
"$CONST",
|
||||
"(ishl $x $(log2 $C))",
|
||||
|
||||
// Nesting too deep.
|
||||
"(iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd (iadd))))))))))))))))))))))))))))))))))))))))))))))))))",
|
||||
}
|
||||
}
|
||||
parse_operation_rhs<Operation<TestOperator, Rhs<TestOperator>>> {
|
||||
ok {
|
||||
"(iadd)",
|
||||
"(iadd 1)",
|
||||
"(iadd 1 2)",
|
||||
"(ishl $x $(log2 $C))",
|
||||
}
|
||||
err {
|
||||
"",
|
||||
"()",
|
||||
"$var",
|
||||
"$CONST",
|
||||
}
|
||||
}
|
||||
parse_operator<TestOperator> {
|
||||
ok {
|
||||
"bor",
|
||||
"iadd",
|
||||
"iadd_imm",
|
||||
"iconst",
|
||||
"imul",
|
||||
"imul_imm",
|
||||
"ishl",
|
||||
"sdiv",
|
||||
"sdiv_imm",
|
||||
"sshr",
|
||||
}
|
||||
err {
|
||||
"",
|
||||
"iadd.i32",
|
||||
"iadd{i32}",
|
||||
}
|
||||
}
|
||||
parse_optimization<Optimization<TestOperator>> {
|
||||
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<Optimizations<TestOperator>> {
|
||||
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<Pattern<TestOperator>> {
|
||||
ok {
|
||||
"1234",
|
||||
"$C",
|
||||
"$x",
|
||||
"(iadd $x $y)",
|
||||
}
|
||||
err {
|
||||
"",
|
||||
"()",
|
||||
"abc",
|
||||
}
|
||||
}
|
||||
parse_precondition<Precondition<TestOperator>> {
|
||||
ok {
|
||||
"(is-power-of-two)",
|
||||
"(is-power-of-two $C)",
|
||||
"(is-power-of-two $C1 $C2)",
|
||||
}
|
||||
err {
|
||||
"",
|
||||
"1234",
|
||||
"()",
|
||||
"$var",
|
||||
"$CONST",
|
||||
}
|
||||
}
|
||||
parse_rhs<Rhs<TestOperator>> {
|
||||
ok {
|
||||
"5",
|
||||
"$C",
|
||||
"$x",
|
||||
"$(log2 $C)",
|
||||
"(iadd $x 1)",
|
||||
}
|
||||
err {
|
||||
"",
|
||||
"()",
|
||||
}
|
||||
}
|
||||
parse_unquote<Unquote<TestOperator>> {
|
||||
ok {
|
||||
"$(log2)",
|
||||
"$(log2 $C)",
|
||||
"$(log2 $C1 1)",
|
||||
"$(neg)",
|
||||
"$(neg $C)",
|
||||
"$(neg $C 1)",
|
||||
}
|
||||
err {
|
||||
"",
|
||||
"(log2 $C)",
|
||||
"$()",
|
||||
}
|
||||
}
|
||||
parse_value_literal<ValueLiteral<TestOperator>> {
|
||||
ok {
|
||||
"12345",
|
||||
"true",
|
||||
}
|
||||
err {
|
||||
"",
|
||||
"'c'",
|
||||
"\"hello\"",
|
||||
"12.34",
|
||||
}
|
||||
}
|
||||
parse_variable<Variable<TestOperator>> {
|
||||
ok {
|
||||
"$v",
|
||||
"$v1",
|
||||
"$v2",
|
||||
"$x",
|
||||
"$y",
|
||||
"$some-var",
|
||||
"$another_var",
|
||||
}
|
||||
err {
|
||||
"zzz",
|
||||
"$",
|
||||
"$CONSTANT",
|
||||
"$fooBar",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,327 +0,0 @@
|
||||
//! Traversals over the AST.
|
||||
|
||||
use crate::ast::*;
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
|
||||
/// A low-level DFS traversal event: either entering or exiting the traversal of
|
||||
/// an AST node.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum TraversalEvent {
|
||||
/// Entering traversal of an AST node.
|
||||
///
|
||||
/// Processing an AST node upon this event corresponds to a pre-order
|
||||
/// DFS traversal.
|
||||
Enter,
|
||||
|
||||
/// Exiting traversal of an AST node.
|
||||
///
|
||||
/// Processing an AST node upon this event corresponds to a post-order DFS
|
||||
/// traversal.
|
||||
Exit,
|
||||
}
|
||||
|
||||
/// A depth-first traversal of an AST.
|
||||
///
|
||||
/// This is a fairly low-level traversal type, and is intended to be used as a
|
||||
/// building block for making specific pre-order or post-order traversals for
|
||||
/// whatever problem is at hand.
|
||||
///
|
||||
/// This implementation is not recursive, and exposes an `Iterator` interface
|
||||
/// that yields pairs of `(TraversalEvent, DynAstRef)` items.
|
||||
///
|
||||
/// The traversal can walk a whole set of `Optimization`s or just a subtree of
|
||||
/// the AST, because the `new` constructor takes anything that can convert into
|
||||
/// a `DynAstRef`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Dfs<'a, TOperator> {
|
||||
stack: Vec<(TraversalEvent, DynAstRef<'a, TOperator>)>,
|
||||
}
|
||||
|
||||
impl<'a, TOperator> Dfs<'a, TOperator>
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
/// Construct a new `Dfs` traversal starting at the given `start` AST node.
|
||||
pub fn new(start: impl Into<DynAstRef<'a, TOperator>>) -> Self {
|
||||
let start = start.into();
|
||||
Dfs {
|
||||
stack: vec![
|
||||
(TraversalEvent::Exit, start),
|
||||
(TraversalEvent::Enter, start),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
/// Peek at the next traversal event and AST node pair, if any.
|
||||
pub fn peek(&self) -> Option<(TraversalEvent, DynAstRef<'a, TOperator>)> {
|
||||
self.stack.last().copied()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TOperator> Iterator for Dfs<'a, TOperator>
|
||||
where
|
||||
TOperator: Copy,
|
||||
{
|
||||
type Item = (TraversalEvent, DynAstRef<'a, TOperator>);
|
||||
|
||||
fn next(&mut self) -> Option<(TraversalEvent, DynAstRef<'a, TOperator>)> {
|
||||
let (event, node) = self.stack.pop()?;
|
||||
if let TraversalEvent::Enter = event {
|
||||
let mut enqueue_children = EnqueueChildren(self);
|
||||
node.child_nodes(&mut enqueue_children)
|
||||
}
|
||||
return Some((event, node));
|
||||
|
||||
struct EnqueueChildren<'a, 'b, TOperator>(&'b mut Dfs<'a, TOperator>)
|
||||
where
|
||||
'a: 'b;
|
||||
|
||||
impl<'a, 'b, TOperator> Extend<DynAstRef<'a, TOperator>> for EnqueueChildren<'a, 'b, TOperator>
|
||||
where
|
||||
'a: 'b,
|
||||
TOperator: Copy,
|
||||
{
|
||||
fn extend<T: IntoIterator<Item = DynAstRef<'a, TOperator>>>(&mut self, iter: T) {
|
||||
let iter = iter.into_iter();
|
||||
|
||||
let (min, max) = iter.size_hint();
|
||||
self.0.stack.reserve(max.unwrap_or(min) * 2);
|
||||
|
||||
let start = self.0.stack.len();
|
||||
|
||||
for node in iter {
|
||||
self.0.stack.push((TraversalEvent::Enter, node));
|
||||
self.0.stack.push((TraversalEvent::Exit, node));
|
||||
}
|
||||
|
||||
// Reverse to make it so that we visit children in order
|
||||
// (e.g. operands are visited in order).
|
||||
self.0.stack[start..].reverse();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A breadth-first traversal of an AST
|
||||
///
|
||||
/// This implementation is not recursive, and exposes an `Iterator` interface
|
||||
/// that yields `DynAstRef` items.
|
||||
///
|
||||
/// The traversal can walk a whole set of `Optimization`s or just a subtree of
|
||||
/// the AST, because the `new` constructor takes anything that can convert into
|
||||
/// a `DynAstRef`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Bfs<'a, TOperator> {
|
||||
queue: VecDeque<DynAstRef<'a, TOperator>>,
|
||||
}
|
||||
|
||||
impl<'a, TOperator> Bfs<'a, TOperator>
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
/// Construct a new `Bfs` traversal starting at the given `start` AST node.
|
||||
pub fn new(start: impl Into<DynAstRef<'a, TOperator>>) -> Self {
|
||||
let mut queue = VecDeque::with_capacity(16);
|
||||
queue.push_back(start.into());
|
||||
Bfs { queue }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TOperator> Iterator for Bfs<'a, TOperator>
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
type Item = DynAstRef<'a, TOperator>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let node = self.queue.pop_front()?;
|
||||
node.child_nodes(&mut self.queue);
|
||||
Some(node)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use peepmatic_test_operator::TestOperator;
|
||||
use DynAstRef::*;
|
||||
|
||||
#[test]
|
||||
fn test_dfs_traversal() {
|
||||
let input = "
|
||||
(=> (when (imul $x $C)
|
||||
(is-power-of-two $C))
|
||||
(ishl $x $(log2 $C)))
|
||||
";
|
||||
let buf = wast::parser::ParseBuffer::new(input).expect("input should lex OK");
|
||||
let ast = match wast::parser::parse::<crate::ast::Optimizations<TestOperator>>(&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());
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
16
cranelift/src/clif-util.rs
Normal file → Executable file
16
cranelift/src/clif-util.rs
Normal file → Executable file
@@ -28,9 +28,6 @@ mod utils;
|
||||
#[cfg(feature = "souper-harvest")]
|
||||
mod souper_harvest;
|
||||
|
||||
#[cfg(feature = "peepmatic-souper")]
|
||||
mod souper_to_peepmatic;
|
||||
|
||||
#[cfg(feature = "wasm")]
|
||||
mod wasm;
|
||||
|
||||
@@ -59,11 +56,6 @@ enum Commands {
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
Wasm(CompiledWithoutSupportOptions),
|
||||
|
||||
#[cfg(feature = "peepmatic-souper")]
|
||||
SouperToPeepmatic(souper_to_peepmatic::Options),
|
||||
#[cfg(not(feature = "peepmatic-souper"))]
|
||||
SouperToPeepmatic(CompiledWithoutSupportOptions),
|
||||
|
||||
#[cfg(feature = "souper-harvest")]
|
||||
SouperHarvest(souper_harvest::Options),
|
||||
#[cfg(not(feature = "souper-harvest"))]
|
||||
@@ -135,14 +127,6 @@ fn main() -> anyhow::Result<()> {
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
Commands::Wasm(_) => anyhow::bail!("Error: clif-util was compiled without wasm support."),
|
||||
|
||||
#[cfg(feature = "peepmatic-souper")]
|
||||
Commands::SouperToPeepmatic(s) => souper_to_peepmatic::run(&s)?,
|
||||
#[cfg(not(feature = "peepmatic-souper"))]
|
||||
Commands::SouperToPeepmatic(_) => anyhow::bail!(
|
||||
"Error: clif-util was compiled without support for the `souper-to-peepmatic` \
|
||||
subcommand",
|
||||
),
|
||||
|
||||
#[cfg(feature = "souper-harvest")]
|
||||
Commands::SouperHarvest(s) => souper_harvest::run(&s)?,
|
||||
#[cfg(not(feature = "souper-harvest"))]
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
use anyhow::{Context, Result};
|
||||
use std::io::{Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use structopt::StructOpt;
|
||||
|
||||
/// Convert Souper optimizations into Peepmatic DSL.
|
||||
#[derive(StructOpt)]
|
||||
pub struct Options {
|
||||
/// Specify an input file to be used. Use '-' for stdin.
|
||||
#[structopt(parse(from_os_str))]
|
||||
input: PathBuf,
|
||||
|
||||
/// Specify the output file to be used. Use '-' for stdout.
|
||||
#[structopt(short("o"), long("output"), default_value("-"), parse(from_os_str))]
|
||||
output: PathBuf,
|
||||
}
|
||||
|
||||
pub fn run(options: &Options) -> Result<()> {
|
||||
let peepmatic_dsl = if options.input == Path::new("-") {
|
||||
let stdin = std::io::stdin();
|
||||
let mut stdin = stdin.lock();
|
||||
let mut souper_dsl = vec![];
|
||||
stdin
|
||||
.read_to_end(&mut souper_dsl)
|
||||
.context("failed to read from stdin")?;
|
||||
let souper_dsl = String::from_utf8(souper_dsl).context("stdin is not UTF-8: {}")?;
|
||||
peepmatic_souper::convert_str(&souper_dsl, Some(Path::new("stdin")))?
|
||||
} else {
|
||||
peepmatic_souper::convert_file(&options.input)?
|
||||
};
|
||||
|
||||
if options.output == Path::new("-") {
|
||||
let stdout = std::io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
stdout
|
||||
.write_all(peepmatic_dsl.as_bytes())
|
||||
.context("error writing to stdout")?;
|
||||
} else {
|
||||
std::fs::write(&options.output, peepmatic_dsl.as_bytes())
|
||||
.with_context(|| format!("error writing to {}", options.output.display()))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -17,7 +17,6 @@ cranelift-interpreter = { path = "../cranelift/interpreter" }
|
||||
cranelift-fuzzgen = { path = "../cranelift/fuzzgen" }
|
||||
libfuzzer-sys = "0.4.0"
|
||||
target-lexicon = "0.12"
|
||||
peepmatic-fuzzing = { path = "../cranelift/peepmatic/crates/fuzzing", optional = true }
|
||||
wasmtime = { path = "../crates/wasmtime" }
|
||||
wasmtime-fuzzing = { path = "../crates/fuzzing" }
|
||||
wasm-smith = "0.7.0"
|
||||
|
||||
@@ -17,14 +17,6 @@ use std::time::Duration;
|
||||
|
||||
// note that this list must be topologically sorted by dependencies
|
||||
const CRATES_TO_PUBLISH: &[&str] = &[
|
||||
// peepmatic
|
||||
"peepmatic-traits",
|
||||
"peepmatic-macro",
|
||||
"peepmatic-automata",
|
||||
"peepmatic-test-operator",
|
||||
"peepmatic-runtime",
|
||||
"peepmatic",
|
||||
"peepmatic-souper",
|
||||
// cranelift
|
||||
"isle",
|
||||
"cranelift-entity",
|
||||
@@ -461,7 +453,6 @@ fn verify(crates: &[Crate]) {
|
||||
.env("CARGO_TARGET_DIR", "./target");
|
||||
if krate.name == "witx"
|
||||
|| krate.name.contains("wasi-nn")
|
||||
|| krate.name.contains("peepmatic")
|
||||
{
|
||||
cmd.arg("--no-verify");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user