Merge pull request #1 from cfallin/initial-regalloc
Initial implementation of regalloc2.
This commit is contained in:
64
.github/workflows/rust.yml
vendored
Normal file
64
.github/workflows/rust.yml
vendored
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# Derived from regalloc.rs' GitHub CI config file.
|
||||||
|
|
||||||
|
name: Rust
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# Lint code with rustfmt, report an error if it needs to be run.
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Install rustfmt
|
||||||
|
run: rustup component add rustfmt
|
||||||
|
- name: Run rustfmt and check there's no difference
|
||||||
|
run: cargo fmt --all -- --check
|
||||||
|
|
||||||
|
# Make sure the code compiles and that all the tests pass.
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Build
|
||||||
|
run: cargo build
|
||||||
|
- name: Run tests
|
||||||
|
run: cargo test --all --verbose
|
||||||
|
|
||||||
|
# Lint dependency graph for security advisories, duplicate versions, and
|
||||||
|
# incompatible licences.
|
||||||
|
cargo_deny:
|
||||||
|
name: Cargo deny
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- run: |
|
||||||
|
set -e
|
||||||
|
curl -L https://github.com/EmbarkStudios/cargo-deny/releases/download/0.8.5/cargo-deny-0.8.5-x86_64-unknown-linux-musl.tar.gz | tar xzf -
|
||||||
|
mv cargo-deny-*-x86_64-unknown-linux-musl/cargo-deny cargo-deny
|
||||||
|
echo `pwd` >> $GITHUB_PATH
|
||||||
|
- run: cargo deny check
|
||||||
|
|
||||||
|
# Builds the fuzz targets.
|
||||||
|
fuzz:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Install nightly
|
||||||
|
run: rustup toolchain install nightly
|
||||||
|
- name: Install cargo-fuzz
|
||||||
|
run: cargo +nightly install cargo-fuzz
|
||||||
|
- name: Build ssagen fuzzing target
|
||||||
|
run: cargo +nightly fuzz build ssagen
|
||||||
|
- name: Build moves fuzzing target
|
||||||
|
run: cargo +nightly fuzz build moves
|
||||||
|
- name: Build ion fuzzing target
|
||||||
|
run: cargo +nightly fuzz build ion
|
||||||
|
- name: Build and smoke-test ion_checker fuzzing target
|
||||||
|
run: cargo +nightly fuzz run ion_checker ./fuzz/smoketest/ion_checker.bin
|
||||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
Cargo.lock
|
||||||
|
target/
|
||||||
|
.*.swp
|
||||||
|
*~
|
||||||
24
Cargo.toml
Normal file
24
Cargo.toml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
[package]
|
||||||
|
name = "regalloc2"
|
||||||
|
version = "0.0.1"
|
||||||
|
authors = ["Chris Fallin <chris@cfallin.org>", "Mozilla SpiderMonkey Developers"]
|
||||||
|
edition = "2018"
|
||||||
|
license = "Apache-2.0 WITH LLVM-exception AND MPL-2.0"
|
||||||
|
description = "Backtracking register allocator inspired from IonMonkey"
|
||||||
|
repository = "https://github.com/bytecodealliance/regalloc2"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
log = { version = "0.4.8", default-features = false }
|
||||||
|
smallvec = "1.6.1"
|
||||||
|
fxhash = "0.2.1"
|
||||||
|
|
||||||
|
# The below are only needed for fuzzing.
|
||||||
|
# Keep this in sync with libfuzzer_sys's crate version:
|
||||||
|
arbitrary = { version = "^0.4.6", optional = true }
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
debug = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
fuzzing = ["arbitrary"]
|
||||||
220
LICENSE
Normal file
220
LICENSE
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
33
README.md
Normal file
33
README.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
## regalloc2: another register allocator
|
||||||
|
|
||||||
|
This is a register allocator that started life as, and is about 50%
|
||||||
|
still, a port of IonMonkey's backtracking register allocator to
|
||||||
|
Rust. In many regards, it has been generalized, optimized, and
|
||||||
|
improved since the initial port, and now supports both SSA and non-SSA
|
||||||
|
use-cases.
|
||||||
|
|
||||||
|
In addition, it contains substantial amounts of testing infrastructure
|
||||||
|
(fuzzing harnesses and checkers) that does not exist in the original
|
||||||
|
IonMonkey allocator.
|
||||||
|
|
||||||
|
See the [design overview](doc/DESIGN.md) for (much!) more detail on
|
||||||
|
how the allocator works.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Unless otherwise specified, code in this crate is licensed under the Apache 2.0
|
||||||
|
License with LLVM Exception. This license text can be found in the file
|
||||||
|
`LICENSE`.
|
||||||
|
|
||||||
|
Files in the `src/ion/` directory are directly ported from original C++ code in
|
||||||
|
IonMonkey, a part of the Firefox codebase. Parts of `src/lib.rs` are also
|
||||||
|
definitions that are directly translated from this original code. As a result,
|
||||||
|
these files are derivative works and are covered by the Mozilla Public License
|
||||||
|
(MPL) 2.0, as described in license headers in those files. Please see the
|
||||||
|
notices in relevant files for links to the original IonMonkey source files from
|
||||||
|
which they have been translated/derived. The MPL text can be found in
|
||||||
|
`src/ion/LICENSE`.
|
||||||
|
|
||||||
|
Parts of the code are derived from regalloc.rs: in particular,
|
||||||
|
`src/checker.rs` and `src/domtree.rs`. This crate has the same license
|
||||||
|
as regalloc.rs, so the license on these files does not differ.
|
||||||
1650
doc/DESIGN.md
Normal file
1650
doc/DESIGN.md
Normal file
File diff suppressed because it is too large
Load Diff
34
doc/TODO
Normal file
34
doc/TODO
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Features
|
||||||
|
|
||||||
|
- Large-input support (> 1M vregs, > 1M blocks)
|
||||||
|
- Two operand impls: u64-based and u32-based. Always accept
|
||||||
|
u64-based `Operand` publicly (do not expose this in interface).
|
||||||
|
- Trait to generalize over them and support both internally
|
||||||
|
(parameterize the whole allocator impl)
|
||||||
|
- On data-structure init, choose one or the other based on max vreg
|
||||||
|
index
|
||||||
|
- Update halfmove keys: u128 rather than u64
|
||||||
|
|
||||||
|
- Support allocation of register pairs (or overlapping registers generally)
|
||||||
|
|
||||||
|
- Rematerialization
|
||||||
|
- Stack-location constraints that place operands in user-defined stack
|
||||||
|
locations (distinct from SpillSlots) (e.g., stack args)
|
||||||
|
|
||||||
|
# Performance
|
||||||
|
|
||||||
|
- Investigate better register hinting
|
||||||
|
- Investigate more principled cost functions and split locations,
|
||||||
|
especially around loop nests
|
||||||
|
|
||||||
|
- Investigate ways to improve bundle-merging; e.g., merge moves before
|
||||||
|
other types of connections
|
||||||
|
|
||||||
|
- Add limited inter-block redundant-move elimination: propagate across
|
||||||
|
splits but not joins.
|
||||||
|
|
||||||
|
- Optimize allocations (some reports of 5-7% of time spent in allocator)
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
|
||||||
|
- Remove support for non-SSA code once no longer necessary
|
||||||
3
fuzz/.gitignore
vendored
Normal file
3
fuzz/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
target
|
||||||
|
corpus
|
||||||
|
artifacts
|
||||||
51
fuzz/Cargo.toml
Normal file
51
fuzz/Cargo.toml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
[package]
|
||||||
|
name = "regalloc2-fuzz"
|
||||||
|
version = "0.0.0"
|
||||||
|
authors = ["Chris Fallin <chris@cfallin.org>"]
|
||||||
|
license = "MPL-2.0 AND Apache-2.0 WITH LLVM-exception"
|
||||||
|
publish = false
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
cargo-fuzz = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
regalloc2 = { path = "../", features = ["fuzzing"] }
|
||||||
|
libfuzzer-sys = "0.3"
|
||||||
|
arbitrary = { version = "^0.4.6", features = ["derive"] }
|
||||||
|
log = { version = "0.4.8", default-features = false }
|
||||||
|
env_logger = "0.8.3"
|
||||||
|
|
||||||
|
# Prevent this from interfering with workspaces
|
||||||
|
[workspace]
|
||||||
|
members = ["."]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "domtree"
|
||||||
|
path = "fuzz_targets/domtree.rs"
|
||||||
|
test = false
|
||||||
|
doc = false
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "ssagen"
|
||||||
|
path = "fuzz_targets/ssagen.rs"
|
||||||
|
test = false
|
||||||
|
doc = false
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "ion"
|
||||||
|
path = "fuzz_targets/ion.rs"
|
||||||
|
test = false
|
||||||
|
doc = false
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "moves"
|
||||||
|
path = "fuzz_targets/moves.rs"
|
||||||
|
test = false
|
||||||
|
doc = false
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "ion_checker"
|
||||||
|
path = "fuzz_targets/ion_checker.rs"
|
||||||
|
test = false
|
||||||
|
doc = false
|
||||||
133
fuzz/fuzz_targets/domtree.rs
Normal file
133
fuzz/fuzz_targets/domtree.rs
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
* Released under the terms of the Apache 2.0 license with LLVM
|
||||||
|
* exception. See `LICENSE` for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#![no_main]
|
||||||
|
use libfuzzer_sys::arbitrary::{Arbitrary, Result, Unstructured};
|
||||||
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use regalloc2::{
|
||||||
|
fuzzing::{domtree, postorder},
|
||||||
|
Block,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct CFG {
|
||||||
|
num_blocks: usize,
|
||||||
|
preds: Vec<Vec<Block>>,
|
||||||
|
succs: Vec<Vec<Block>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arbitrary for CFG {
|
||||||
|
fn arbitrary(u: &mut Unstructured) -> Result<CFG> {
|
||||||
|
let num_blocks = u.int_in_range(1..=1000)?;
|
||||||
|
let mut succs = vec![];
|
||||||
|
for _ in 0..num_blocks {
|
||||||
|
let mut block_succs = vec![];
|
||||||
|
for _ in 0..u.int_in_range(0..=5)? {
|
||||||
|
block_succs.push(Block::new(u.int_in_range(0..=(num_blocks - 1))?));
|
||||||
|
}
|
||||||
|
succs.push(block_succs);
|
||||||
|
}
|
||||||
|
let mut preds = vec![];
|
||||||
|
for _ in 0..num_blocks {
|
||||||
|
preds.push(vec![]);
|
||||||
|
}
|
||||||
|
for from in 0..num_blocks {
|
||||||
|
for succ in &succs[from] {
|
||||||
|
preds[succ.index()].push(Block::new(from));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(CFG {
|
||||||
|
num_blocks,
|
||||||
|
preds,
|
||||||
|
succs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct Path {
|
||||||
|
blocks: Vec<Block>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Path {
|
||||||
|
fn choose_from_cfg(cfg: &CFG, u: &mut Unstructured) -> Result<Path> {
|
||||||
|
let succs = u.int_in_range(0..=(2 * cfg.num_blocks))?;
|
||||||
|
let mut block = Block::new(0);
|
||||||
|
let mut blocks = vec![];
|
||||||
|
blocks.push(block);
|
||||||
|
for _ in 0..succs {
|
||||||
|
if cfg.succs[block.index()].is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
block = *u.choose(&cfg.succs[block.index()])?;
|
||||||
|
blocks.push(block);
|
||||||
|
}
|
||||||
|
Ok(Path { blocks })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_idom_violations(idom: &[Block], path: &Path) {
|
||||||
|
// "a dom b" means that any path from the entry block through the CFG that
|
||||||
|
// contains a and b will contain a before b.
|
||||||
|
//
|
||||||
|
// To test this, for any given block b_i, we have the set S of b_0 .. b_{i-1},
|
||||||
|
// and we walk up the domtree from b_i to get all blocks that dominate b_i;
|
||||||
|
// each such block must appear in S. (Otherwise, we have a counterexample
|
||||||
|
// for which dominance says it should appear in the path prefix, but it does
|
||||||
|
// not.)
|
||||||
|
let mut visited = HashSet::new();
|
||||||
|
visited.insert(Block::new(0));
|
||||||
|
for block in &path.blocks {
|
||||||
|
let mut parent = idom[block.index()];
|
||||||
|
let mut domset = HashSet::new();
|
||||||
|
domset.insert(*block);
|
||||||
|
while parent.is_valid() {
|
||||||
|
assert!(visited.contains(&parent));
|
||||||
|
domset.insert(parent);
|
||||||
|
let next = idom[parent.index()];
|
||||||
|
parent = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that `dominates()` returns true for every block in domset,
|
||||||
|
// and false for every other block.
|
||||||
|
for domblock in 0..idom.len() {
|
||||||
|
let domblock = Block::new(domblock);
|
||||||
|
assert_eq!(
|
||||||
|
domset.contains(&domblock),
|
||||||
|
domtree::dominates(idom, domblock, *block)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
visited.insert(*block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct TestCase {
|
||||||
|
cfg: CFG,
|
||||||
|
path: Path,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arbitrary for TestCase {
|
||||||
|
fn arbitrary(u: &mut Unstructured) -> Result<TestCase> {
|
||||||
|
let cfg = CFG::arbitrary(u)?;
|
||||||
|
let path = Path::choose_from_cfg(&cfg, u)?;
|
||||||
|
Ok(TestCase { cfg, path })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fuzz_target!(|testcase: TestCase| {
|
||||||
|
let postord = postorder::calculate(testcase.cfg.num_blocks, Block::new(0), |block| {
|
||||||
|
&testcase.cfg.succs[block.index()]
|
||||||
|
});
|
||||||
|
let idom = domtree::calculate(
|
||||||
|
testcase.cfg.num_blocks,
|
||||||
|
|block| &testcase.cfg.preds[block.index()],
|
||||||
|
&postord[..],
|
||||||
|
Block::new(0),
|
||||||
|
);
|
||||||
|
check_idom_violations(&idom[..], &testcase.path);
|
||||||
|
});
|
||||||
16
fuzz/fuzz_targets/ion.rs
Normal file
16
fuzz/fuzz_targets/ion.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* Released under the terms of the Apache 2.0 license with LLVM
|
||||||
|
* exception. See `LICENSE` for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#![no_main]
|
||||||
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
|
use regalloc2::fuzzing::func::Func;
|
||||||
|
|
||||||
|
fuzz_target!(|func: Func| {
|
||||||
|
let _ = env_logger::try_init();
|
||||||
|
log::trace!("func:\n{:?}", func);
|
||||||
|
let env = regalloc2::fuzzing::func::machine_env();
|
||||||
|
let _out = regalloc2::fuzzing::ion::run(&func, &env, false).expect("regalloc did not succeed");
|
||||||
|
});
|
||||||
48
fuzz/fuzz_targets/ion_checker.rs
Normal file
48
fuzz/fuzz_targets/ion_checker.rs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Released under the terms of the Apache 2.0 license with LLVM
|
||||||
|
* exception. See `LICENSE` for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#![no_main]
|
||||||
|
use libfuzzer_sys::arbitrary::{Arbitrary, Result, Unstructured};
|
||||||
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
|
use regalloc2::fuzzing::checker::Checker;
|
||||||
|
use regalloc2::fuzzing::func::{Func, Options};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct TestCase {
|
||||||
|
func: Func,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arbitrary for TestCase {
|
||||||
|
fn arbitrary(u: &mut Unstructured) -> Result<TestCase> {
|
||||||
|
Ok(TestCase {
|
||||||
|
func: Func::arbitrary_with_options(
|
||||||
|
u,
|
||||||
|
&Options {
|
||||||
|
reused_inputs: true,
|
||||||
|
fixed_regs: true,
|
||||||
|
clobbers: true,
|
||||||
|
control_flow: true,
|
||||||
|
reducible: false,
|
||||||
|
block_params: true,
|
||||||
|
always_local_uses: false,
|
||||||
|
reftypes: true,
|
||||||
|
},
|
||||||
|
)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fuzz_target!(|testcase: TestCase| {
|
||||||
|
let func = testcase.func;
|
||||||
|
let _ = env_logger::try_init();
|
||||||
|
log::trace!("func:\n{:?}", func);
|
||||||
|
let env = regalloc2::fuzzing::func::machine_env();
|
||||||
|
let out = regalloc2::fuzzing::ion::run(&func, &env, true).expect("regalloc did not succeed");
|
||||||
|
|
||||||
|
let mut checker = Checker::new(&func);
|
||||||
|
checker.prepare(&out);
|
||||||
|
checker.run().expect("checker failed");
|
||||||
|
});
|
||||||
81
fuzz/fuzz_targets/moves.rs
Normal file
81
fuzz/fuzz_targets/moves.rs
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* Released under the terms of the Apache 2.0 license with LLVM
|
||||||
|
* exception. See `LICENSE` for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#![no_main]
|
||||||
|
use libfuzzer_sys::arbitrary::{Arbitrary, Result, Unstructured};
|
||||||
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
|
use regalloc2::fuzzing::moves::ParallelMoves;
|
||||||
|
use regalloc2::{Allocation, PReg, RegClass};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct TestCase {
|
||||||
|
moves: Vec<(Allocation, Allocation)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arbitrary for TestCase {
|
||||||
|
fn arbitrary(u: &mut Unstructured) -> Result<Self> {
|
||||||
|
let mut ret = TestCase { moves: vec![] };
|
||||||
|
let mut written = HashSet::new();
|
||||||
|
while bool::arbitrary(u)? {
|
||||||
|
let reg1 = u.int_in_range(0..=30)?;
|
||||||
|
let reg2 = u.int_in_range(0..=30)?;
|
||||||
|
if written.contains(®2) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
written.insert(reg2);
|
||||||
|
ret.moves.push((
|
||||||
|
Allocation::reg(PReg::new(reg1, RegClass::Int)),
|
||||||
|
Allocation::reg(PReg::new(reg2, RegClass::Int)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fuzz_target!(|testcase: TestCase| {
|
||||||
|
let _ = env_logger::try_init();
|
||||||
|
let scratch = Allocation::reg(PReg::new(31, RegClass::Int));
|
||||||
|
let mut par = ParallelMoves::new(scratch);
|
||||||
|
for &(src, dst) in &testcase.moves {
|
||||||
|
par.add(src, dst, ());
|
||||||
|
}
|
||||||
|
let moves = par.resolve();
|
||||||
|
|
||||||
|
// Compute the final source reg for each dest reg in the original
|
||||||
|
// parallel-move set.
|
||||||
|
let mut final_src_per_dest: Vec<Option<usize>> = vec![None; 32];
|
||||||
|
for &(src, dst) in &testcase.moves {
|
||||||
|
if let (Some(preg_src), Some(preg_dst)) = (src.as_reg(), dst.as_reg()) {
|
||||||
|
final_src_per_dest[preg_dst.hw_enc()] = Some(preg_src.hw_enc());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simulate the sequence of moves.
|
||||||
|
let mut regfile: Vec<Option<usize>> = vec![None; 32];
|
||||||
|
for i in 0..32 {
|
||||||
|
regfile[i] = Some(i);
|
||||||
|
}
|
||||||
|
for (src, dst, _) in moves {
|
||||||
|
if let (Some(preg_src), Some(preg_dst)) = (src.as_reg(), dst.as_reg()) {
|
||||||
|
let data = regfile[preg_src.hw_enc()];
|
||||||
|
regfile[preg_dst.hw_enc()] = data;
|
||||||
|
} else {
|
||||||
|
panic!("Bad allocation in move list");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that the expected register-moves occurred.
|
||||||
|
// N.B.: range up to 31 (not 32) to skip scratch register.
|
||||||
|
for i in 0..31 {
|
||||||
|
if let Some(orig_src) = final_src_per_dest[i] {
|
||||||
|
assert_eq!(regfile[i], Some(orig_src));
|
||||||
|
} else {
|
||||||
|
// Should be untouched.
|
||||||
|
assert_eq!(regfile[i], Some(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
42
fuzz/fuzz_targets/ssagen.rs
Normal file
42
fuzz/fuzz_targets/ssagen.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Released under the terms of the Apache 2.0 license with LLVM
|
||||||
|
* exception. See `LICENSE` for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#![no_main]
|
||||||
|
use libfuzzer_sys::arbitrary::{Arbitrary, Result, Unstructured};
|
||||||
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
|
use regalloc2::fuzzing::cfg::CFGInfo;
|
||||||
|
use regalloc2::fuzzing::func::{Func, Options};
|
||||||
|
use regalloc2::fuzzing::ssa::validate_ssa;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct TestCase {
|
||||||
|
f: Func,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arbitrary for TestCase {
|
||||||
|
fn arbitrary(u: &mut Unstructured) -> Result<Self> {
|
||||||
|
Ok(TestCase {
|
||||||
|
f: Func::arbitrary_with_options(
|
||||||
|
u,
|
||||||
|
&Options {
|
||||||
|
reused_inputs: true,
|
||||||
|
fixed_regs: true,
|
||||||
|
clobbers: true,
|
||||||
|
control_flow: true,
|
||||||
|
reducible: false,
|
||||||
|
always_local_uses: false,
|
||||||
|
block_params: true,
|
||||||
|
reftypes: true,
|
||||||
|
},
|
||||||
|
)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fuzz_target!(|t: TestCase| {
|
||||||
|
let cfginfo = CFGInfo::new(&t.f).expect("could not create CFG info");
|
||||||
|
validate_ssa(&t.f, &cfginfo).expect("invalid SSA");
|
||||||
|
});
|
||||||
BIN
fuzz/smoketest/ion_checker.bin
Normal file
BIN
fuzz/smoketest/ion_checker.bin
Normal file
Binary file not shown.
153
src/cfg.rs
Normal file
153
src/cfg.rs
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
/*
|
||||||
|
* Released under the terms of the Apache 2.0 license with LLVM
|
||||||
|
* exception. See `LICENSE` for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! Lightweight CFG analyses.
|
||||||
|
|
||||||
|
use crate::{domtree, postorder, Block, Function, Inst, OperandKind, ProgPoint, RegAllocError};
|
||||||
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct CFGInfo {
|
||||||
|
/// Postorder traversal of blocks.
|
||||||
|
pub postorder: Vec<Block>,
|
||||||
|
/// Domtree parents, indexed by block.
|
||||||
|
pub domtree: Vec<Block>,
|
||||||
|
/// For each instruction, the block it belongs to.
|
||||||
|
pub insn_block: Vec<Block>,
|
||||||
|
/// For each vreg, the instruction that defines it, if any.
|
||||||
|
pub vreg_def_inst: Vec<Inst>,
|
||||||
|
/// For each vreg, the block that defines it as a blockparam, if
|
||||||
|
/// any. (Every vreg must have a valid entry in either
|
||||||
|
/// `vreg_def_inst` or `vreg_def_blockparam`.)
|
||||||
|
pub vreg_def_blockparam: Vec<(Block, u32)>,
|
||||||
|
/// For each block, the first instruction.
|
||||||
|
pub block_entry: Vec<ProgPoint>,
|
||||||
|
/// For each block, the last instruction.
|
||||||
|
pub block_exit: Vec<ProgPoint>,
|
||||||
|
/// For each block, what is the approximate loop depth?
|
||||||
|
///
|
||||||
|
/// This measure is fully precise iff the input CFG is reducible
|
||||||
|
/// and blocks are in RPO, so that loop backedges are precisely
|
||||||
|
/// those whose block target indices are less than their source
|
||||||
|
/// indices. Otherwise, it will be approximate, but should still
|
||||||
|
/// be usable for heuristic purposes.
|
||||||
|
pub approx_loop_depth: Vec<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CFGInfo {
|
||||||
|
pub fn new<F: Function>(f: &F) -> Result<CFGInfo, RegAllocError> {
|
||||||
|
let postorder = postorder::calculate(f.num_blocks(), f.entry_block(), |block| {
|
||||||
|
f.block_succs(block)
|
||||||
|
});
|
||||||
|
let domtree = domtree::calculate(
|
||||||
|
f.num_blocks(),
|
||||||
|
|block| f.block_preds(block),
|
||||||
|
&postorder[..],
|
||||||
|
f.entry_block(),
|
||||||
|
);
|
||||||
|
let mut insn_block = vec![Block::invalid(); f.num_insts()];
|
||||||
|
let mut vreg_def_inst = vec![Inst::invalid(); f.num_vregs()];
|
||||||
|
let mut vreg_def_blockparam = vec![(Block::invalid(), 0); f.num_vregs()];
|
||||||
|
let mut block_entry = vec![ProgPoint::before(Inst::invalid()); f.num_blocks()];
|
||||||
|
let mut block_exit = vec![ProgPoint::before(Inst::invalid()); f.num_blocks()];
|
||||||
|
let mut backedge_in = vec![0; f.num_blocks()];
|
||||||
|
let mut backedge_out = vec![0; f.num_blocks()];
|
||||||
|
|
||||||
|
for block in 0..f.num_blocks() {
|
||||||
|
let block = Block::new(block);
|
||||||
|
for (i, param) in f.block_params(block).iter().enumerate() {
|
||||||
|
vreg_def_blockparam[param.vreg()] = (block, i as u32);
|
||||||
|
}
|
||||||
|
for inst in f.block_insns(block).iter() {
|
||||||
|
insn_block[inst.index()] = block;
|
||||||
|
for operand in f.inst_operands(inst) {
|
||||||
|
match operand.kind() {
|
||||||
|
OperandKind::Def => {
|
||||||
|
vreg_def_inst[operand.vreg().vreg()] = inst;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
block_entry[block.index()] = ProgPoint::before(f.block_insns(block).first());
|
||||||
|
block_exit[block.index()] = ProgPoint::after(f.block_insns(block).last());
|
||||||
|
|
||||||
|
// Check critical edge condition: if there is more than
|
||||||
|
// one predecessor, each must have only one successor
|
||||||
|
// (this block).
|
||||||
|
let preds = f.block_preds(block).len() + if block == f.entry_block() { 1 } else { 0 };
|
||||||
|
if preds > 1 {
|
||||||
|
for &pred in f.block_preds(block) {
|
||||||
|
let succs = f.block_succs(pred).len();
|
||||||
|
if succs > 1 {
|
||||||
|
return Err(RegAllocError::CritEdge(pred, block));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check branch-arg condition: if any successors have more
|
||||||
|
// than one predecessor (given above, there will only be
|
||||||
|
// one such successor), then the last instruction of this
|
||||||
|
// block (the branch) cannot have any args other than the
|
||||||
|
// blockparams.
|
||||||
|
let mut require_no_branch_args = false;
|
||||||
|
for &succ in f.block_succs(block) {
|
||||||
|
let preds = f.block_preds(succ).len() + if succ == f.entry_block() { 1 } else { 0 };
|
||||||
|
if preds > 1 {
|
||||||
|
require_no_branch_args = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if require_no_branch_args {
|
||||||
|
let last = f.block_insns(block).last();
|
||||||
|
if f.branch_blockparam_arg_offset(block, last) > 0 {
|
||||||
|
return Err(RegAllocError::DisallowedBranchArg(last));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for &succ in f.block_succs(block) {
|
||||||
|
if succ.index() <= block.index() {
|
||||||
|
backedge_in[succ.index()] += 1;
|
||||||
|
backedge_out[block.index()] += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut approx_loop_depth = vec![];
|
||||||
|
let mut backedge_stack: SmallVec<[usize; 4]> = smallvec![];
|
||||||
|
let mut cur_depth = 0;
|
||||||
|
for block in 0..f.num_blocks() {
|
||||||
|
if backedge_in[block] > 0 {
|
||||||
|
cur_depth += 1;
|
||||||
|
backedge_stack.push(backedge_in[block]);
|
||||||
|
}
|
||||||
|
|
||||||
|
approx_loop_depth.push(cur_depth);
|
||||||
|
|
||||||
|
while backedge_stack.len() > 0 && backedge_out[block] > 0 {
|
||||||
|
backedge_out[block] -= 1;
|
||||||
|
*backedge_stack.last_mut().unwrap() -= 1;
|
||||||
|
if *backedge_stack.last().unwrap() == 0 {
|
||||||
|
cur_depth -= 1;
|
||||||
|
backedge_stack.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(CFGInfo {
|
||||||
|
postorder,
|
||||||
|
domtree,
|
||||||
|
insn_block,
|
||||||
|
vreg_def_inst,
|
||||||
|
vreg_def_blockparam,
|
||||||
|
block_entry,
|
||||||
|
block_exit,
|
||||||
|
approx_loop_depth,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dominates(&self, a: Block, b: Block) -> bool {
|
||||||
|
domtree::dominates(&self.domtree[..], a, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
745
src/checker.rs
Normal file
745
src/checker.rs
Normal file
@@ -0,0 +1,745 @@
|
|||||||
|
/*
|
||||||
|
* The following code is derived from `lib/src/checker.rs` in the
|
||||||
|
* regalloc.rs project
|
||||||
|
* (https://github.com/bytecodealliance/regalloc.rs). regalloc.rs is
|
||||||
|
* also licensed under Apache-2.0 with the LLVM exception, as the rest
|
||||||
|
* of regalloc2's non-Ion-derived code is.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! Checker: verifies that spills/reloads/moves retain equivalent
|
||||||
|
//! dataflow to original, VReg-based code.
|
||||||
|
//!
|
||||||
|
//! The basic idea is that we track symbolic values as they flow
|
||||||
|
//! through spills and reloads. The symbolic values represent
|
||||||
|
//! particular virtual registers in the original function body
|
||||||
|
//! presented to the register allocator. Any instruction in the
|
||||||
|
//! original function body (i.e., not added by the allocator)
|
||||||
|
//! conceptually generates a symbolic value "Vn" when storing to (or
|
||||||
|
//! modifying) a virtual register.
|
||||||
|
//!
|
||||||
|
//! These symbolic values are precise but partial: in other words, if
|
||||||
|
//! a physical register is described as containing a virtual register
|
||||||
|
//! at a program point, it must actually contain the value of this
|
||||||
|
//! register (modulo any analysis bugs); but it may resolve to
|
||||||
|
//! `Conflicts` even in cases where one *could* statically prove that
|
||||||
|
//! it contains a certain register, because the analysis is not
|
||||||
|
//! perfectly path-sensitive or value-sensitive. However, all
|
||||||
|
//! assignments *produced by our register allocator* should be
|
||||||
|
//! analyzed fully precisely.
|
||||||
|
//!
|
||||||
|
//! Operand constraints (fixed register, register, any) are also checked
|
||||||
|
//! at each operand.
|
||||||
|
//!
|
||||||
|
//! The dataflow analysis state at each program point is:
|
||||||
|
//!
|
||||||
|
//! - map of: Allocation -> lattice value (top > Vn symbols (unordered) > bottom)
|
||||||
|
//!
|
||||||
|
//! And the transfer functions for instructions are (where `A` is the
|
||||||
|
//! above map from allocated physical registers to symbolic values):
|
||||||
|
//!
|
||||||
|
//! - `Edit::Move` inserted by RA: [ alloc_d := alloc_s ]
|
||||||
|
//!
|
||||||
|
//! A[alloc_d] := A[alloc_s]
|
||||||
|
//!
|
||||||
|
//! - phi-node [ V_i := phi block_j:V_j, block_k:V_k, ... ]
|
||||||
|
//! with allocations [ A_i := phi block_j:A_j, block_k:A_k, ... ]
|
||||||
|
//! (N.B.: phi-nodes are not semantically present in the final
|
||||||
|
//! machine code, but we include their allocations so that this
|
||||||
|
//! checker can work)
|
||||||
|
//!
|
||||||
|
//! A[A_i] := meet(A[A_j], A[A_k], ...)
|
||||||
|
//!
|
||||||
|
//! - statement in pre-regalloc function [ V_i := op V_j, V_k, ... ]
|
||||||
|
//! with allocated form [ A_i := op A_j, A_k, ... ]
|
||||||
|
//!
|
||||||
|
//! A[A_i] := `V_i`
|
||||||
|
//!
|
||||||
|
//! In other words, a statement, even after allocation, generates
|
||||||
|
//! a symbol that corresponds to its original virtual-register
|
||||||
|
//! def.
|
||||||
|
//!
|
||||||
|
//! (N.B.: moves in pre-regalloc function fall into this last case
|
||||||
|
//! -- they are "just another operation" and generate a new
|
||||||
|
//! symbol)
|
||||||
|
//!
|
||||||
|
//! At control-flow join points, the symbols meet using a very simple
|
||||||
|
//! lattice meet-function: two different symbols in the same
|
||||||
|
//! allocation meet to "conflicted"; otherwise, the symbol meets with
|
||||||
|
//! itself to produce itself (reflexivity).
|
||||||
|
//!
|
||||||
|
//! To check correctness, we first find the dataflow fixpoint with the
|
||||||
|
//! above lattice and transfer/meet functions. Then, at each op, we
|
||||||
|
//! examine the dataflow solution at the preceding program point, and
|
||||||
|
//! check that the allocation for each op arg (input/use) contains the
|
||||||
|
//! symbol corresponding to the original virtual register specified
|
||||||
|
//! for this arg.
|
||||||
|
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
Allocation, AllocationKind, Block, Edit, Function, Inst, InstPosition, Operand,
|
||||||
|
OperandConstraint, OperandKind, OperandPos, Output, PReg, ProgPoint, SpillSlot, VReg,
|
||||||
|
};
|
||||||
|
|
||||||
|
use std::collections::{HashMap, HashSet, VecDeque};
|
||||||
|
use std::default::Default;
|
||||||
|
use std::hash::Hash;
|
||||||
|
use std::result::Result;
|
||||||
|
|
||||||
|
/// A set of errors detected by the regalloc checker.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct CheckerErrors {
|
||||||
|
errors: Vec<CheckerError>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A single error detected by the regalloc checker.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum CheckerError {
|
||||||
|
MissingAllocation {
|
||||||
|
inst: Inst,
|
||||||
|
op: Operand,
|
||||||
|
},
|
||||||
|
UnknownValueInAllocation {
|
||||||
|
inst: Inst,
|
||||||
|
op: Operand,
|
||||||
|
alloc: Allocation,
|
||||||
|
},
|
||||||
|
ConflictedValueInAllocation {
|
||||||
|
inst: Inst,
|
||||||
|
op: Operand,
|
||||||
|
alloc: Allocation,
|
||||||
|
},
|
||||||
|
IncorrectValueInAllocation {
|
||||||
|
inst: Inst,
|
||||||
|
op: Operand,
|
||||||
|
alloc: Allocation,
|
||||||
|
actual: VReg,
|
||||||
|
},
|
||||||
|
ConstraintViolated {
|
||||||
|
inst: Inst,
|
||||||
|
op: Operand,
|
||||||
|
alloc: Allocation,
|
||||||
|
},
|
||||||
|
AllocationIsNotReg {
|
||||||
|
inst: Inst,
|
||||||
|
op: Operand,
|
||||||
|
alloc: Allocation,
|
||||||
|
},
|
||||||
|
AllocationIsNotFixedReg {
|
||||||
|
inst: Inst,
|
||||||
|
op: Operand,
|
||||||
|
alloc: Allocation,
|
||||||
|
},
|
||||||
|
AllocationIsNotReuse {
|
||||||
|
inst: Inst,
|
||||||
|
op: Operand,
|
||||||
|
alloc: Allocation,
|
||||||
|
expected_alloc: Allocation,
|
||||||
|
},
|
||||||
|
AllocationIsNotStack {
|
||||||
|
inst: Inst,
|
||||||
|
op: Operand,
|
||||||
|
alloc: Allocation,
|
||||||
|
},
|
||||||
|
ConflictedValueInStackmap {
|
||||||
|
inst: Inst,
|
||||||
|
slot: SpillSlot,
|
||||||
|
},
|
||||||
|
NonRefValueInStackmap {
|
||||||
|
inst: Inst,
|
||||||
|
slot: SpillSlot,
|
||||||
|
vreg: VReg,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Abstract state for an allocation.
|
||||||
|
///
|
||||||
|
/// Forms a lattice with \top (`Unknown`), \bot (`Conflicted`), and a
|
||||||
|
/// number of mutually unordered value-points in between, one per real
|
||||||
|
/// or virtual register. Any two different registers meet to \bot.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
enum CheckerValue {
|
||||||
|
/// "top" value: this storage slot has no known value.
|
||||||
|
Unknown,
|
||||||
|
/// "bottom" value: this storage slot has a conflicted value.
|
||||||
|
Conflicted,
|
||||||
|
/// Reg: this storage slot has a value that originated as a def
|
||||||
|
/// into the given virtual register.
|
||||||
|
///
|
||||||
|
/// The boolean flag indicates whether the value is
|
||||||
|
/// reference-typed.
|
||||||
|
Reg(VReg, bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CheckerValue {
|
||||||
|
fn default() -> CheckerValue {
|
||||||
|
CheckerValue::Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CheckerValue {
|
||||||
|
/// Meet function of the abstract-interpretation value lattice.
|
||||||
|
fn meet(&self, other: &CheckerValue) -> CheckerValue {
|
||||||
|
match (self, other) {
|
||||||
|
(&CheckerValue::Unknown, _) => *other,
|
||||||
|
(_, &CheckerValue::Unknown) => *self,
|
||||||
|
(&CheckerValue::Conflicted, _) => *self,
|
||||||
|
(_, &CheckerValue::Conflicted) => *other,
|
||||||
|
(&CheckerValue::Reg(r1, ref1), &CheckerValue::Reg(r2, ref2))
|
||||||
|
if r1 == r2 && ref1 == ref2 =>
|
||||||
|
{
|
||||||
|
CheckerValue::Reg(r1, ref1)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
log::trace!("{:?} and {:?} meet to Conflicted", self, other);
|
||||||
|
CheckerValue::Conflicted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// State that steps through program points as we scan over the instruction stream.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
struct CheckerState {
|
||||||
|
allocations: HashMap<Allocation, CheckerValue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CheckerState {
|
||||||
|
fn default() -> CheckerState {
|
||||||
|
CheckerState {
|
||||||
|
allocations: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for CheckerValue {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
CheckerValue::Unknown => write!(f, "?"),
|
||||||
|
CheckerValue::Conflicted => write!(f, "!"),
|
||||||
|
CheckerValue::Reg(r, false) => write!(f, "{}", r),
|
||||||
|
CheckerValue::Reg(r, true) => write!(f, "{}/ref", r),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn merge_map<K: Copy + Clone + PartialEq + Eq + Hash>(
|
||||||
|
into: &mut HashMap<K, CheckerValue>,
|
||||||
|
from: &HashMap<K, CheckerValue>,
|
||||||
|
) {
|
||||||
|
for (k, v) in from {
|
||||||
|
let into_v = into.entry(*k).or_insert(Default::default());
|
||||||
|
let merged = into_v.meet(v);
|
||||||
|
*into_v = merged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CheckerState {
|
||||||
|
/// Create a new checker state.
|
||||||
|
fn new() -> CheckerState {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Merge this checker state with another at a CFG join-point.
|
||||||
|
fn meet_with(&mut self, other: &CheckerState) {
|
||||||
|
merge_map(&mut self.allocations, &other.allocations);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_val(
|
||||||
|
&self,
|
||||||
|
inst: Inst,
|
||||||
|
op: Operand,
|
||||||
|
alloc: Allocation,
|
||||||
|
val: CheckerValue,
|
||||||
|
allocs: &[Allocation],
|
||||||
|
) -> Result<(), CheckerError> {
|
||||||
|
if alloc == Allocation::none() {
|
||||||
|
return Err(CheckerError::MissingAllocation { inst, op });
|
||||||
|
}
|
||||||
|
|
||||||
|
match val {
|
||||||
|
CheckerValue::Unknown => {
|
||||||
|
return Err(CheckerError::UnknownValueInAllocation { inst, op, alloc });
|
||||||
|
}
|
||||||
|
CheckerValue::Conflicted => {
|
||||||
|
return Err(CheckerError::ConflictedValueInAllocation { inst, op, alloc });
|
||||||
|
}
|
||||||
|
CheckerValue::Reg(r, _) if r != op.vreg() => {
|
||||||
|
return Err(CheckerError::IncorrectValueInAllocation {
|
||||||
|
inst,
|
||||||
|
op,
|
||||||
|
alloc,
|
||||||
|
actual: r,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.check_constraint(inst, op, alloc, allocs)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check an instruction against this state. This must be called
|
||||||
|
/// twice: once with `InstPosition::Before`, and once with
|
||||||
|
/// `InstPosition::After` (after updating state with defs).
|
||||||
|
fn check(&self, pos: InstPosition, checkinst: &CheckerInst) -> Result<(), CheckerError> {
|
||||||
|
match checkinst {
|
||||||
|
&CheckerInst::Op {
|
||||||
|
inst,
|
||||||
|
ref operands,
|
||||||
|
ref allocs,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
// Skip Use-checks at the After point if there are any
|
||||||
|
// reused inputs: the Def which reuses the input
|
||||||
|
// happens early.
|
||||||
|
let has_reused_input = operands
|
||||||
|
.iter()
|
||||||
|
.any(|op| matches!(op.constraint(), OperandConstraint::Reuse(_)));
|
||||||
|
if has_reused_input && pos == InstPosition::After {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each operand, check (i) that the allocation
|
||||||
|
// contains the expected vreg, and (ii) that it meets
|
||||||
|
// the requirements of the OperandConstraint.
|
||||||
|
for (op, alloc) in operands.iter().zip(allocs.iter()) {
|
||||||
|
let is_here = match (op.pos(), pos) {
|
||||||
|
(OperandPos::Early, InstPosition::Before) => true,
|
||||||
|
(OperandPos::Late, InstPosition::After) => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
if !is_here {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if op.kind() == OperandKind::Def {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let val = self
|
||||||
|
.allocations
|
||||||
|
.get(alloc)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or(Default::default());
|
||||||
|
log::trace!(
|
||||||
|
"checker: checkinst {:?}: op {:?}, alloc {:?}, checker value {:?}",
|
||||||
|
checkinst,
|
||||||
|
op,
|
||||||
|
alloc,
|
||||||
|
val
|
||||||
|
);
|
||||||
|
self.check_val(inst, *op, *alloc, val, allocs)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&CheckerInst::Safepoint { inst, ref slots } => {
|
||||||
|
for &slot in slots {
|
||||||
|
let alloc = Allocation::stack(slot);
|
||||||
|
let val = self
|
||||||
|
.allocations
|
||||||
|
.get(&alloc)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or(Default::default());
|
||||||
|
log::trace!(
|
||||||
|
"checker: checkinst {:?}: safepoint slot {}, checker value {:?}",
|
||||||
|
checkinst,
|
||||||
|
slot,
|
||||||
|
val
|
||||||
|
);
|
||||||
|
|
||||||
|
match val {
|
||||||
|
CheckerValue::Unknown => {}
|
||||||
|
CheckerValue::Conflicted => {
|
||||||
|
return Err(CheckerError::ConflictedValueInStackmap { inst, slot });
|
||||||
|
}
|
||||||
|
CheckerValue::Reg(vreg, false) => {
|
||||||
|
return Err(CheckerError::NonRefValueInStackmap { inst, slot, vreg });
|
||||||
|
}
|
||||||
|
CheckerValue::Reg(_, true) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update according to instruction.
|
||||||
|
fn update<'a, F: Function>(&mut self, checkinst: &CheckerInst, checker: &Checker<'a, F>) {
|
||||||
|
match checkinst {
|
||||||
|
&CheckerInst::Move { into, from } => {
|
||||||
|
let val = self
|
||||||
|
.allocations
|
||||||
|
.get(&from)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or(Default::default());
|
||||||
|
log::trace!(
|
||||||
|
"checker: checkinst {:?} updating: move {:?} -> {:?} val {:?}",
|
||||||
|
checkinst,
|
||||||
|
from,
|
||||||
|
into,
|
||||||
|
val
|
||||||
|
);
|
||||||
|
self.allocations.insert(into, val);
|
||||||
|
}
|
||||||
|
&CheckerInst::Op {
|
||||||
|
ref operands,
|
||||||
|
ref allocs,
|
||||||
|
ref clobbers,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
for (op, alloc) in operands.iter().zip(allocs.iter()) {
|
||||||
|
if op.kind() != OperandKind::Def {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let reftyped = checker.reftyped_vregs.contains(&op.vreg());
|
||||||
|
self.allocations
|
||||||
|
.insert(*alloc, CheckerValue::Reg(op.vreg(), reftyped));
|
||||||
|
}
|
||||||
|
for clobber in clobbers {
|
||||||
|
self.allocations.remove(&Allocation::reg(*clobber));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&CheckerInst::DefAlloc { alloc, vreg } => {
|
||||||
|
let reftyped = checker.reftyped_vregs.contains(&vreg);
|
||||||
|
self.allocations
|
||||||
|
.insert(alloc, CheckerValue::Reg(vreg, reftyped));
|
||||||
|
}
|
||||||
|
&CheckerInst::Safepoint { ref slots, .. } => {
|
||||||
|
for (alloc, value) in &mut self.allocations {
|
||||||
|
if let CheckerValue::Reg(_, true) = *value {
|
||||||
|
if alloc.is_reg() {
|
||||||
|
*value = CheckerValue::Conflicted;
|
||||||
|
} else if alloc.is_stack() && !slots.contains(&alloc.as_stack().unwrap()) {
|
||||||
|
*value = CheckerValue::Conflicted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_constraint(
|
||||||
|
&self,
|
||||||
|
inst: Inst,
|
||||||
|
op: Operand,
|
||||||
|
alloc: Allocation,
|
||||||
|
allocs: &[Allocation],
|
||||||
|
) -> Result<(), CheckerError> {
|
||||||
|
match op.constraint() {
|
||||||
|
OperandConstraint::Any => {}
|
||||||
|
OperandConstraint::Reg => {
|
||||||
|
if alloc.kind() != AllocationKind::Reg {
|
||||||
|
return Err(CheckerError::AllocationIsNotReg { inst, op, alloc });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OperandConstraint::Stack => {
|
||||||
|
if alloc.kind() != AllocationKind::Stack {
|
||||||
|
return Err(CheckerError::AllocationIsNotStack { inst, op, alloc });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OperandConstraint::FixedReg(preg) => {
|
||||||
|
if alloc != Allocation::reg(preg) {
|
||||||
|
return Err(CheckerError::AllocationIsNotFixedReg { inst, op, alloc });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OperandConstraint::Reuse(idx) => {
|
||||||
|
if alloc.kind() != AllocationKind::Reg {
|
||||||
|
return Err(CheckerError::AllocationIsNotReg { inst, op, alloc });
|
||||||
|
}
|
||||||
|
if alloc != allocs[idx] {
|
||||||
|
return Err(CheckerError::AllocationIsNotReuse {
|
||||||
|
inst,
|
||||||
|
op,
|
||||||
|
alloc,
|
||||||
|
expected_alloc: allocs[idx],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An instruction representation in the checker's BB summary.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) enum CheckerInst {
|
||||||
|
/// A move between allocations (these could be registers or
|
||||||
|
/// spillslots).
|
||||||
|
Move { into: Allocation, from: Allocation },
|
||||||
|
|
||||||
|
/// A regular instruction with fixed use and def slots. Contains
|
||||||
|
/// both the original operands (as given to the regalloc) and the
|
||||||
|
/// allocation results.
|
||||||
|
Op {
|
||||||
|
inst: Inst,
|
||||||
|
operands: Vec<Operand>,
|
||||||
|
allocs: Vec<Allocation>,
|
||||||
|
clobbers: Vec<PReg>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Define an allocation's contents. Like BlockParams but for one
|
||||||
|
/// allocation. Used sometimes when moves are elided but ownership
|
||||||
|
/// of a value is logically transferred to a new vreg.
|
||||||
|
DefAlloc { alloc: Allocation, vreg: VReg },
|
||||||
|
|
||||||
|
/// A safepoint, with the given SpillSlots specified as containing
|
||||||
|
/// reftyped values. All other reftyped values become invalid.
|
||||||
|
Safepoint { inst: Inst, slots: Vec<SpillSlot> },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Checker<'a, F: Function> {
|
||||||
|
f: &'a F,
|
||||||
|
bb_in: HashMap<Block, CheckerState>,
|
||||||
|
bb_insts: HashMap<Block, Vec<CheckerInst>>,
|
||||||
|
reftyped_vregs: HashSet<VReg>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, F: Function> Checker<'a, F> {
|
||||||
|
/// Create a new checker for the given function, initializing CFG
|
||||||
|
/// info immediately. The client should call the `add_*()`
|
||||||
|
/// methods to add abstract instructions to each BB before
|
||||||
|
/// invoking `run()` to check for errors.
|
||||||
|
pub fn new(f: &'a F) -> Checker<'a, F> {
|
||||||
|
let mut bb_in = HashMap::new();
|
||||||
|
let mut bb_insts = HashMap::new();
|
||||||
|
let mut reftyped_vregs = HashSet::new();
|
||||||
|
|
||||||
|
for block in 0..f.num_blocks() {
|
||||||
|
let block = Block::new(block);
|
||||||
|
bb_in.insert(block, Default::default());
|
||||||
|
bb_insts.insert(block, vec![]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for &vreg in f.reftype_vregs() {
|
||||||
|
reftyped_vregs.insert(vreg);
|
||||||
|
}
|
||||||
|
|
||||||
|
Checker {
|
||||||
|
f,
|
||||||
|
bb_in,
|
||||||
|
bb_insts,
|
||||||
|
reftyped_vregs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the list of checker instructions based on the given func
|
||||||
|
/// and allocation results.
|
||||||
|
pub fn prepare(&mut self, out: &Output) {
|
||||||
|
log::trace!("checker: out = {:?}", out);
|
||||||
|
// Preprocess safepoint stack-maps into per-inst vecs.
|
||||||
|
let mut safepoint_slots: HashMap<Inst, Vec<SpillSlot>> = HashMap::new();
|
||||||
|
for &(progpoint, slot) in &out.safepoint_slots {
|
||||||
|
safepoint_slots
|
||||||
|
.entry(progpoint.inst())
|
||||||
|
.or_insert_with(|| vec![])
|
||||||
|
.push(slot);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each original instruction, create an `Op`.
|
||||||
|
let mut last_inst = None;
|
||||||
|
let mut insert_idx = 0;
|
||||||
|
for block in 0..self.f.num_blocks() {
|
||||||
|
let block = Block::new(block);
|
||||||
|
for inst in self.f.block_insns(block).iter() {
|
||||||
|
assert!(last_inst.is_none() || inst > last_inst.unwrap());
|
||||||
|
last_inst = Some(inst);
|
||||||
|
|
||||||
|
// Any inserted edits before instruction.
|
||||||
|
self.handle_edits(block, out, &mut insert_idx, ProgPoint::before(inst));
|
||||||
|
|
||||||
|
// If this is a safepoint, then check the spillslots at this point.
|
||||||
|
if self.f.requires_refs_on_stack(inst) {
|
||||||
|
let slots = safepoint_slots.remove(&inst).unwrap_or_else(|| vec![]);
|
||||||
|
|
||||||
|
let checkinst = CheckerInst::Safepoint { inst, slots };
|
||||||
|
self.bb_insts.get_mut(&block).unwrap().push(checkinst);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if this is a branch: the blockparams do not
|
||||||
|
// exist in post-regalloc code, and the edge-moves
|
||||||
|
// have to be inserted before the branch rather than
|
||||||
|
// after.
|
||||||
|
if !self.f.is_branch(inst) {
|
||||||
|
// Instruction itself.
|
||||||
|
let operands: Vec<_> = self.f.inst_operands(inst).iter().cloned().collect();
|
||||||
|
let allocs: Vec<_> = out.inst_allocs(inst).iter().cloned().collect();
|
||||||
|
let clobbers: Vec<_> = self.f.inst_clobbers(inst).iter().cloned().collect();
|
||||||
|
let checkinst = CheckerInst::Op {
|
||||||
|
inst,
|
||||||
|
operands,
|
||||||
|
allocs,
|
||||||
|
clobbers,
|
||||||
|
};
|
||||||
|
log::trace!("checker: adding inst {:?}", checkinst);
|
||||||
|
self.bb_insts.get_mut(&block).unwrap().push(checkinst);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any inserted edits after instruction.
|
||||||
|
self.handle_edits(block, out, &mut insert_idx, ProgPoint::after(inst));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_edits(&mut self, block: Block, out: &Output, idx: &mut usize, pos: ProgPoint) {
|
||||||
|
while *idx < out.edits.len() && out.edits[*idx].0 <= pos {
|
||||||
|
let &(edit_pos, ref edit) = &out.edits[*idx];
|
||||||
|
*idx += 1;
|
||||||
|
if edit_pos < pos {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
log::trace!("checker: adding edit {:?} at pos {:?}", edit, pos);
|
||||||
|
match edit {
|
||||||
|
&Edit::Move { from, to, to_vreg } => {
|
||||||
|
self.bb_insts
|
||||||
|
.get_mut(&block)
|
||||||
|
.unwrap()
|
||||||
|
.push(CheckerInst::Move { into: to, from });
|
||||||
|
if let Some(vreg) = to_vreg {
|
||||||
|
self.bb_insts
|
||||||
|
.get_mut(&block)
|
||||||
|
.unwrap()
|
||||||
|
.push(CheckerInst::DefAlloc { alloc: to, vreg });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&Edit::DefAlloc { alloc, vreg } => {
|
||||||
|
self.bb_insts
|
||||||
|
.get_mut(&block)
|
||||||
|
.unwrap()
|
||||||
|
.push(CheckerInst::DefAlloc { alloc, vreg });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Perform the dataflow analysis to compute checker state at each BB entry.
|
||||||
|
fn analyze(&mut self) {
|
||||||
|
let mut queue = VecDeque::new();
|
||||||
|
let mut queue_set = HashSet::new();
|
||||||
|
for block in 0..self.f.num_blocks() {
|
||||||
|
let block = Block::new(block);
|
||||||
|
queue.push_back(block);
|
||||||
|
queue_set.insert(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
while !queue.is_empty() {
|
||||||
|
let block = queue.pop_front().unwrap();
|
||||||
|
queue_set.remove(&block);
|
||||||
|
let mut state = self.bb_in.get(&block).cloned().unwrap();
|
||||||
|
log::trace!("analyze: block {} has state {:?}", block.index(), state);
|
||||||
|
for inst in self.bb_insts.get(&block).unwrap() {
|
||||||
|
state.update(inst, self);
|
||||||
|
log::trace!("analyze: inst {:?} -> state {:?}", inst, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
for &succ in self.f.block_succs(block) {
|
||||||
|
let cur_succ_in = self.bb_in.get(&succ).unwrap();
|
||||||
|
let mut new_state = state.clone();
|
||||||
|
new_state.meet_with(cur_succ_in);
|
||||||
|
let changed = &new_state != cur_succ_in;
|
||||||
|
if changed {
|
||||||
|
log::trace!(
|
||||||
|
"analyze: block {} state changed from {:?} to {:?}; pushing onto queue",
|
||||||
|
succ.index(),
|
||||||
|
cur_succ_in,
|
||||||
|
new_state
|
||||||
|
);
|
||||||
|
self.bb_in.insert(succ, new_state);
|
||||||
|
if !queue_set.contains(&succ) {
|
||||||
|
queue.push_back(succ);
|
||||||
|
queue_set.insert(succ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Using BB-start state computed by `analyze()`, step the checker state
|
||||||
|
/// through each BB and check each instruction's register allocations
|
||||||
|
/// for errors.
|
||||||
|
fn find_errors(&self) -> Result<(), CheckerErrors> {
|
||||||
|
let mut errors = vec![];
|
||||||
|
for (block, input) in &self.bb_in {
|
||||||
|
let mut state = input.clone();
|
||||||
|
for inst in self.bb_insts.get(block).unwrap() {
|
||||||
|
if let Err(e) = state.check(InstPosition::Before, inst) {
|
||||||
|
log::trace!("Checker error: {:?}", e);
|
||||||
|
errors.push(e);
|
||||||
|
}
|
||||||
|
state.update(inst, self);
|
||||||
|
if let Err(e) = state.check(InstPosition::After, inst) {
|
||||||
|
log::trace!("Checker error: {:?}", e);
|
||||||
|
errors.push(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.is_empty() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(CheckerErrors { errors })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find any errors, returning `Err(CheckerErrors)` with all errors found
|
||||||
|
/// or `Ok(())` otherwise.
|
||||||
|
pub fn run(mut self) -> Result<(), CheckerErrors> {
|
||||||
|
self.analyze();
|
||||||
|
let result = self.find_errors();
|
||||||
|
|
||||||
|
log::trace!("=== CHECKER RESULT ===");
|
||||||
|
fn print_state(state: &CheckerState) {
|
||||||
|
let mut s = vec![];
|
||||||
|
for (alloc, state) in &state.allocations {
|
||||||
|
s.push(format!("{} := {}", alloc, state));
|
||||||
|
}
|
||||||
|
log::trace!(" {{ {} }}", s.join(", "))
|
||||||
|
}
|
||||||
|
for vreg in self.f.reftype_vregs() {
|
||||||
|
log::trace!(" REF: {}", vreg);
|
||||||
|
}
|
||||||
|
for bb in 0..self.f.num_blocks() {
|
||||||
|
let bb = Block::new(bb);
|
||||||
|
log::trace!("block{}:", bb.index());
|
||||||
|
let insts = self.bb_insts.get(&bb).unwrap();
|
||||||
|
let mut state = self.bb_in.get(&bb).unwrap().clone();
|
||||||
|
print_state(&state);
|
||||||
|
for inst in insts {
|
||||||
|
match inst {
|
||||||
|
&CheckerInst::Op {
|
||||||
|
inst,
|
||||||
|
ref operands,
|
||||||
|
ref allocs,
|
||||||
|
ref clobbers,
|
||||||
|
} => {
|
||||||
|
log::trace!(
|
||||||
|
" inst{}: {:?} ({:?}) clobbers:{:?}",
|
||||||
|
inst.index(),
|
||||||
|
operands,
|
||||||
|
allocs,
|
||||||
|
clobbers
|
||||||
|
);
|
||||||
|
}
|
||||||
|
&CheckerInst::Move { from, into } => {
|
||||||
|
log::trace!(" {} -> {}", from, into);
|
||||||
|
}
|
||||||
|
&CheckerInst::DefAlloc { alloc, vreg } => {
|
||||||
|
log::trace!(" defalloc: {}:{}", vreg, alloc);
|
||||||
|
}
|
||||||
|
&CheckerInst::Safepoint { ref slots, .. } => {
|
||||||
|
let mut slotargs = vec![];
|
||||||
|
for &slot in slots {
|
||||||
|
slotargs.push(format!("{}", slot));
|
||||||
|
}
|
||||||
|
log::trace!(" safepoint: {}", slotargs.join(", "));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.update(inst, &self);
|
||||||
|
print_state(&state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
118
src/domtree.rs
Normal file
118
src/domtree.rs
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* Derives from the dominator tree implementation in regalloc.rs, which is
|
||||||
|
* licensed under the Apache Public License 2.0 with LLVM Exception. See:
|
||||||
|
* https://github.com/bytecodealliance/regalloc.rs
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This is an implementation of the algorithm described in
|
||||||
|
//
|
||||||
|
// A Simple, Fast Dominance Algorithm
|
||||||
|
// Keith D. Cooper, Timothy J. Harvey, and Ken Kennedy
|
||||||
|
// Department of Computer Science, Rice University, Houston, Texas, USA
|
||||||
|
// TR-06-33870
|
||||||
|
// https://www.cs.rice.edu/~keith/EMBED/dom.pdf
|
||||||
|
|
||||||
|
use crate::Block;
|
||||||
|
|
||||||
|
// Helper
|
||||||
|
fn merge_sets(
|
||||||
|
idom: &[Block], // map from Block to Block
|
||||||
|
block_to_rpo: &[Option<u32>],
|
||||||
|
mut node1: Block,
|
||||||
|
mut node2: Block,
|
||||||
|
) -> Block {
|
||||||
|
while node1 != node2 {
|
||||||
|
if node1.is_invalid() || node2.is_invalid() {
|
||||||
|
return Block::invalid();
|
||||||
|
}
|
||||||
|
let rpo1 = block_to_rpo[node1.index()].unwrap();
|
||||||
|
let rpo2 = block_to_rpo[node2.index()].unwrap();
|
||||||
|
if rpo1 > rpo2 {
|
||||||
|
node1 = idom[node1.index()];
|
||||||
|
} else if rpo2 > rpo1 {
|
||||||
|
node2 = idom[node2.index()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(node1 == node2);
|
||||||
|
node1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate<'a, PredFn: Fn(Block) -> &'a [Block]>(
|
||||||
|
num_blocks: usize,
|
||||||
|
preds: PredFn,
|
||||||
|
post_ord: &[Block],
|
||||||
|
start: Block,
|
||||||
|
) -> Vec<Block> {
|
||||||
|
// We have post_ord, which is the postorder sequence.
|
||||||
|
|
||||||
|
// Compute maps from RPO to block number and vice-versa.
|
||||||
|
let mut block_to_rpo = vec![None; num_blocks];
|
||||||
|
block_to_rpo.resize(num_blocks, None);
|
||||||
|
for (i, rpo_block) in post_ord.iter().rev().enumerate() {
|
||||||
|
block_to_rpo[rpo_block.index()] = Some(i as u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut idom = vec![Block::invalid(); num_blocks];
|
||||||
|
|
||||||
|
// The start node must have itself as a parent.
|
||||||
|
idom[start.index()] = start;
|
||||||
|
|
||||||
|
let mut changed = true;
|
||||||
|
while changed {
|
||||||
|
changed = false;
|
||||||
|
// Consider blocks in reverse postorder. Skip any that are unreachable.
|
||||||
|
for &node in post_ord.iter().rev() {
|
||||||
|
let rponum = block_to_rpo[node.index()].unwrap();
|
||||||
|
|
||||||
|
let mut parent = Block::invalid();
|
||||||
|
for &pred in preds(node).iter() {
|
||||||
|
let pred_rpo = match block_to_rpo[pred.index()] {
|
||||||
|
Some(r) => r,
|
||||||
|
None => {
|
||||||
|
// Skip unreachable preds.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if pred_rpo < rponum {
|
||||||
|
parent = pred;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if parent.is_valid() {
|
||||||
|
for &pred in preds(node).iter() {
|
||||||
|
if pred == parent {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if idom[pred.index()].is_invalid() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
parent = merge_sets(&idom, &block_to_rpo[..], parent, pred);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if parent.is_valid() && parent != idom[node.index()] {
|
||||||
|
idom[node.index()] = parent;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now set the start node's dominator-tree parent to "invalid";
|
||||||
|
// this allows the loop in `dominates` to terminate.
|
||||||
|
idom[start.index()] = Block::invalid();
|
||||||
|
|
||||||
|
idom
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dominates(idom: &[Block], a: Block, mut b: Block) -> bool {
|
||||||
|
loop {
|
||||||
|
if a == b {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if b.is_invalid() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
b = idom[b.index()];
|
||||||
|
}
|
||||||
|
}
|
||||||
603
src/fuzzing/func.rs
Normal file
603
src/fuzzing/func.rs
Normal file
@@ -0,0 +1,603 @@
|
|||||||
|
/*
|
||||||
|
* Released under the terms of the Apache 2.0 license with LLVM
|
||||||
|
* exception. See `LICENSE` for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
domtree, postorder, Allocation, Block, Function, Inst, InstRange, MachineEnv, Operand,
|
||||||
|
OperandConstraint, OperandKind, OperandPos, PReg, RegClass, VReg,
|
||||||
|
};
|
||||||
|
|
||||||
|
use arbitrary::Result as ArbitraryResult;
|
||||||
|
use arbitrary::{Arbitrary, Unstructured};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum InstOpcode {
|
||||||
|
Phi,
|
||||||
|
Op,
|
||||||
|
Call,
|
||||||
|
Ret,
|
||||||
|
Branch,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct InstData {
|
||||||
|
op: InstOpcode,
|
||||||
|
operands: Vec<Operand>,
|
||||||
|
clobbers: Vec<PReg>,
|
||||||
|
is_safepoint: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InstData {
|
||||||
|
pub fn op(def: usize, uses: &[usize]) -> InstData {
|
||||||
|
let mut operands = vec![Operand::reg_def(VReg::new(def, RegClass::Int))];
|
||||||
|
for &u in uses {
|
||||||
|
operands.push(Operand::reg_use(VReg::new(u, RegClass::Int)));
|
||||||
|
}
|
||||||
|
InstData {
|
||||||
|
op: InstOpcode::Op,
|
||||||
|
operands,
|
||||||
|
clobbers: vec![],
|
||||||
|
is_safepoint: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn branch(uses: &[usize]) -> InstData {
|
||||||
|
let mut operands = vec![];
|
||||||
|
for &u in uses {
|
||||||
|
operands.push(Operand::reg_use(VReg::new(u, RegClass::Int)));
|
||||||
|
}
|
||||||
|
InstData {
|
||||||
|
op: InstOpcode::Branch,
|
||||||
|
operands,
|
||||||
|
clobbers: vec![],
|
||||||
|
is_safepoint: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn ret() -> InstData {
|
||||||
|
InstData {
|
||||||
|
op: InstOpcode::Ret,
|
||||||
|
operands: vec![],
|
||||||
|
clobbers: vec![],
|
||||||
|
is_safepoint: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Func {
|
||||||
|
insts: Vec<InstData>,
|
||||||
|
blocks: Vec<InstRange>,
|
||||||
|
block_preds: Vec<Vec<Block>>,
|
||||||
|
block_succs: Vec<Vec<Block>>,
|
||||||
|
block_params: Vec<Vec<VReg>>,
|
||||||
|
num_vregs: usize,
|
||||||
|
reftype_vregs: Vec<VReg>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Function for Func {
|
||||||
|
fn num_insts(&self) -> usize {
|
||||||
|
self.insts.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn num_blocks(&self) -> usize {
|
||||||
|
self.blocks.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn entry_block(&self) -> Block {
|
||||||
|
assert!(self.blocks.len() > 0);
|
||||||
|
Block::new(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn block_insns(&self, block: Block) -> InstRange {
|
||||||
|
self.blocks[block.index()]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn block_succs(&self, block: Block) -> &[Block] {
|
||||||
|
&self.block_succs[block.index()][..]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn block_preds(&self, block: Block) -> &[Block] {
|
||||||
|
&self.block_preds[block.index()][..]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn block_params(&self, block: Block) -> &[VReg] {
|
||||||
|
&self.block_params[block.index()][..]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_call(&self, insn: Inst) -> bool {
|
||||||
|
self.insts[insn.index()].op == InstOpcode::Call
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_ret(&self, insn: Inst) -> bool {
|
||||||
|
self.insts[insn.index()].op == InstOpcode::Ret
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_branch(&self, insn: Inst) -> bool {
|
||||||
|
self.insts[insn.index()].op == InstOpcode::Branch
|
||||||
|
}
|
||||||
|
|
||||||
|
fn branch_blockparam_arg_offset(&self, _: Block, _: Inst) -> usize {
|
||||||
|
// Branch blockparam args always start at zero for this
|
||||||
|
// Function implementation.
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn requires_refs_on_stack(&self, insn: Inst) -> bool {
|
||||||
|
self.insts[insn.index()].is_safepoint
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reftype_vregs(&self) -> &[VReg] {
|
||||||
|
&self.reftype_vregs[..]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_move(&self, _: Inst) -> Option<(Operand, Operand)> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inst_operands(&self, insn: Inst) -> &[Operand] {
|
||||||
|
&self.insts[insn.index()].operands[..]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inst_clobbers(&self, insn: Inst) -> &[PReg] {
|
||||||
|
&self.insts[insn.index()].clobbers[..]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn num_vregs(&self) -> usize {
|
||||||
|
self.num_vregs
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spillslot_size(&self, regclass: RegClass) -> usize {
|
||||||
|
match regclass {
|
||||||
|
RegClass::Int => 1,
|
||||||
|
RegClass::Float => 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FuncBuilder {
|
||||||
|
postorder: Vec<Block>,
|
||||||
|
idom: Vec<Block>,
|
||||||
|
f: Func,
|
||||||
|
insts_per_block: Vec<Vec<InstData>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FuncBuilder {
|
||||||
|
fn new() -> Self {
|
||||||
|
FuncBuilder {
|
||||||
|
postorder: vec![],
|
||||||
|
idom: vec![],
|
||||||
|
f: Func {
|
||||||
|
block_preds: vec![],
|
||||||
|
block_succs: vec![],
|
||||||
|
block_params: vec![],
|
||||||
|
insts: vec![],
|
||||||
|
blocks: vec![],
|
||||||
|
num_vregs: 0,
|
||||||
|
reftype_vregs: vec![],
|
||||||
|
},
|
||||||
|
insts_per_block: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_block(&mut self) -> Block {
|
||||||
|
let b = Block::new(self.f.blocks.len());
|
||||||
|
self.f
|
||||||
|
.blocks
|
||||||
|
.push(InstRange::forward(Inst::new(0), Inst::new(0)));
|
||||||
|
self.f.block_preds.push(vec![]);
|
||||||
|
self.f.block_succs.push(vec![]);
|
||||||
|
self.f.block_params.push(vec![]);
|
||||||
|
self.insts_per_block.push(vec![]);
|
||||||
|
b
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_inst(&mut self, block: Block, data: InstData) {
|
||||||
|
self.insts_per_block[block.index()].push(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_edge(&mut self, from: Block, to: Block) {
|
||||||
|
self.f.block_succs[from.index()].push(to);
|
||||||
|
self.f.block_preds[to.index()].push(from);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_block_params(&mut self, block: Block, params: &[VReg]) {
|
||||||
|
self.f.block_params[block.index()] = params.iter().cloned().collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute_doms(&mut self) {
|
||||||
|
self.postorder = postorder::calculate(self.f.blocks.len(), Block::new(0), |block| {
|
||||||
|
&self.f.block_succs[block.index()][..]
|
||||||
|
});
|
||||||
|
self.idom = domtree::calculate(
|
||||||
|
self.f.blocks.len(),
|
||||||
|
|block| &self.f.block_preds[block.index()][..],
|
||||||
|
&self.postorder[..],
|
||||||
|
Block::new(0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finalize(mut self) -> Func {
|
||||||
|
for (blocknum, blockrange) in self.f.blocks.iter_mut().enumerate() {
|
||||||
|
let begin_inst = self.f.insts.len();
|
||||||
|
for inst in &self.insts_per_block[blocknum] {
|
||||||
|
self.f.insts.push(inst.clone());
|
||||||
|
}
|
||||||
|
let end_inst = self.f.insts.len();
|
||||||
|
*blockrange = InstRange::forward(Inst::new(begin_inst), Inst::new(end_inst));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arbitrary for OperandConstraint {
|
||||||
|
fn arbitrary(u: &mut Unstructured) -> ArbitraryResult<Self> {
|
||||||
|
Ok(*u.choose(&[OperandConstraint::Any, OperandConstraint::Reg])?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn choose_dominating_block(
|
||||||
|
idom: &[Block],
|
||||||
|
mut block: Block,
|
||||||
|
allow_self: bool,
|
||||||
|
u: &mut Unstructured,
|
||||||
|
) -> ArbitraryResult<Block> {
|
||||||
|
assert!(block.is_valid());
|
||||||
|
let orig_block = block;
|
||||||
|
loop {
|
||||||
|
if (allow_self || block != orig_block) && bool::arbitrary(u)? {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if idom[block.index()].is_invalid() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
block = idom[block.index()];
|
||||||
|
}
|
||||||
|
let block = if block != orig_block || allow_self {
|
||||||
|
block
|
||||||
|
} else {
|
||||||
|
Block::invalid()
|
||||||
|
};
|
||||||
|
Ok(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct Options {
|
||||||
|
pub reused_inputs: bool,
|
||||||
|
pub fixed_regs: bool,
|
||||||
|
pub clobbers: bool,
|
||||||
|
pub control_flow: bool,
|
||||||
|
pub reducible: bool,
|
||||||
|
pub block_params: bool,
|
||||||
|
pub always_local_uses: bool,
|
||||||
|
pub reftypes: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::default::Default for Options {
|
||||||
|
fn default() -> Self {
|
||||||
|
Options {
|
||||||
|
reused_inputs: false,
|
||||||
|
fixed_regs: false,
|
||||||
|
clobbers: false,
|
||||||
|
control_flow: true,
|
||||||
|
reducible: false,
|
||||||
|
block_params: true,
|
||||||
|
always_local_uses: false,
|
||||||
|
reftypes: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arbitrary for Func {
|
||||||
|
fn arbitrary(u: &mut Unstructured) -> ArbitraryResult<Func> {
|
||||||
|
Func::arbitrary_with_options(u, &Options::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Func {
|
||||||
|
pub fn arbitrary_with_options(u: &mut Unstructured, opts: &Options) -> ArbitraryResult<Func> {
|
||||||
|
// General strategy:
|
||||||
|
// 1. Create an arbitrary CFG.
|
||||||
|
// 2. Create a list of vregs to define in each block.
|
||||||
|
// 3. Define some of those vregs in each block as blockparams.f.
|
||||||
|
// 4. Populate blocks with ops that define the rest of the vregs.
|
||||||
|
// - For each use, choose an available vreg: either one
|
||||||
|
// already defined (via blockparam or inst) in this block,
|
||||||
|
// or one defined in a dominating block.
|
||||||
|
|
||||||
|
let mut builder = FuncBuilder::new();
|
||||||
|
for _ in 0..u.int_in_range(1..=100)? {
|
||||||
|
builder.add_block();
|
||||||
|
}
|
||||||
|
let num_blocks = builder.f.blocks.len();
|
||||||
|
|
||||||
|
// Generate a CFG. Create a "spine" of either single blocks,
|
||||||
|
// with links to the next; or fork patterns, with the left
|
||||||
|
// fork linking to the next and the right fork in `out_blocks`
|
||||||
|
// to be connected below. This creates an arbitrary CFG with
|
||||||
|
// split critical edges, which is a property that we require
|
||||||
|
// for the regalloc.
|
||||||
|
let mut from = 0;
|
||||||
|
let mut out_blocks = vec![];
|
||||||
|
let mut in_blocks = vec![];
|
||||||
|
// For reducibility, if selected: enforce strict nesting of backedges
|
||||||
|
let mut max_backedge_src = 0;
|
||||||
|
let mut min_backedge_dest = num_blocks;
|
||||||
|
while from < num_blocks {
|
||||||
|
in_blocks.push(from);
|
||||||
|
if num_blocks > 3 && from < num_blocks - 3 && bool::arbitrary(u)? && opts.control_flow {
|
||||||
|
// To avoid critical edges, we use from+1 as an edge
|
||||||
|
// block, and advance `from` an extra block; `from+2`
|
||||||
|
// will be the next normal iteration.
|
||||||
|
builder.add_edge(Block::new(from), Block::new(from + 1));
|
||||||
|
builder.add_edge(Block::new(from), Block::new(from + 2));
|
||||||
|
builder.add_edge(Block::new(from + 2), Block::new(from + 3));
|
||||||
|
out_blocks.push(from + 1);
|
||||||
|
from += 2;
|
||||||
|
} else if from < num_blocks - 1 {
|
||||||
|
builder.add_edge(Block::new(from), Block::new(from + 1));
|
||||||
|
}
|
||||||
|
from += 1;
|
||||||
|
}
|
||||||
|
for pred in out_blocks {
|
||||||
|
let mut succ = *u.choose(&in_blocks[..])?;
|
||||||
|
if opts.reducible && (pred >= succ) {
|
||||||
|
if pred < max_backedge_src || succ > min_backedge_dest {
|
||||||
|
// If the chosen edge would result in an
|
||||||
|
// irreducible CFG, just make this a diamond
|
||||||
|
// instead.
|
||||||
|
succ = pred + 2;
|
||||||
|
} else {
|
||||||
|
max_backedge_src = pred;
|
||||||
|
min_backedge_dest = succ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builder.add_edge(Block::new(pred), Block::new(succ));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.compute_doms();
|
||||||
|
|
||||||
|
for block in 0..num_blocks {
|
||||||
|
builder.f.block_preds[block].clear();
|
||||||
|
}
|
||||||
|
for block in 0..num_blocks {
|
||||||
|
for &succ in &builder.f.block_succs[block] {
|
||||||
|
builder.f.block_preds[succ.index()].push(Block::new(block));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.compute_doms();
|
||||||
|
|
||||||
|
let mut vregs_by_block = vec![];
|
||||||
|
let mut vregs_by_block_to_be_defined = vec![];
|
||||||
|
let mut block_params = vec![vec![]; num_blocks];
|
||||||
|
for block in 0..num_blocks {
|
||||||
|
let mut vregs = vec![];
|
||||||
|
for _ in 0..u.int_in_range(5..=15)? {
|
||||||
|
let vreg = VReg::new(builder.f.num_vregs, RegClass::Int);
|
||||||
|
builder.f.num_vregs += 1;
|
||||||
|
vregs.push(vreg);
|
||||||
|
if opts.reftypes && bool::arbitrary(u)? {
|
||||||
|
builder.f.reftype_vregs.push(vreg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vregs_by_block.push(vregs.clone());
|
||||||
|
vregs_by_block_to_be_defined.push(vec![]);
|
||||||
|
let mut max_block_params = u.int_in_range(0..=std::cmp::min(3, vregs.len() / 3))?;
|
||||||
|
for &vreg in &vregs {
|
||||||
|
if block > 0 && opts.block_params && bool::arbitrary(u)? && max_block_params > 0 {
|
||||||
|
block_params[block].push(vreg);
|
||||||
|
max_block_params -= 1;
|
||||||
|
} else {
|
||||||
|
vregs_by_block_to_be_defined.last_mut().unwrap().push(vreg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vregs_by_block_to_be_defined.last_mut().unwrap().reverse();
|
||||||
|
builder.set_block_params(Block::new(block), &block_params[block][..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for block in 0..num_blocks {
|
||||||
|
let mut avail = block_params[block].clone();
|
||||||
|
let mut remaining_nonlocal_uses = u.int_in_range(0..=3)?;
|
||||||
|
while let Some(vreg) = vregs_by_block_to_be_defined[block].pop() {
|
||||||
|
let def_constraint = OperandConstraint::arbitrary(u)?;
|
||||||
|
let def_pos = if bool::arbitrary(u)? {
|
||||||
|
OperandPos::Early
|
||||||
|
} else {
|
||||||
|
OperandPos::Late
|
||||||
|
};
|
||||||
|
let mut operands = vec![Operand::new(
|
||||||
|
vreg,
|
||||||
|
def_constraint,
|
||||||
|
OperandKind::Def,
|
||||||
|
def_pos,
|
||||||
|
)];
|
||||||
|
let mut allocations = vec![Allocation::none()];
|
||||||
|
for _ in 0..u.int_in_range(0..=3)? {
|
||||||
|
let vreg = if avail.len() > 0
|
||||||
|
&& (opts.always_local_uses
|
||||||
|
|| remaining_nonlocal_uses == 0
|
||||||
|
|| bool::arbitrary(u)?)
|
||||||
|
{
|
||||||
|
*u.choose(&avail[..])?
|
||||||
|
} else if !opts.always_local_uses {
|
||||||
|
let def_block = choose_dominating_block(
|
||||||
|
&builder.idom[..],
|
||||||
|
Block::new(block),
|
||||||
|
/* allow_self = */ false,
|
||||||
|
u,
|
||||||
|
)?;
|
||||||
|
if !def_block.is_valid() {
|
||||||
|
// No vregs already defined, and no pred blocks that dominate us
|
||||||
|
// (perhaps we are the entry block): just stop generating inputs.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
remaining_nonlocal_uses -= 1;
|
||||||
|
*u.choose(&vregs_by_block[def_block.index()])?
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
let use_constraint = OperandConstraint::arbitrary(u)?;
|
||||||
|
operands.push(Operand::new(
|
||||||
|
vreg,
|
||||||
|
use_constraint,
|
||||||
|
OperandKind::Use,
|
||||||
|
OperandPos::Early,
|
||||||
|
));
|
||||||
|
allocations.push(Allocation::none());
|
||||||
|
}
|
||||||
|
let mut clobbers: Vec<PReg> = vec![];
|
||||||
|
if operands.len() > 1 && opts.reused_inputs && bool::arbitrary(u)? {
|
||||||
|
// Make the def a reused input.
|
||||||
|
let op = operands[0];
|
||||||
|
assert_eq!(op.kind(), OperandKind::Def);
|
||||||
|
let reused = u.int_in_range(1..=(operands.len() - 1))?;
|
||||||
|
operands[0] = Operand::new(
|
||||||
|
op.vreg(),
|
||||||
|
OperandConstraint::Reuse(reused),
|
||||||
|
op.kind(),
|
||||||
|
OperandPos::Late,
|
||||||
|
);
|
||||||
|
// Make sure reused input is a Reg.
|
||||||
|
let op = operands[reused];
|
||||||
|
operands[reused] = Operand::new(
|
||||||
|
op.vreg(),
|
||||||
|
OperandConstraint::Reg,
|
||||||
|
op.kind(),
|
||||||
|
OperandPos::Early,
|
||||||
|
);
|
||||||
|
} else if opts.fixed_regs && bool::arbitrary(u)? {
|
||||||
|
let mut fixed = vec![];
|
||||||
|
for _ in 0..u.int_in_range(0..=operands.len() - 1)? {
|
||||||
|
// Pick an operand and make it a fixed reg.
|
||||||
|
let fixed_reg = PReg::new(u.int_in_range(0..=30)?, RegClass::Int);
|
||||||
|
if fixed.contains(&fixed_reg) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
fixed.push(fixed_reg);
|
||||||
|
let i = u.int_in_range(0..=(operands.len() - 1))?;
|
||||||
|
let op = operands[i];
|
||||||
|
operands[i] = Operand::new(
|
||||||
|
op.vreg(),
|
||||||
|
OperandConstraint::FixedReg(fixed_reg),
|
||||||
|
op.kind(),
|
||||||
|
op.pos(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if opts.clobbers && bool::arbitrary(u)? {
|
||||||
|
for _ in 0..u.int_in_range(0..=5)? {
|
||||||
|
let reg = u.int_in_range(0..=30)?;
|
||||||
|
if clobbers.iter().any(|r| r.hw_enc() == reg) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
clobbers.push(PReg::new(reg, RegClass::Int));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_safepoint = opts.reftypes
|
||||||
|
&& operands
|
||||||
|
.iter()
|
||||||
|
.all(|op| !builder.f.reftype_vregs.contains(&op.vreg()))
|
||||||
|
&& bool::arbitrary(u)?;
|
||||||
|
|
||||||
|
let op = *u.choose(&[InstOpcode::Op, InstOpcode::Call])?;
|
||||||
|
builder.add_inst(
|
||||||
|
Block::new(block),
|
||||||
|
InstData {
|
||||||
|
op,
|
||||||
|
operands,
|
||||||
|
clobbers,
|
||||||
|
is_safepoint,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
avail.push(vreg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define the branch with blockparam args that must end
|
||||||
|
// the block.
|
||||||
|
if builder.f.block_succs[block].len() > 0 {
|
||||||
|
let mut args = vec![];
|
||||||
|
for &succ in &builder.f.block_succs[block] {
|
||||||
|
for _ in 0..builder.f.block_params[succ.index()].len() {
|
||||||
|
let dom_block = choose_dominating_block(
|
||||||
|
&builder.idom[..],
|
||||||
|
Block::new(block),
|
||||||
|
false,
|
||||||
|
u,
|
||||||
|
)?;
|
||||||
|
let vreg = if dom_block.is_valid() && bool::arbitrary(u)? {
|
||||||
|
u.choose(&vregs_by_block[dom_block.index()][..])?
|
||||||
|
} else {
|
||||||
|
u.choose(&avail[..])?
|
||||||
|
};
|
||||||
|
args.push(vreg.vreg());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builder.add_inst(Block::new(block), InstData::branch(&args[..]));
|
||||||
|
} else {
|
||||||
|
builder.add_inst(Block::new(block), InstData::ret());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(builder.finalize())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for Func {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(f, "{{\n")?;
|
||||||
|
for vreg in self.reftype_vregs() {
|
||||||
|
write!(f, " REF: {}\n", vreg)?;
|
||||||
|
}
|
||||||
|
for (i, blockrange) in self.blocks.iter().enumerate() {
|
||||||
|
let succs = self.block_succs[i]
|
||||||
|
.iter()
|
||||||
|
.map(|b| b.index())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let preds = self.block_preds[i]
|
||||||
|
.iter()
|
||||||
|
.map(|b| b.index())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let params = self.block_params[i]
|
||||||
|
.iter()
|
||||||
|
.map(|v| format!("v{}", v.vreg()))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ");
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
" block{}({}): # succs:{:?} preds:{:?}\n",
|
||||||
|
i, params, succs, preds
|
||||||
|
)?;
|
||||||
|
for inst in blockrange.iter() {
|
||||||
|
if self.requires_refs_on_stack(inst) {
|
||||||
|
write!(f, " -- SAFEPOINT --\n")?;
|
||||||
|
}
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
" inst{}: {:?} ops:{:?} clobber:{:?}\n",
|
||||||
|
inst.index(),
|
||||||
|
self.insts[inst.index()].op,
|
||||||
|
self.insts[inst.index()].operands,
|
||||||
|
self.insts[inst.index()].clobbers
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write!(f, "}}\n")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn machine_env() -> MachineEnv {
|
||||||
|
// Reg 31 is the scratch reg.
|
||||||
|
let regs: Vec<PReg> = (0..31).map(|i| PReg::new(i, RegClass::Int)).collect();
|
||||||
|
let preferred_regs_by_class: [Vec<PReg>; 2] = [regs.iter().cloned().take(24).collect(), vec![]];
|
||||||
|
let non_preferred_regs_by_class: [Vec<PReg>; 2] =
|
||||||
|
[regs.iter().cloned().skip(24).collect(), vec![]];
|
||||||
|
let scratch_by_class: [PReg; 2] = [PReg::new(31, RegClass::Int), PReg::new(0, RegClass::Float)];
|
||||||
|
MachineEnv {
|
||||||
|
regs,
|
||||||
|
preferred_regs_by_class,
|
||||||
|
non_preferred_regs_by_class,
|
||||||
|
scratch_by_class,
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/fuzzing/mod.rs
Normal file
32
src/fuzzing/mod.rs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Released under the terms of the Apache 2.0 license with LLVM
|
||||||
|
* exception. See `LICENSE` for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! Utilities for fuzzing.
|
||||||
|
|
||||||
|
pub mod func;
|
||||||
|
|
||||||
|
// Re-exports for fuzz targets.
|
||||||
|
|
||||||
|
pub mod domtree {
|
||||||
|
pub use crate::domtree::*;
|
||||||
|
}
|
||||||
|
pub mod postorder {
|
||||||
|
pub use crate::postorder::*;
|
||||||
|
}
|
||||||
|
pub mod moves {
|
||||||
|
pub use crate::moves::*;
|
||||||
|
}
|
||||||
|
pub mod cfg {
|
||||||
|
pub use crate::cfg::*;
|
||||||
|
}
|
||||||
|
pub mod ssa {
|
||||||
|
pub use crate::ssa::*;
|
||||||
|
}
|
||||||
|
pub mod ion {
|
||||||
|
pub use crate::ion::*;
|
||||||
|
}
|
||||||
|
pub mod checker {
|
||||||
|
pub use crate::checker::*;
|
||||||
|
}
|
||||||
181
src/index.rs
Normal file
181
src/index.rs
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
#[macro_export]
|
||||||
|
macro_rules! define_index {
|
||||||
|
($ix:ident) => {
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct $ix(pub u32);
|
||||||
|
impl $ix {
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn new(i: usize) -> Self {
|
||||||
|
Self(i as u32)
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn index(self) -> usize {
|
||||||
|
assert!(self.is_valid());
|
||||||
|
self.0 as usize
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn invalid() -> Self {
|
||||||
|
Self(u32::MAX)
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn is_invalid(self) -> bool {
|
||||||
|
self == Self::invalid()
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn is_valid(self) -> bool {
|
||||||
|
self != Self::invalid()
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn next(self) -> $ix {
|
||||||
|
assert!(self.is_valid());
|
||||||
|
Self(self.0 + 1)
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn prev(self) -> $ix {
|
||||||
|
assert!(self.is_valid());
|
||||||
|
Self(self.0 - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn raw_u32(self) -> u32 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::index::ContainerIndex for $ix {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ContainerIndex: Clone + Copy + std::fmt::Debug + PartialEq + Eq {}
|
||||||
|
|
||||||
|
pub trait ContainerComparator {
|
||||||
|
type Ix: ContainerIndex;
|
||||||
|
fn compare(&self, a: Self::Ix, b: Self::Ix) -> std::cmp::Ordering;
|
||||||
|
}
|
||||||
|
|
||||||
|
define_index!(Inst);
|
||||||
|
define_index!(Block);
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct InstRange(Inst, Inst, bool);
|
||||||
|
|
||||||
|
impl InstRange {
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn forward(from: Inst, to: Inst) -> Self {
|
||||||
|
assert!(from.index() <= to.index());
|
||||||
|
InstRange(from, to, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn backward(from: Inst, to: Inst) -> Self {
|
||||||
|
assert!(from.index() >= to.index());
|
||||||
|
InstRange(to, from, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn first(self) -> Inst {
|
||||||
|
assert!(self.len() > 0);
|
||||||
|
if self.is_forward() {
|
||||||
|
self.0
|
||||||
|
} else {
|
||||||
|
self.1.prev()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn last(self) -> Inst {
|
||||||
|
assert!(self.len() > 0);
|
||||||
|
if self.is_forward() {
|
||||||
|
self.1.prev()
|
||||||
|
} else {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn rest(self) -> InstRange {
|
||||||
|
assert!(self.len() > 0);
|
||||||
|
if self.is_forward() {
|
||||||
|
InstRange::forward(self.0.next(), self.1)
|
||||||
|
} else {
|
||||||
|
InstRange::backward(self.1.prev(), self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn len(self) -> usize {
|
||||||
|
self.1.index() - self.0.index()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn is_forward(self) -> bool {
|
||||||
|
self.2
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn rev(self) -> Self {
|
||||||
|
Self(self.0, self.1, !self.2)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn iter(self) -> InstRangeIter {
|
||||||
|
InstRangeIter(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct InstRangeIter(InstRange);
|
||||||
|
|
||||||
|
impl Iterator for InstRangeIter {
|
||||||
|
type Item = Inst;
|
||||||
|
#[inline(always)]
|
||||||
|
fn next(&mut self) -> Option<Inst> {
|
||||||
|
if self.0.len() == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let ret = self.0.first();
|
||||||
|
self.0 = self.0.rest();
|
||||||
|
Some(ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_inst_range() {
|
||||||
|
let range = InstRange::forward(Inst::new(0), Inst::new(0));
|
||||||
|
assert_eq!(range.len(), 0);
|
||||||
|
|
||||||
|
let range = InstRange::forward(Inst::new(0), Inst::new(5));
|
||||||
|
assert_eq!(range.first().index(), 0);
|
||||||
|
assert_eq!(range.last().index(), 4);
|
||||||
|
assert_eq!(range.len(), 5);
|
||||||
|
assert_eq!(
|
||||||
|
range.iter().collect::<Vec<_>>(),
|
||||||
|
vec![
|
||||||
|
Inst::new(0),
|
||||||
|
Inst::new(1),
|
||||||
|
Inst::new(2),
|
||||||
|
Inst::new(3),
|
||||||
|
Inst::new(4)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
let range = range.rev();
|
||||||
|
assert_eq!(range.first().index(), 4);
|
||||||
|
assert_eq!(range.last().index(), 0);
|
||||||
|
assert_eq!(range.len(), 5);
|
||||||
|
assert_eq!(
|
||||||
|
range.iter().collect::<Vec<_>>(),
|
||||||
|
vec![
|
||||||
|
Inst::new(4),
|
||||||
|
Inst::new(3),
|
||||||
|
Inst::new(2),
|
||||||
|
Inst::new(1),
|
||||||
|
Inst::new(0)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
367
src/indexset.rs
Normal file
367
src/indexset.rs
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
/*
|
||||||
|
* Released under the terms of the Apache 2.0 license with LLVM
|
||||||
|
* exception. See `LICENSE` for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! Index sets: sets of integers that represent indices into a space.
|
||||||
|
|
||||||
|
use fxhash::FxHashMap;
|
||||||
|
use std::cell::Cell;
|
||||||
|
|
||||||
|
const SMALL_ELEMS: usize = 12;
|
||||||
|
|
||||||
|
/// A hybrid large/small-mode sparse mapping from integer indices to
|
||||||
|
/// elements.
|
||||||
|
///
|
||||||
|
/// The trailing `(u32, u64)` elements in each variant is a one-item
|
||||||
|
/// cache to allow fast access when streaming through.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
enum AdaptiveMap {
|
||||||
|
Small {
|
||||||
|
len: u32,
|
||||||
|
keys: [u32; SMALL_ELEMS],
|
||||||
|
values: [u64; SMALL_ELEMS],
|
||||||
|
},
|
||||||
|
Large(FxHashMap<u32, u64>),
|
||||||
|
}
|
||||||
|
|
||||||
|
const INVALID: u32 = 0xffff_ffff;
|
||||||
|
|
||||||
|
impl AdaptiveMap {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self::Small {
|
||||||
|
len: 0,
|
||||||
|
keys: [INVALID; SMALL_ELEMS],
|
||||||
|
values: [0; SMALL_ELEMS],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expand into `Large` mode if we are at capacity and have no
|
||||||
|
/// zero-value pairs that can be trimmed.
|
||||||
|
#[inline(never)]
|
||||||
|
fn expand(&mut self) {
|
||||||
|
match self {
|
||||||
|
&mut Self::Small {
|
||||||
|
ref mut len,
|
||||||
|
ref mut keys,
|
||||||
|
ref mut values,
|
||||||
|
} => {
|
||||||
|
// Note: we *may* remain as `Small` if there are any
|
||||||
|
// zero elements. Try removing them first, before we
|
||||||
|
// commit to a memory allocation.
|
||||||
|
if values.iter().any(|v| *v == 0) {
|
||||||
|
let mut out = 0;
|
||||||
|
for i in 0..(*len as usize) {
|
||||||
|
if values[i] == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if out < i {
|
||||||
|
keys[out] = keys[i];
|
||||||
|
values[out] = values[i];
|
||||||
|
}
|
||||||
|
out += 1;
|
||||||
|
}
|
||||||
|
*len = out as u32;
|
||||||
|
} else {
|
||||||
|
let mut map = FxHashMap::default();
|
||||||
|
for i in 0..(*len as usize) {
|
||||||
|
map.insert(keys[i], values[i]);
|
||||||
|
}
|
||||||
|
*self = Self::Large(map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
fn get_or_insert<'a>(&'a mut self, key: u32) -> &'a mut u64 {
|
||||||
|
// Check whether the key is present and we are in small mode;
|
||||||
|
// if no to both, we need to expand first.
|
||||||
|
let (needs_expand, small_mode_idx) = match self {
|
||||||
|
&mut Self::Small { len, ref keys, .. } => {
|
||||||
|
// Perform this scan but do not return right away;
|
||||||
|
// doing so runs into overlapping-borrow issues
|
||||||
|
// because the current non-lexical lifetimes
|
||||||
|
// implementation is not able to see that the `self`
|
||||||
|
// mutable borrow on return is only on the
|
||||||
|
// early-return path.
|
||||||
|
let small_mode_idx = keys.iter().take(len as usize).position(|k| *k == key);
|
||||||
|
let needs_expand = small_mode_idx.is_none() && len == SMALL_ELEMS as u32;
|
||||||
|
(needs_expand, small_mode_idx)
|
||||||
|
}
|
||||||
|
_ => (false, None),
|
||||||
|
};
|
||||||
|
|
||||||
|
if needs_expand {
|
||||||
|
assert!(small_mode_idx.is_none());
|
||||||
|
self.expand();
|
||||||
|
}
|
||||||
|
|
||||||
|
match self {
|
||||||
|
&mut Self::Small {
|
||||||
|
ref mut len,
|
||||||
|
ref mut keys,
|
||||||
|
ref mut values,
|
||||||
|
} => {
|
||||||
|
// If we found the key already while checking whether
|
||||||
|
// we need to expand above, use that index to return
|
||||||
|
// early.
|
||||||
|
if let Some(i) = small_mode_idx {
|
||||||
|
return &mut values[i];
|
||||||
|
}
|
||||||
|
// Otherwise, the key must not be present; add a new
|
||||||
|
// entry.
|
||||||
|
assert!(*len < SMALL_ELEMS as u32);
|
||||||
|
let idx = *len;
|
||||||
|
*len += 1;
|
||||||
|
keys[idx as usize] = key;
|
||||||
|
values[idx as usize] = 0;
|
||||||
|
&mut values[idx as usize]
|
||||||
|
}
|
||||||
|
&mut Self::Large(ref mut map) => map.entry(key).or_insert(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
fn get_mut(&mut self, key: u32) -> Option<&mut u64> {
|
||||||
|
match self {
|
||||||
|
&mut Self::Small {
|
||||||
|
len,
|
||||||
|
ref keys,
|
||||||
|
ref mut values,
|
||||||
|
} => {
|
||||||
|
for i in 0..len {
|
||||||
|
if keys[i as usize] == key {
|
||||||
|
return Some(&mut values[i as usize]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
&mut Self::Large(ref mut map) => map.get_mut(&key),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
fn get(&self, key: u32) -> Option<u64> {
|
||||||
|
match self {
|
||||||
|
&Self::Small {
|
||||||
|
len,
|
||||||
|
ref keys,
|
||||||
|
ref values,
|
||||||
|
} => {
|
||||||
|
for i in 0..len {
|
||||||
|
if keys[i as usize] == key {
|
||||||
|
let value = values[i as usize];
|
||||||
|
return Some(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
&Self::Large(ref map) => {
|
||||||
|
let value = map.get(&key).cloned();
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn iter<'a>(&'a self) -> AdaptiveMapIter<'a> {
|
||||||
|
match self {
|
||||||
|
&Self::Small {
|
||||||
|
len,
|
||||||
|
ref keys,
|
||||||
|
ref values,
|
||||||
|
} => AdaptiveMapIter::Small(&keys[0..len as usize], &values[0..len as usize]),
|
||||||
|
&Self::Large(ref map) => AdaptiveMapIter::Large(map.iter()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AdaptiveMapIter<'a> {
|
||||||
|
Small(&'a [u32], &'a [u64]),
|
||||||
|
Large(std::collections::hash_map::Iter<'a, u32, u64>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> std::iter::Iterator for AdaptiveMapIter<'a> {
|
||||||
|
type Item = (u32, u64);
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
match self {
|
||||||
|
&mut Self::Small(ref mut keys, ref mut values) => {
|
||||||
|
if keys.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let (k, v) = ((*keys)[0], (*values)[0]);
|
||||||
|
*keys = &(*keys)[1..];
|
||||||
|
*values = &(*values)[1..];
|
||||||
|
Some((k, v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&mut Self::Large(ref mut it) => it.next().map(|(&k, &v)| (k, v)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A conceptually infinite-length set of indices that allows union
|
||||||
|
/// and efficient iteration over elements.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct IndexSet {
|
||||||
|
elems: AdaptiveMap,
|
||||||
|
cache: Cell<(u32, u64)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const BITS_PER_WORD: usize = 64;
|
||||||
|
|
||||||
|
impl IndexSet {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
elems: AdaptiveMap::new(),
|
||||||
|
cache: Cell::new((INVALID, 0)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn elem(&mut self, bit_index: usize) -> &mut u64 {
|
||||||
|
let word_index = (bit_index / BITS_PER_WORD) as u32;
|
||||||
|
if self.cache.get().0 == word_index {
|
||||||
|
self.cache.set((INVALID, 0));
|
||||||
|
}
|
||||||
|
self.elems.get_or_insert(word_index)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn maybe_elem_mut(&mut self, bit_index: usize) -> Option<&mut u64> {
|
||||||
|
let word_index = (bit_index / BITS_PER_WORD) as u32;
|
||||||
|
if self.cache.get().0 == word_index {
|
||||||
|
self.cache.set((INVALID, 0));
|
||||||
|
}
|
||||||
|
self.elems.get_mut(word_index)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn maybe_elem(&self, bit_index: usize) -> Option<u64> {
|
||||||
|
let word_index = (bit_index / BITS_PER_WORD) as u32;
|
||||||
|
if self.cache.get().0 == word_index {
|
||||||
|
Some(self.cache.get().1)
|
||||||
|
} else {
|
||||||
|
self.elems.get(word_index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn set(&mut self, idx: usize, val: bool) {
|
||||||
|
let bit = idx % BITS_PER_WORD;
|
||||||
|
if val {
|
||||||
|
*self.elem(idx) |= 1 << bit;
|
||||||
|
} else if let Some(word) = self.maybe_elem_mut(idx) {
|
||||||
|
*word &= !(1 << bit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn assign(&mut self, other: &Self) {
|
||||||
|
self.elems = other.elems.clone();
|
||||||
|
self.cache = other.cache.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn get(&self, idx: usize) -> bool {
|
||||||
|
let bit = idx % BITS_PER_WORD;
|
||||||
|
if let Some(word) = self.maybe_elem(idx) {
|
||||||
|
(word & (1 << bit)) != 0
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn union_with(&mut self, other: &Self) -> bool {
|
||||||
|
let mut changed = 0;
|
||||||
|
for (word_idx, bits) in other.elems.iter() {
|
||||||
|
if bits == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let word_idx = word_idx as usize;
|
||||||
|
let self_word = self.elem(word_idx * BITS_PER_WORD);
|
||||||
|
changed |= bits & !*self_word;
|
||||||
|
*self_word |= bits;
|
||||||
|
}
|
||||||
|
changed != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter<'a>(&'a self) -> impl Iterator<Item = usize> + 'a {
|
||||||
|
self.elems.iter().flat_map(|(word_idx, bits)| {
|
||||||
|
let word_idx = word_idx as usize;
|
||||||
|
set_bits(bits).map(move |i| BITS_PER_WORD * word_idx + i)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is the adaptive data structure in "small" mode? This is meant
|
||||||
|
/// for testing assertions only.
|
||||||
|
pub(crate) fn is_small(&self) -> bool {
|
||||||
|
match &self.elems {
|
||||||
|
&AdaptiveMap::Small { .. } => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_bits(bits: u64) -> impl Iterator<Item = usize> {
|
||||||
|
let iter = SetBitsIter(bits);
|
||||||
|
iter
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SetBitsIter(u64);
|
||||||
|
|
||||||
|
impl Iterator for SetBitsIter {
|
||||||
|
type Item = usize;
|
||||||
|
fn next(&mut self) -> Option<usize> {
|
||||||
|
// Build an `Option<NonZeroU64>` so that on the nonzero path,
|
||||||
|
// the compiler can optimize the trailing-zeroes operator
|
||||||
|
// using that knowledge.
|
||||||
|
std::num::NonZeroU64::new(self.0).map(|nz| {
|
||||||
|
let bitidx = nz.trailing_zeros();
|
||||||
|
self.0 &= self.0 - 1; // clear highest set bit
|
||||||
|
bitidx as usize
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for IndexSet {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
let vals = self.iter().collect::<Vec<_>>();
|
||||||
|
write!(f, "{:?}", vals)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::IndexSet;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_set_bits_iter() {
|
||||||
|
let mut vec = IndexSet::new();
|
||||||
|
let mut sum = 0;
|
||||||
|
for i in 0..1024 {
|
||||||
|
if i % 17 == 0 {
|
||||||
|
vec.set(i, true);
|
||||||
|
sum += i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut checksum = 0;
|
||||||
|
for bit in vec.iter() {
|
||||||
|
assert!(bit % 17 == 0);
|
||||||
|
checksum += bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(sum, checksum);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_expand_remove_zero_elems() {
|
||||||
|
let mut vec = IndexSet::new();
|
||||||
|
// Set 12 different words (this is the max small-mode size).
|
||||||
|
for i in 0..12 {
|
||||||
|
vec.set(64 * i, true);
|
||||||
|
}
|
||||||
|
// Now clear a bit, and set a bit in a different word. We
|
||||||
|
// should still be in small mode.
|
||||||
|
vec.set(64 * 5, false);
|
||||||
|
vec.set(64 * 100, true);
|
||||||
|
assert!(vec.is_small());
|
||||||
|
}
|
||||||
|
}
|
||||||
373
src/ion/LICENSE
Normal file
373
src/ion/LICENSE
Normal file
@@ -0,0 +1,373 @@
|
|||||||
|
Mozilla Public License Version 2.0
|
||||||
|
==================================
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
--------------
|
||||||
|
|
||||||
|
1.1. "Contributor"
|
||||||
|
means each individual or legal entity that creates, contributes to
|
||||||
|
the creation of, or owns Covered Software.
|
||||||
|
|
||||||
|
1.2. "Contributor Version"
|
||||||
|
means the combination of the Contributions of others (if any) used
|
||||||
|
by a Contributor and that particular Contributor's Contribution.
|
||||||
|
|
||||||
|
1.3. "Contribution"
|
||||||
|
means Covered Software of a particular Contributor.
|
||||||
|
|
||||||
|
1.4. "Covered Software"
|
||||||
|
means Source Code Form to which the initial Contributor has attached
|
||||||
|
the notice in Exhibit A, the Executable Form of such Source Code
|
||||||
|
Form, and Modifications of such Source Code Form, in each case
|
||||||
|
including portions thereof.
|
||||||
|
|
||||||
|
1.5. "Incompatible With Secondary Licenses"
|
||||||
|
means
|
||||||
|
|
||||||
|
(a) that the initial Contributor has attached the notice described
|
||||||
|
in Exhibit B to the Covered Software; or
|
||||||
|
|
||||||
|
(b) that the Covered Software was made available under the terms of
|
||||||
|
version 1.1 or earlier of the License, but not also under the
|
||||||
|
terms of a Secondary License.
|
||||||
|
|
||||||
|
1.6. "Executable Form"
|
||||||
|
means any form of the work other than Source Code Form.
|
||||||
|
|
||||||
|
1.7. "Larger Work"
|
||||||
|
means a work that combines Covered Software with other material, in
|
||||||
|
a separate file or files, that is not Covered Software.
|
||||||
|
|
||||||
|
1.8. "License"
|
||||||
|
means this document.
|
||||||
|
|
||||||
|
1.9. "Licensable"
|
||||||
|
means having the right to grant, to the maximum extent possible,
|
||||||
|
whether at the time of the initial grant or subsequently, any and
|
||||||
|
all of the rights conveyed by this License.
|
||||||
|
|
||||||
|
1.10. "Modifications"
|
||||||
|
means any of the following:
|
||||||
|
|
||||||
|
(a) any file in Source Code Form that results from an addition to,
|
||||||
|
deletion from, or modification of the contents of Covered
|
||||||
|
Software; or
|
||||||
|
|
||||||
|
(b) any new file in Source Code Form that contains any Covered
|
||||||
|
Software.
|
||||||
|
|
||||||
|
1.11. "Patent Claims" of a Contributor
|
||||||
|
means any patent claim(s), including without limitation, method,
|
||||||
|
process, and apparatus claims, in any patent Licensable by such
|
||||||
|
Contributor that would be infringed, but for the grant of the
|
||||||
|
License, by the making, using, selling, offering for sale, having
|
||||||
|
made, import, or transfer of either its Contributions or its
|
||||||
|
Contributor Version.
|
||||||
|
|
||||||
|
1.12. "Secondary License"
|
||||||
|
means either the GNU General Public License, Version 2.0, the GNU
|
||||||
|
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||||
|
Public License, Version 3.0, or any later versions of those
|
||||||
|
licenses.
|
||||||
|
|
||||||
|
1.13. "Source Code Form"
|
||||||
|
means the form of the work preferred for making modifications.
|
||||||
|
|
||||||
|
1.14. "You" (or "Your")
|
||||||
|
means an individual or a legal entity exercising rights under this
|
||||||
|
License. For legal entities, "You" includes any entity that
|
||||||
|
controls, is controlled by, or is under common control with You. For
|
||||||
|
purposes of this definition, "control" means (a) the power, direct
|
||||||
|
or indirect, to cause the direction or management of such entity,
|
||||||
|
whether by contract or otherwise, or (b) ownership of more than
|
||||||
|
fifty percent (50%) of the outstanding shares or beneficial
|
||||||
|
ownership of such entity.
|
||||||
|
|
||||||
|
2. License Grants and Conditions
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
2.1. Grants
|
||||||
|
|
||||||
|
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||||
|
non-exclusive license:
|
||||||
|
|
||||||
|
(a) under intellectual property rights (other than patent or trademark)
|
||||||
|
Licensable by such Contributor to use, reproduce, make available,
|
||||||
|
modify, display, perform, distribute, and otherwise exploit its
|
||||||
|
Contributions, either on an unmodified basis, with Modifications, or
|
||||||
|
as part of a Larger Work; and
|
||||||
|
|
||||||
|
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||||
|
for sale, have made, import, and otherwise transfer either its
|
||||||
|
Contributions or its Contributor Version.
|
||||||
|
|
||||||
|
2.2. Effective Date
|
||||||
|
|
||||||
|
The licenses granted in Section 2.1 with respect to any Contribution
|
||||||
|
become effective for each Contribution on the date the Contributor first
|
||||||
|
distributes such Contribution.
|
||||||
|
|
||||||
|
2.3. Limitations on Grant Scope
|
||||||
|
|
||||||
|
The licenses granted in this Section 2 are the only rights granted under
|
||||||
|
this License. No additional rights or licenses will be implied from the
|
||||||
|
distribution or licensing of Covered Software under this License.
|
||||||
|
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||||
|
Contributor:
|
||||||
|
|
||||||
|
(a) for any code that a Contributor has removed from Covered Software;
|
||||||
|
or
|
||||||
|
|
||||||
|
(b) for infringements caused by: (i) Your and any other third party's
|
||||||
|
modifications of Covered Software, or (ii) the combination of its
|
||||||
|
Contributions with other software (except as part of its Contributor
|
||||||
|
Version); or
|
||||||
|
|
||||||
|
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||||
|
its Contributions.
|
||||||
|
|
||||||
|
This License does not grant any rights in the trademarks, service marks,
|
||||||
|
or logos of any Contributor (except as may be necessary to comply with
|
||||||
|
the notice requirements in Section 3.4).
|
||||||
|
|
||||||
|
2.4. Subsequent Licenses
|
||||||
|
|
||||||
|
No Contributor makes additional grants as a result of Your choice to
|
||||||
|
distribute the Covered Software under a subsequent version of this
|
||||||
|
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||||
|
permitted under the terms of Section 3.3).
|
||||||
|
|
||||||
|
2.5. Representation
|
||||||
|
|
||||||
|
Each Contributor represents that the Contributor believes its
|
||||||
|
Contributions are its original creation(s) or it has sufficient rights
|
||||||
|
to grant the rights to its Contributions conveyed by this License.
|
||||||
|
|
||||||
|
2.6. Fair Use
|
||||||
|
|
||||||
|
This License is not intended to limit any rights You have under
|
||||||
|
applicable copyright doctrines of fair use, fair dealing, or other
|
||||||
|
equivalents.
|
||||||
|
|
||||||
|
2.7. Conditions
|
||||||
|
|
||||||
|
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||||
|
in Section 2.1.
|
||||||
|
|
||||||
|
3. Responsibilities
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
3.1. Distribution of Source Form
|
||||||
|
|
||||||
|
All distribution of Covered Software in Source Code Form, including any
|
||||||
|
Modifications that You create or to which You contribute, must be under
|
||||||
|
the terms of this License. You must inform recipients that the Source
|
||||||
|
Code Form of the Covered Software is governed by the terms of this
|
||||||
|
License, and how they can obtain a copy of this License. You may not
|
||||||
|
attempt to alter or restrict the recipients' rights in the Source Code
|
||||||
|
Form.
|
||||||
|
|
||||||
|
3.2. Distribution of Executable Form
|
||||||
|
|
||||||
|
If You distribute Covered Software in Executable Form then:
|
||||||
|
|
||||||
|
(a) such Covered Software must also be made available in Source Code
|
||||||
|
Form, as described in Section 3.1, and You must inform recipients of
|
||||||
|
the Executable Form how they can obtain a copy of such Source Code
|
||||||
|
Form by reasonable means in a timely manner, at a charge no more
|
||||||
|
than the cost of distribution to the recipient; and
|
||||||
|
|
||||||
|
(b) You may distribute such Executable Form under the terms of this
|
||||||
|
License, or sublicense it under different terms, provided that the
|
||||||
|
license for the Executable Form does not attempt to limit or alter
|
||||||
|
the recipients' rights in the Source Code Form under this License.
|
||||||
|
|
||||||
|
3.3. Distribution of a Larger Work
|
||||||
|
|
||||||
|
You may create and distribute a Larger Work under terms of Your choice,
|
||||||
|
provided that You also comply with the requirements of this License for
|
||||||
|
the Covered Software. If the Larger Work is a combination of Covered
|
||||||
|
Software with a work governed by one or more Secondary Licenses, and the
|
||||||
|
Covered Software is not Incompatible With Secondary Licenses, this
|
||||||
|
License permits You to additionally distribute such Covered Software
|
||||||
|
under the terms of such Secondary License(s), so that the recipient of
|
||||||
|
the Larger Work may, at their option, further distribute the Covered
|
||||||
|
Software under the terms of either this License or such Secondary
|
||||||
|
License(s).
|
||||||
|
|
||||||
|
3.4. Notices
|
||||||
|
|
||||||
|
You may not remove or alter the substance of any license notices
|
||||||
|
(including copyright notices, patent notices, disclaimers of warranty,
|
||||||
|
or limitations of liability) contained within the Source Code Form of
|
||||||
|
the Covered Software, except that You may alter any license notices to
|
||||||
|
the extent required to remedy known factual inaccuracies.
|
||||||
|
|
||||||
|
3.5. Application of Additional Terms
|
||||||
|
|
||||||
|
You may choose to offer, and to charge a fee for, warranty, support,
|
||||||
|
indemnity or liability obligations to one or more recipients of Covered
|
||||||
|
Software. However, You may do so only on Your own behalf, and not on
|
||||||
|
behalf of any Contributor. You must make it absolutely clear that any
|
||||||
|
such warranty, support, indemnity, or liability obligation is offered by
|
||||||
|
You alone, and You hereby agree to indemnify every Contributor for any
|
||||||
|
liability incurred by such Contributor as a result of warranty, support,
|
||||||
|
indemnity or liability terms You offer. You may include additional
|
||||||
|
disclaimers of warranty and limitations of liability specific to any
|
||||||
|
jurisdiction.
|
||||||
|
|
||||||
|
4. Inability to Comply Due to Statute or Regulation
|
||||||
|
---------------------------------------------------
|
||||||
|
|
||||||
|
If it is impossible for You to comply with any of the terms of this
|
||||||
|
License with respect to some or all of the Covered Software due to
|
||||||
|
statute, judicial order, or regulation then You must: (a) comply with
|
||||||
|
the terms of this License to the maximum extent possible; and (b)
|
||||||
|
describe the limitations and the code they affect. Such description must
|
||||||
|
be placed in a text file included with all distributions of the Covered
|
||||||
|
Software under this License. Except to the extent prohibited by statute
|
||||||
|
or regulation, such description must be sufficiently detailed for a
|
||||||
|
recipient of ordinary skill to be able to understand it.
|
||||||
|
|
||||||
|
5. Termination
|
||||||
|
--------------
|
||||||
|
|
||||||
|
5.1. The rights granted under this License will terminate automatically
|
||||||
|
if You fail to comply with any of its terms. However, if You become
|
||||||
|
compliant, then the rights granted under this License from a particular
|
||||||
|
Contributor are reinstated (a) provisionally, unless and until such
|
||||||
|
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||||
|
ongoing basis, if such Contributor fails to notify You of the
|
||||||
|
non-compliance by some reasonable means prior to 60 days after You have
|
||||||
|
come back into compliance. Moreover, Your grants from a particular
|
||||||
|
Contributor are reinstated on an ongoing basis if such Contributor
|
||||||
|
notifies You of the non-compliance by some reasonable means, this is the
|
||||||
|
first time You have received notice of non-compliance with this License
|
||||||
|
from such Contributor, and You become compliant prior to 30 days after
|
||||||
|
Your receipt of the notice.
|
||||||
|
|
||||||
|
5.2. If You initiate litigation against any entity by asserting a patent
|
||||||
|
infringement claim (excluding declaratory judgment actions,
|
||||||
|
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||||
|
directly or indirectly infringes any patent, then the rights granted to
|
||||||
|
You by any and all Contributors for the Covered Software under Section
|
||||||
|
2.1 of this License shall terminate.
|
||||||
|
|
||||||
|
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||||
|
end user license agreements (excluding distributors and resellers) which
|
||||||
|
have been validly granted by You or Your distributors under this License
|
||||||
|
prior to termination shall survive termination.
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 6. Disclaimer of Warranty *
|
||||||
|
* ------------------------- *
|
||||||
|
* *
|
||||||
|
* Covered Software is provided under this License on an "as is" *
|
||||||
|
* basis, without warranty of any kind, either expressed, implied, or *
|
||||||
|
* statutory, including, without limitation, warranties that the *
|
||||||
|
* Covered Software is free of defects, merchantable, fit for a *
|
||||||
|
* particular purpose or non-infringing. The entire risk as to the *
|
||||||
|
* quality and performance of the Covered Software is with You. *
|
||||||
|
* Should any Covered Software prove defective in any respect, You *
|
||||||
|
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||||
|
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||||
|
* essential part of this License. No use of any Covered Software is *
|
||||||
|
* authorized under this License except under this disclaimer. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 7. Limitation of Liability *
|
||||||
|
* -------------------------- *
|
||||||
|
* *
|
||||||
|
* Under no circumstances and under no legal theory, whether tort *
|
||||||
|
* (including negligence), contract, or otherwise, shall any *
|
||||||
|
* Contributor, or anyone who distributes Covered Software as *
|
||||||
|
* permitted above, be liable to You for any direct, indirect, *
|
||||||
|
* special, incidental, or consequential damages of any character *
|
||||||
|
* including, without limitation, damages for lost profits, loss of *
|
||||||
|
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||||
|
* and all other commercial damages or losses, even if such party *
|
||||||
|
* shall have been informed of the possibility of such damages. This *
|
||||||
|
* limitation of liability shall not apply to liability for death or *
|
||||||
|
* personal injury resulting from such party's negligence to the *
|
||||||
|
* extent applicable law prohibits such limitation. Some *
|
||||||
|
* jurisdictions do not allow the exclusion or limitation of *
|
||||||
|
* incidental or consequential damages, so this exclusion and *
|
||||||
|
* limitation may not apply to You. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
8. Litigation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Any litigation relating to this License may be brought only in the
|
||||||
|
courts of a jurisdiction where the defendant maintains its principal
|
||||||
|
place of business and such litigation shall be governed by laws of that
|
||||||
|
jurisdiction, without reference to its conflict-of-law provisions.
|
||||||
|
Nothing in this Section shall prevent a party's ability to bring
|
||||||
|
cross-claims or counter-claims.
|
||||||
|
|
||||||
|
9. Miscellaneous
|
||||||
|
----------------
|
||||||
|
|
||||||
|
This License represents the complete agreement concerning the subject
|
||||||
|
matter hereof. If any provision of this License is held to be
|
||||||
|
unenforceable, such provision shall be reformed only to the extent
|
||||||
|
necessary to make it enforceable. Any law or regulation which provides
|
||||||
|
that the language of a contract shall be construed against the drafter
|
||||||
|
shall not be used to construe this License against a Contributor.
|
||||||
|
|
||||||
|
10. Versions of the License
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
10.1. New Versions
|
||||||
|
|
||||||
|
Mozilla Foundation is the license steward. Except as provided in Section
|
||||||
|
10.3, no one other than the license steward has the right to modify or
|
||||||
|
publish new versions of this License. Each version will be given a
|
||||||
|
distinguishing version number.
|
||||||
|
|
||||||
|
10.2. Effect of New Versions
|
||||||
|
|
||||||
|
You may distribute the Covered Software under the terms of the version
|
||||||
|
of the License under which You originally received the Covered Software,
|
||||||
|
or under the terms of any subsequent version published by the license
|
||||||
|
steward.
|
||||||
|
|
||||||
|
10.3. Modified Versions
|
||||||
|
|
||||||
|
If you create software not governed by this License, and you want to
|
||||||
|
create a new license for such software, you may create and use a
|
||||||
|
modified version of this License if you rename the license and remove
|
||||||
|
any references to the name of the license steward (except to note that
|
||||||
|
such modified license differs from this License).
|
||||||
|
|
||||||
|
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||||
|
Licenses
|
||||||
|
|
||||||
|
If You choose to distribute Source Code Form that is Incompatible With
|
||||||
|
Secondary Licenses under the terms of this version of the License, the
|
||||||
|
notice described in Exhibit B of this License must be attached.
|
||||||
|
|
||||||
|
Exhibit A - Source Code Form License Notice
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
If it is not possible or desirable to put the notice in a particular
|
||||||
|
file, then You may include the notice in a location (such as a LICENSE
|
||||||
|
file in a relevant directory) where a recipient would be likely to look
|
||||||
|
for such a notice.
|
||||||
|
|
||||||
|
You may add additional accurate notices of copyright ownership.
|
||||||
|
|
||||||
|
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||||
|
---------------------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
|
defined by the Mozilla Public License, v. 2.0.
|
||||||
535
src/ion/data_structures.rs
Normal file
535
src/ion/data_structures.rs
Normal file
@@ -0,0 +1,535 @@
|
|||||||
|
/*
|
||||||
|
* The following license applies to this file, which was initially
|
||||||
|
* derived from the files `js/src/jit/BacktrackingAllocator.h` and
|
||||||
|
* `js/src/jit/BacktrackingAllocator.cpp` in Mozilla Firefox:
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*
|
||||||
|
* Since the initial port, the design has been substantially evolved
|
||||||
|
* and optimized.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! Data structures for backtracking allocator.
|
||||||
|
|
||||||
|
use super::liveranges::SpillWeight;
|
||||||
|
use crate::cfg::CFGInfo;
|
||||||
|
use crate::index::ContainerComparator;
|
||||||
|
use crate::indexset::IndexSet;
|
||||||
|
use crate::{
|
||||||
|
define_index, Allocation, Block, Edit, Function, Inst, MachineEnv, Operand, PReg, ProgPoint,
|
||||||
|
RegClass, SpillSlot, VReg,
|
||||||
|
};
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
/// A range from `from` (inclusive) to `to` (exclusive).
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub struct CodeRange {
|
||||||
|
pub from: ProgPoint,
|
||||||
|
pub to: ProgPoint,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CodeRange {
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.from == self.to
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn contains(&self, other: &Self) -> bool {
|
||||||
|
other.from >= self.from && other.to <= self.to
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn contains_point(&self, other: ProgPoint) -> bool {
|
||||||
|
other >= self.from && other < self.to
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn overlaps(&self, other: &Self) -> bool {
|
||||||
|
other.to > self.from && other.from < self.to
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.to.inst().index() - self.from.inst().index()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::cmp::PartialOrd for CodeRange {
|
||||||
|
#[inline(always)]
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::cmp::Ord for CodeRange {
|
||||||
|
#[inline(always)]
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
if self.to <= other.from {
|
||||||
|
Ordering::Less
|
||||||
|
} else if self.from >= other.to {
|
||||||
|
Ordering::Greater
|
||||||
|
} else {
|
||||||
|
Ordering::Equal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
define_index!(LiveBundleIndex);
|
||||||
|
define_index!(LiveRangeIndex);
|
||||||
|
define_index!(SpillSetIndex);
|
||||||
|
define_index!(UseIndex);
|
||||||
|
define_index!(VRegIndex);
|
||||||
|
define_index!(PRegIndex);
|
||||||
|
define_index!(SpillSlotIndex);
|
||||||
|
|
||||||
|
/// Used to carry small sets of bundles, e.g. for conflict sets.
|
||||||
|
pub type LiveBundleVec = SmallVec<[LiveBundleIndex; 4]>;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct LiveRangeListEntry {
|
||||||
|
pub range: CodeRange,
|
||||||
|
pub index: LiveRangeIndex,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type LiveRangeList = SmallVec<[LiveRangeListEntry; 4]>;
|
||||||
|
pub type UseList = SmallVec<[Use; 2]>;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct LiveRange {
|
||||||
|
pub range: CodeRange,
|
||||||
|
|
||||||
|
pub vreg: VRegIndex,
|
||||||
|
pub bundle: LiveBundleIndex,
|
||||||
|
pub uses_spill_weight_and_flags: u32,
|
||||||
|
|
||||||
|
pub uses: UseList,
|
||||||
|
|
||||||
|
pub merged_into: LiveRangeIndex,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum LiveRangeFlag {
|
||||||
|
StartsAtDef = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LiveRange {
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn set_flag(&mut self, flag: LiveRangeFlag) {
|
||||||
|
self.uses_spill_weight_and_flags |= (flag as u32) << 29;
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn clear_flag(&mut self, flag: LiveRangeFlag) {
|
||||||
|
self.uses_spill_weight_and_flags &= !((flag as u32) << 29);
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn assign_flag(&mut self, flag: LiveRangeFlag, val: bool) {
|
||||||
|
let bit = if val { (flag as u32) << 29 } else { 0 };
|
||||||
|
self.uses_spill_weight_and_flags &= 0xe000_0000;
|
||||||
|
self.uses_spill_weight_and_flags |= bit;
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn has_flag(&self, flag: LiveRangeFlag) -> bool {
|
||||||
|
self.uses_spill_weight_and_flags & ((flag as u32) << 29) != 0
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn flag_word(&self) -> u32 {
|
||||||
|
self.uses_spill_weight_and_flags & 0xe000_0000
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn merge_flags(&mut self, flag_word: u32) {
|
||||||
|
self.uses_spill_weight_and_flags |= flag_word;
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn uses_spill_weight(&self) -> SpillWeight {
|
||||||
|
let bits = (self.uses_spill_weight_and_flags & 0x1fff_ffff) << 2;
|
||||||
|
SpillWeight::from_f32(f32::from_bits(bits))
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn set_uses_spill_weight(&mut self, weight: SpillWeight) {
|
||||||
|
let weight_bits = (weight.to_f32().to_bits() >> 2) & 0x1fff_ffff;
|
||||||
|
self.uses_spill_weight_and_flags =
|
||||||
|
(self.uses_spill_weight_and_flags & 0xe000_0000) | weight_bits;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct Use {
|
||||||
|
pub operand: Operand,
|
||||||
|
pub pos: ProgPoint,
|
||||||
|
pub slot: u8,
|
||||||
|
pub weight: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Use {
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn new(operand: Operand, pos: ProgPoint, slot: u8) -> Self {
|
||||||
|
Self {
|
||||||
|
operand,
|
||||||
|
pos,
|
||||||
|
slot,
|
||||||
|
// Weight is updated on insertion into LR.
|
||||||
|
weight: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const SLOT_NONE: u8 = u8::MAX;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct LiveBundle {
|
||||||
|
pub ranges: LiveRangeList,
|
||||||
|
pub spillset: SpillSetIndex,
|
||||||
|
pub allocation: Allocation,
|
||||||
|
pub prio: u32, // recomputed after every bulk update
|
||||||
|
pub spill_weight_and_props: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LiveBundle {
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn set_cached_spill_weight_and_props(
|
||||||
|
&mut self,
|
||||||
|
spill_weight: u32,
|
||||||
|
minimal: bool,
|
||||||
|
fixed: bool,
|
||||||
|
stack: bool,
|
||||||
|
) {
|
||||||
|
debug_assert!(spill_weight < ((1 << 29) - 1));
|
||||||
|
self.spill_weight_and_props = spill_weight
|
||||||
|
| (if minimal { 1 << 31 } else { 0 })
|
||||||
|
| (if fixed { 1 << 30 } else { 0 })
|
||||||
|
| (if stack { 1 << 29 } else { 0 });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn cached_minimal(&self) -> bool {
|
||||||
|
self.spill_weight_and_props & (1 << 31) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn cached_fixed(&self) -> bool {
|
||||||
|
self.spill_weight_and_props & (1 << 30) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn cached_stack(&self) -> bool {
|
||||||
|
self.spill_weight_and_props & (1 << 29) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn set_cached_fixed(&mut self) {
|
||||||
|
self.spill_weight_and_props |= 1 << 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn set_cached_stack(&mut self) {
|
||||||
|
self.spill_weight_and_props |= 1 << 29;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn cached_spill_weight(&self) -> u32 {
|
||||||
|
self.spill_weight_and_props & ((1 << 29) - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub struct BundleProperties {
|
||||||
|
pub minimal: bool,
|
||||||
|
pub fixed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct SpillSet {
|
||||||
|
pub vregs: SmallVec<[VRegIndex; 2]>,
|
||||||
|
pub slot: SpillSlotIndex,
|
||||||
|
pub reg_hint: PReg,
|
||||||
|
pub class: RegClass,
|
||||||
|
pub spill_bundle: LiveBundleIndex,
|
||||||
|
pub required: bool,
|
||||||
|
pub size: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct VRegData {
|
||||||
|
pub ranges: LiveRangeList,
|
||||||
|
pub blockparam: Block,
|
||||||
|
pub is_ref: bool,
|
||||||
|
pub is_pinned: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct PRegData {
|
||||||
|
pub reg: PReg,
|
||||||
|
pub allocations: LiveRangeSet,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Env<'a, F: Function> {
|
||||||
|
pub func: &'a F,
|
||||||
|
pub env: &'a MachineEnv,
|
||||||
|
pub cfginfo: CFGInfo,
|
||||||
|
pub liveins: Vec<IndexSet>,
|
||||||
|
pub liveouts: Vec<IndexSet>,
|
||||||
|
/// Blockparam outputs: from-vreg, (end of) from-block, (start of)
|
||||||
|
/// to-block, to-vreg. The field order is significant: these are sorted so
|
||||||
|
/// that a scan over vregs, then blocks in each range, can scan in
|
||||||
|
/// order through this (sorted) list and add allocs to the
|
||||||
|
/// half-move list.
|
||||||
|
pub blockparam_outs: Vec<(VRegIndex, Block, Block, VRegIndex)>,
|
||||||
|
/// Blockparam inputs: to-vreg, (start of) to-block, (end of)
|
||||||
|
/// from-block. As above for `blockparam_outs`, field order is
|
||||||
|
/// significant.
|
||||||
|
pub blockparam_ins: Vec<(VRegIndex, Block, Block)>,
|
||||||
|
/// Blockparam allocs: block, idx, vreg, alloc. Info to describe
|
||||||
|
/// blockparam locations at block entry, for metadata purposes
|
||||||
|
/// (e.g. for the checker).
|
||||||
|
pub blockparam_allocs: Vec<(Block, u32, VRegIndex, Allocation)>,
|
||||||
|
|
||||||
|
pub ranges: Vec<LiveRange>,
|
||||||
|
pub bundles: Vec<LiveBundle>,
|
||||||
|
pub spillsets: Vec<SpillSet>,
|
||||||
|
pub vregs: Vec<VRegData>,
|
||||||
|
pub vreg_regs: Vec<VReg>,
|
||||||
|
pub pregs: Vec<PRegData>,
|
||||||
|
pub allocation_queue: PrioQueue,
|
||||||
|
pub clobbers: Vec<Inst>, // Sorted list of insts with clobbers.
|
||||||
|
pub safepoints: Vec<Inst>, // Sorted list of safepoint insts.
|
||||||
|
pub safepoints_per_vreg: HashMap<usize, HashSet<Inst>>,
|
||||||
|
|
||||||
|
pub spilled_bundles: Vec<LiveBundleIndex>,
|
||||||
|
pub spillslots: Vec<SpillSlotData>,
|
||||||
|
pub slots_by_size: Vec<SpillSlotList>,
|
||||||
|
|
||||||
|
pub extra_spillslot: Vec<Option<Allocation>>,
|
||||||
|
|
||||||
|
// Program moves: these are moves in the provided program that we
|
||||||
|
// handle with our internal machinery, in order to avoid the
|
||||||
|
// overhead of ordinary operand processing. We expect the client
|
||||||
|
// to not generate any code for instructions that return
|
||||||
|
// `Some(..)` for `.is_move()`, and instead use the edits that we
|
||||||
|
// provide to implement those moves (or some simplified version of
|
||||||
|
// them) post-regalloc.
|
||||||
|
//
|
||||||
|
// (from-vreg, inst, from-alloc), sorted by (from-vreg, inst)
|
||||||
|
pub prog_move_srcs: Vec<((VRegIndex, Inst), Allocation)>,
|
||||||
|
// (to-vreg, inst, to-alloc), sorted by (to-vreg, inst)
|
||||||
|
pub prog_move_dsts: Vec<((VRegIndex, Inst), Allocation)>,
|
||||||
|
// (from-vreg, to-vreg) for bundle-merging.
|
||||||
|
pub prog_move_merges: Vec<(LiveRangeIndex, LiveRangeIndex)>,
|
||||||
|
|
||||||
|
// When multiple fixed-register constraints are present on a
|
||||||
|
// single VReg at a single program point (this can happen for,
|
||||||
|
// e.g., call args that use the same value multiple times), we
|
||||||
|
// remove all but one of the fixed-register constraints, make a
|
||||||
|
// note here, and add a clobber with that PReg instread to keep
|
||||||
|
// the register available. When we produce the final edit-list, we
|
||||||
|
// will insert a copy from wherever the VReg's primary allocation
|
||||||
|
// was to the approprate PReg.
|
||||||
|
//
|
||||||
|
// (progpoint, copy-from-preg, copy-to-preg, to-slot)
|
||||||
|
pub multi_fixed_reg_fixups: Vec<(ProgPoint, PRegIndex, PRegIndex, usize)>,
|
||||||
|
|
||||||
|
pub inserted_moves: Vec<InsertedMove>,
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
pub edits: Vec<(u32, InsertMovePrio, Edit)>,
|
||||||
|
pub allocs: Vec<Allocation>,
|
||||||
|
pub inst_alloc_offsets: Vec<u32>,
|
||||||
|
pub num_spillslots: u32,
|
||||||
|
pub safepoint_slots: Vec<(ProgPoint, SpillSlot)>,
|
||||||
|
|
||||||
|
pub allocated_bundle_count: usize,
|
||||||
|
|
||||||
|
pub stats: Stats,
|
||||||
|
|
||||||
|
// For debug output only: a list of textual annotations at every
|
||||||
|
// ProgPoint to insert into the final allocated program listing.
|
||||||
|
pub debug_annotations: std::collections::HashMap<ProgPoint, Vec<String>>,
|
||||||
|
pub annotations_enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct SpillSlotData {
|
||||||
|
pub ranges: LiveRangeSet,
|
||||||
|
pub class: RegClass,
|
||||||
|
pub alloc: Allocation,
|
||||||
|
pub next_spillslot: SpillSlotIndex,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct SpillSlotList {
|
||||||
|
pub first_spillslot: SpillSlotIndex,
|
||||||
|
pub last_spillslot: SpillSlotIndex,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct PrioQueue {
|
||||||
|
pub heap: std::collections::BinaryHeap<PrioQueueEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct PrioQueueEntry {
|
||||||
|
pub prio: u32,
|
||||||
|
pub bundle: LiveBundleIndex,
|
||||||
|
pub reg_hint: PReg,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct LiveRangeSet {
|
||||||
|
pub btree: BTreeMap<LiveRangeKey, LiveRangeIndex>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct LiveRangeKey {
|
||||||
|
pub from: u32,
|
||||||
|
pub to: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LiveRangeKey {
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn from_range(range: &CodeRange) -> Self {
|
||||||
|
Self {
|
||||||
|
from: range.from.to_index(),
|
||||||
|
to: range.to.to_index(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn to_range(&self) -> CodeRange {
|
||||||
|
CodeRange {
|
||||||
|
from: ProgPoint::from_index(self.from),
|
||||||
|
to: ProgPoint::from_index(self.to),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::cmp::PartialEq for LiveRangeKey {
|
||||||
|
#[inline(always)]
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.to > other.from && self.from < other.to
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::cmp::Eq for LiveRangeKey {}
|
||||||
|
impl std::cmp::PartialOrd for LiveRangeKey {
|
||||||
|
#[inline(always)]
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::cmp::Ord for LiveRangeKey {
|
||||||
|
#[inline(always)]
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
if self.to <= other.from {
|
||||||
|
std::cmp::Ordering::Less
|
||||||
|
} else if self.from >= other.to {
|
||||||
|
std::cmp::Ordering::Greater
|
||||||
|
} else {
|
||||||
|
std::cmp::Ordering::Equal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PrioQueueComparator<'a> {
|
||||||
|
pub prios: &'a [usize],
|
||||||
|
}
|
||||||
|
impl<'a> ContainerComparator for PrioQueueComparator<'a> {
|
||||||
|
type Ix = LiveBundleIndex;
|
||||||
|
fn compare(&self, a: Self::Ix, b: Self::Ix) -> std::cmp::Ordering {
|
||||||
|
self.prios[a.index()].cmp(&self.prios[b.index()])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrioQueue {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
PrioQueue {
|
||||||
|
heap: std::collections::BinaryHeap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn insert(&mut self, bundle: LiveBundleIndex, prio: usize, reg_hint: PReg) {
|
||||||
|
self.heap.push(PrioQueueEntry {
|
||||||
|
prio: prio as u32,
|
||||||
|
bundle,
|
||||||
|
reg_hint,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn is_empty(self) -> bool {
|
||||||
|
self.heap.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn pop(&mut self) -> Option<(LiveBundleIndex, PReg)> {
|
||||||
|
self.heap.pop().map(|entry| (entry.bundle, entry.reg_hint))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LiveRangeSet {
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
btree: BTreeMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct InsertedMove {
|
||||||
|
pub pos: ProgPoint,
|
||||||
|
pub prio: InsertMovePrio,
|
||||||
|
pub from_alloc: Allocation,
|
||||||
|
pub to_alloc: Allocation,
|
||||||
|
pub to_vreg: Option<VReg>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub enum InsertMovePrio {
|
||||||
|
InEdgeMoves,
|
||||||
|
BlockParam,
|
||||||
|
Regular,
|
||||||
|
PostRegular,
|
||||||
|
MultiFixedReg,
|
||||||
|
ReusedInput,
|
||||||
|
OutEdgeMoves,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
pub struct Stats {
|
||||||
|
pub livein_blocks: usize,
|
||||||
|
pub livein_iterations: usize,
|
||||||
|
pub initial_liverange_count: usize,
|
||||||
|
pub merged_bundle_count: usize,
|
||||||
|
pub prog_moves: usize,
|
||||||
|
pub prog_moves_dead_src: usize,
|
||||||
|
pub prog_move_merge_attempt: usize,
|
||||||
|
pub prog_move_merge_success: usize,
|
||||||
|
pub process_bundle_count: usize,
|
||||||
|
pub process_bundle_reg_probes_fixed: usize,
|
||||||
|
pub process_bundle_reg_success_fixed: usize,
|
||||||
|
pub process_bundle_bounding_range_probe_start_any: usize,
|
||||||
|
pub process_bundle_bounding_range_probes_any: usize,
|
||||||
|
pub process_bundle_bounding_range_success_any: usize,
|
||||||
|
pub process_bundle_reg_probe_start_any: usize,
|
||||||
|
pub process_bundle_reg_probes_any: usize,
|
||||||
|
pub process_bundle_reg_success_any: usize,
|
||||||
|
pub evict_bundle_event: usize,
|
||||||
|
pub evict_bundle_count: usize,
|
||||||
|
pub splits: usize,
|
||||||
|
pub splits_clobbers: usize,
|
||||||
|
pub splits_hot: usize,
|
||||||
|
pub splits_conflicts: usize,
|
||||||
|
pub splits_defs: usize,
|
||||||
|
pub splits_all: usize,
|
||||||
|
pub final_liverange_count: usize,
|
||||||
|
pub final_bundle_count: usize,
|
||||||
|
pub spill_bundle_count: usize,
|
||||||
|
pub spill_bundle_reg_probes: usize,
|
||||||
|
pub spill_bundle_reg_success: usize,
|
||||||
|
pub blockparam_ins_count: usize,
|
||||||
|
pub blockparam_outs_count: usize,
|
||||||
|
pub blockparam_allocs_count: usize,
|
||||||
|
pub halfmoves_count: usize,
|
||||||
|
pub edits_count: usize,
|
||||||
|
}
|
||||||
141
src/ion/dump.rs
Normal file
141
src/ion/dump.rs
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
//! Debugging output.
|
||||||
|
|
||||||
|
use super::Env;
|
||||||
|
use crate::{Block, Function, ProgPoint};
|
||||||
|
|
||||||
|
impl<'a, F: Function> Env<'a, F> {
|
||||||
|
pub fn dump_state(&self) {
|
||||||
|
log::trace!("Bundles:");
|
||||||
|
for (i, b) in self.bundles.iter().enumerate() {
|
||||||
|
log::trace!(
|
||||||
|
"bundle{}: spillset={:?} alloc={:?}",
|
||||||
|
i,
|
||||||
|
b.spillset,
|
||||||
|
b.allocation
|
||||||
|
);
|
||||||
|
for entry in &b.ranges {
|
||||||
|
log::trace!(
|
||||||
|
" * range {:?} -- {:?}: range{}",
|
||||||
|
entry.range.from,
|
||||||
|
entry.range.to,
|
||||||
|
entry.index.index()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log::trace!("VRegs:");
|
||||||
|
for (i, v) in self.vregs.iter().enumerate() {
|
||||||
|
log::trace!("vreg{}:", i);
|
||||||
|
for entry in &v.ranges {
|
||||||
|
log::trace!(
|
||||||
|
" * range {:?} -- {:?}: range{}",
|
||||||
|
entry.range.from,
|
||||||
|
entry.range.to,
|
||||||
|
entry.index.index()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log::trace!("Ranges:");
|
||||||
|
for (i, r) in self.ranges.iter().enumerate() {
|
||||||
|
log::trace!(
|
||||||
|
"range{}: range={:?} vreg={:?} bundle={:?} weight={:?}",
|
||||||
|
i,
|
||||||
|
r.range,
|
||||||
|
r.vreg,
|
||||||
|
r.bundle,
|
||||||
|
r.uses_spill_weight(),
|
||||||
|
);
|
||||||
|
for u in &r.uses {
|
||||||
|
log::trace!(" * use at {:?} (slot {}): {:?}", u.pos, u.slot, u.operand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn annotate(&mut self, progpoint: ProgPoint, s: String) {
|
||||||
|
if self.annotations_enabled {
|
||||||
|
self.debug_annotations
|
||||||
|
.entry(progpoint)
|
||||||
|
.or_insert_with(|| vec![])
|
||||||
|
.push(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dump_results(&self) {
|
||||||
|
log::info!("=== REGALLOC RESULTS ===");
|
||||||
|
for block in 0..self.func.num_blocks() {
|
||||||
|
let block = Block::new(block);
|
||||||
|
log::info!(
|
||||||
|
"block{}: [succs {:?} preds {:?}]",
|
||||||
|
block.index(),
|
||||||
|
self.func
|
||||||
|
.block_succs(block)
|
||||||
|
.iter()
|
||||||
|
.map(|b| b.index())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
self.func
|
||||||
|
.block_preds(block)
|
||||||
|
.iter()
|
||||||
|
.map(|b| b.index())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
);
|
||||||
|
for inst in self.func.block_insns(block).iter() {
|
||||||
|
for annotation in self
|
||||||
|
.debug_annotations
|
||||||
|
.get(&ProgPoint::before(inst))
|
||||||
|
.map(|v| &v[..])
|
||||||
|
.unwrap_or(&[])
|
||||||
|
{
|
||||||
|
log::info!(" inst{}-pre: {}", inst.index(), annotation);
|
||||||
|
}
|
||||||
|
let ops = self
|
||||||
|
.func
|
||||||
|
.inst_operands(inst)
|
||||||
|
.iter()
|
||||||
|
.map(|op| format!("{}", op))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let clobbers = self
|
||||||
|
.func
|
||||||
|
.inst_clobbers(inst)
|
||||||
|
.iter()
|
||||||
|
.map(|preg| format!("{}", preg))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let allocs = (0..ops.len())
|
||||||
|
.map(|i| format!("{}", self.get_alloc(inst, i)))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let opname = if self.func.is_branch(inst) {
|
||||||
|
"br"
|
||||||
|
} else if self.func.is_call(inst) {
|
||||||
|
"call"
|
||||||
|
} else if self.func.is_ret(inst) {
|
||||||
|
"ret"
|
||||||
|
} else {
|
||||||
|
"op"
|
||||||
|
};
|
||||||
|
let args = ops
|
||||||
|
.iter()
|
||||||
|
.zip(allocs.iter())
|
||||||
|
.map(|(op, alloc)| format!("{} [{}]", op, alloc))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let clobbers = if clobbers.is_empty() {
|
||||||
|
"".to_string()
|
||||||
|
} else {
|
||||||
|
format!(" [clobber: {}]", clobbers.join(", "))
|
||||||
|
};
|
||||||
|
log::info!(
|
||||||
|
" inst{}: {} {}{}",
|
||||||
|
inst.index(),
|
||||||
|
opname,
|
||||||
|
args.join(", "),
|
||||||
|
clobbers
|
||||||
|
);
|
||||||
|
for annotation in self
|
||||||
|
.debug_annotations
|
||||||
|
.get(&ProgPoint::after(inst))
|
||||||
|
.map(|v| &v[..])
|
||||||
|
.unwrap_or(&[])
|
||||||
|
{
|
||||||
|
log::info!(" inst{}-post: {}", inst.index(), annotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1249
src/ion/liveranges.rs
Normal file
1249
src/ion/liveranges.rs
Normal file
File diff suppressed because it is too large
Load Diff
439
src/ion/merge.rs
Normal file
439
src/ion/merge.rs
Normal file
@@ -0,0 +1,439 @@
|
|||||||
|
/*
|
||||||
|
* The following license applies to this file, which was initially
|
||||||
|
* derived from the files `js/src/jit/BacktrackingAllocator.h` and
|
||||||
|
* `js/src/jit/BacktrackingAllocator.cpp` in Mozilla Firefox:
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*
|
||||||
|
* Since the initial port, the design has been substantially evolved
|
||||||
|
* and optimized.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! Bundle merging.
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
Env, LiveBundleIndex, LiveRangeIndex, LiveRangeKey, Requirement, SpillSet, SpillSetIndex,
|
||||||
|
SpillSlotIndex, VRegIndex,
|
||||||
|
};
|
||||||
|
use crate::{Function, Inst, OperandConstraint, PReg};
|
||||||
|
use smallvec::smallvec;
|
||||||
|
|
||||||
|
impl<'a, F: Function> Env<'a, F> {
|
||||||
|
pub fn merge_bundles(&mut self, from: LiveBundleIndex, to: LiveBundleIndex) -> bool {
|
||||||
|
if from == to {
|
||||||
|
// Merge bundle into self -- trivial merge.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
log::trace!(
|
||||||
|
"merging from bundle{} to bundle{}",
|
||||||
|
from.index(),
|
||||||
|
to.index()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Both bundles must deal with the same RegClass.
|
||||||
|
let from_rc = self.spillsets[self.bundles[from.index()].spillset.index()].class;
|
||||||
|
let to_rc = self.spillsets[self.bundles[to.index()].spillset.index()].class;
|
||||||
|
if from_rc != to_rc {
|
||||||
|
log::trace!(" -> mismatching reg classes");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If either bundle is already assigned (due to a pinned vreg), don't merge.
|
||||||
|
if self.bundles[from.index()].allocation.is_some()
|
||||||
|
|| self.bundles[to.index()].allocation.is_some()
|
||||||
|
{
|
||||||
|
log::trace!("one of the bundles is already assigned (pinned)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
// Sanity check: both bundles should contain only ranges with appropriate VReg classes.
|
||||||
|
for entry in &self.bundles[from.index()].ranges {
|
||||||
|
let vreg = self.ranges[entry.index.index()].vreg;
|
||||||
|
assert_eq!(from_rc, self.vreg_regs[vreg.index()].class());
|
||||||
|
}
|
||||||
|
for entry in &self.bundles[to.index()].ranges {
|
||||||
|
let vreg = self.ranges[entry.index.index()].vreg;
|
||||||
|
assert_eq!(to_rc, self.vreg_regs[vreg.index()].class());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for overlap in LiveRanges and for conflicting
|
||||||
|
// requirements.
|
||||||
|
let ranges_from = &self.bundles[from.index()].ranges[..];
|
||||||
|
let ranges_to = &self.bundles[to.index()].ranges[..];
|
||||||
|
let mut idx_from = 0;
|
||||||
|
let mut idx_to = 0;
|
||||||
|
let mut range_count = 0;
|
||||||
|
while idx_from < ranges_from.len() && idx_to < ranges_to.len() {
|
||||||
|
range_count += 1;
|
||||||
|
if range_count > 200 {
|
||||||
|
log::trace!(
|
||||||
|
"reached merge complexity (range_count = {}); exiting",
|
||||||
|
range_count
|
||||||
|
);
|
||||||
|
// Limit merge complexity.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ranges_from[idx_from].range.from >= ranges_to[idx_to].range.to {
|
||||||
|
idx_to += 1;
|
||||||
|
} else if ranges_to[idx_to].range.from >= ranges_from[idx_from].range.to {
|
||||||
|
idx_from += 1;
|
||||||
|
} else {
|
||||||
|
// Overlap -- cannot merge.
|
||||||
|
log::trace!(
|
||||||
|
" -> overlap between {:?} and {:?}, exiting",
|
||||||
|
ranges_from[idx_from].index,
|
||||||
|
ranges_to[idx_to].index
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for a requirements conflict.
|
||||||
|
if self.bundles[from.index()].cached_stack()
|
||||||
|
|| self.bundles[from.index()].cached_fixed()
|
||||||
|
|| self.bundles[to.index()].cached_stack()
|
||||||
|
|| self.bundles[to.index()].cached_fixed()
|
||||||
|
{
|
||||||
|
let req = self
|
||||||
|
.compute_requirement(from)
|
||||||
|
.merge(self.compute_requirement(to));
|
||||||
|
if req == Requirement::Conflict {
|
||||||
|
log::trace!(" -> conflicting requirements; aborting merge");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log::trace!(" -> committing to merge");
|
||||||
|
|
||||||
|
// If we reach here, then the bundles do not overlap -- merge
|
||||||
|
// them! We do this with a merge-sort-like scan over both
|
||||||
|
// lists, building a new range list and replacing the list on
|
||||||
|
// `to` when we're done.
|
||||||
|
if ranges_from.is_empty() {
|
||||||
|
// `from` bundle is empty -- trivial merge.
|
||||||
|
log::trace!(" -> from bundle{} is empty; trivial merge", from.index());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ranges_to.is_empty() {
|
||||||
|
// `to` bundle is empty -- just move the list over from
|
||||||
|
// `from` and set `bundle` up-link on all ranges.
|
||||||
|
log::trace!(" -> to bundle{} is empty; trivial merge", to.index());
|
||||||
|
let list = std::mem::replace(&mut self.bundles[from.index()].ranges, smallvec![]);
|
||||||
|
for entry in &list {
|
||||||
|
self.ranges[entry.index.index()].bundle = to;
|
||||||
|
|
||||||
|
if self.annotations_enabled {
|
||||||
|
self.annotate(
|
||||||
|
entry.range.from,
|
||||||
|
format!(
|
||||||
|
" MERGE range{} v{} from bundle{} to bundle{}",
|
||||||
|
entry.index.index(),
|
||||||
|
self.ranges[entry.index.index()].vreg.index(),
|
||||||
|
from.index(),
|
||||||
|
to.index(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.bundles[to.index()].ranges = list;
|
||||||
|
|
||||||
|
if self.bundles[from.index()].cached_stack() {
|
||||||
|
self.bundles[to.index()].set_cached_stack();
|
||||||
|
}
|
||||||
|
if self.bundles[from.index()].cached_fixed() {
|
||||||
|
self.bundles[to.index()].set_cached_fixed();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
log::trace!(
|
||||||
|
"merging: ranges_from = {:?} ranges_to = {:?}",
|
||||||
|
ranges_from,
|
||||||
|
ranges_to
|
||||||
|
);
|
||||||
|
|
||||||
|
// Two non-empty lists of LiveRanges: concatenate and
|
||||||
|
// sort. This is faster than a mergesort-like merge into a new
|
||||||
|
// list, empirically.
|
||||||
|
let from_list = std::mem::replace(&mut self.bundles[from.index()].ranges, smallvec![]);
|
||||||
|
for entry in &from_list {
|
||||||
|
self.ranges[entry.index.index()].bundle = to;
|
||||||
|
}
|
||||||
|
self.bundles[to.index()]
|
||||||
|
.ranges
|
||||||
|
.extend_from_slice(&from_list[..]);
|
||||||
|
self.bundles[to.index()]
|
||||||
|
.ranges
|
||||||
|
.sort_unstable_by_key(|entry| entry.range.from);
|
||||||
|
|
||||||
|
if self.annotations_enabled {
|
||||||
|
log::trace!("merging: merged = {:?}", self.bundles[to.index()].ranges);
|
||||||
|
let mut last_range = None;
|
||||||
|
for i in 0..self.bundles[to.index()].ranges.len() {
|
||||||
|
let entry = self.bundles[to.index()].ranges[i];
|
||||||
|
if last_range.is_some() {
|
||||||
|
assert!(last_range.unwrap() < entry.range);
|
||||||
|
}
|
||||||
|
last_range = Some(entry.range);
|
||||||
|
|
||||||
|
if self.ranges[entry.index.index()].bundle == from {
|
||||||
|
self.annotate(
|
||||||
|
entry.range.from,
|
||||||
|
format!(
|
||||||
|
" MERGE range{} v{} from bundle{} to bundle{}",
|
||||||
|
entry.index.index(),
|
||||||
|
self.ranges[entry.index.index()].vreg.index(),
|
||||||
|
from.index(),
|
||||||
|
to.index(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
log::trace!(
|
||||||
|
" -> merged result for bundle{}: range{}",
|
||||||
|
to.index(),
|
||||||
|
entry.index.index(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.bundles[from.index()].spillset != self.bundles[to.index()].spillset {
|
||||||
|
let from_vregs = std::mem::replace(
|
||||||
|
&mut self.spillsets[self.bundles[from.index()].spillset.index()].vregs,
|
||||||
|
smallvec![],
|
||||||
|
);
|
||||||
|
let to_vregs = &mut self.spillsets[self.bundles[to.index()].spillset.index()].vregs;
|
||||||
|
for vreg in from_vregs {
|
||||||
|
if !to_vregs.contains(&vreg) {
|
||||||
|
to_vregs.push(vreg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.bundles[from.index()].cached_stack() {
|
||||||
|
self.bundles[to.index()].set_cached_stack();
|
||||||
|
}
|
||||||
|
if self.bundles[from.index()].cached_fixed() {
|
||||||
|
self.bundles[to.index()].set_cached_fixed();
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn merge_vreg_bundles(&mut self) {
|
||||||
|
// Create a bundle for every vreg, initially.
|
||||||
|
log::trace!("merge_vreg_bundles: creating vreg bundles");
|
||||||
|
for vreg in 0..self.vregs.len() {
|
||||||
|
let vreg = VRegIndex::new(vreg);
|
||||||
|
if self.vregs[vreg.index()].ranges.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is a pinned vreg, go ahead and add it to the
|
||||||
|
// commitment map, and avoid creating a bundle entirely.
|
||||||
|
if self.vregs[vreg.index()].is_pinned {
|
||||||
|
for entry in &self.vregs[vreg.index()].ranges {
|
||||||
|
let preg = self
|
||||||
|
.func
|
||||||
|
.is_pinned_vreg(self.vreg_regs[vreg.index()])
|
||||||
|
.unwrap();
|
||||||
|
let key = LiveRangeKey::from_range(&entry.range);
|
||||||
|
self.pregs[preg.index()]
|
||||||
|
.allocations
|
||||||
|
.btree
|
||||||
|
.insert(key, LiveRangeIndex::invalid());
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bundle = self.create_bundle();
|
||||||
|
self.bundles[bundle.index()].ranges = self.vregs[vreg.index()].ranges.clone();
|
||||||
|
log::trace!("vreg v{} gets bundle{}", vreg.index(), bundle.index());
|
||||||
|
for entry in &self.bundles[bundle.index()].ranges {
|
||||||
|
log::trace!(
|
||||||
|
" -> with LR range{}: {:?}",
|
||||||
|
entry.index.index(),
|
||||||
|
entry.range
|
||||||
|
);
|
||||||
|
self.ranges[entry.index.index()].bundle = bundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut fixed = false;
|
||||||
|
let mut stack = false;
|
||||||
|
for entry in &self.bundles[bundle.index()].ranges {
|
||||||
|
for u in &self.ranges[entry.index.index()].uses {
|
||||||
|
if let OperandConstraint::FixedReg(_) = u.operand.constraint() {
|
||||||
|
fixed = true;
|
||||||
|
}
|
||||||
|
if let OperandConstraint::Stack = u.operand.constraint() {
|
||||||
|
stack = true;
|
||||||
|
}
|
||||||
|
if fixed && stack {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if fixed {
|
||||||
|
self.bundles[bundle.index()].set_cached_fixed();
|
||||||
|
}
|
||||||
|
if stack {
|
||||||
|
self.bundles[bundle.index()].set_cached_stack();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a spillslot for this bundle.
|
||||||
|
let ssidx = SpillSetIndex::new(self.spillsets.len());
|
||||||
|
let reg = self.vreg_regs[vreg.index()];
|
||||||
|
let size = self.func.spillslot_size(reg.class()) as u8;
|
||||||
|
self.spillsets.push(SpillSet {
|
||||||
|
vregs: smallvec![vreg],
|
||||||
|
slot: SpillSlotIndex::invalid(),
|
||||||
|
size,
|
||||||
|
required: false,
|
||||||
|
class: reg.class(),
|
||||||
|
reg_hint: PReg::invalid(),
|
||||||
|
spill_bundle: LiveBundleIndex::invalid(),
|
||||||
|
});
|
||||||
|
self.bundles[bundle.index()].spillset = ssidx;
|
||||||
|
}
|
||||||
|
|
||||||
|
for inst in 0..self.func.num_insts() {
|
||||||
|
let inst = Inst::new(inst);
|
||||||
|
|
||||||
|
// Attempt to merge Reuse-constraint operand outputs with the
|
||||||
|
// corresponding inputs.
|
||||||
|
for op in self.func.inst_operands(inst) {
|
||||||
|
if let OperandConstraint::Reuse(reuse_idx) = op.constraint() {
|
||||||
|
let src_vreg = op.vreg();
|
||||||
|
let dst_vreg = self.func.inst_operands(inst)[reuse_idx].vreg();
|
||||||
|
if self.vregs[src_vreg.vreg()].is_pinned
|
||||||
|
|| self.vregs[dst_vreg.vreg()].is_pinned
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
log::trace!(
|
||||||
|
"trying to merge reused-input def: src {} to dst {}",
|
||||||
|
src_vreg,
|
||||||
|
dst_vreg
|
||||||
|
);
|
||||||
|
let src_bundle =
|
||||||
|
self.ranges[self.vregs[src_vreg.vreg()].ranges[0].index.index()].bundle;
|
||||||
|
assert!(src_bundle.is_valid());
|
||||||
|
let dest_bundle =
|
||||||
|
self.ranges[self.vregs[dst_vreg.vreg()].ranges[0].index.index()].bundle;
|
||||||
|
assert!(dest_bundle.is_valid());
|
||||||
|
self.merge_bundles(/* from */ dest_bundle, /* to */ src_bundle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to merge blockparams with their inputs.
|
||||||
|
for i in 0..self.blockparam_outs.len() {
|
||||||
|
let (from_vreg, _, _, to_vreg) = self.blockparam_outs[i];
|
||||||
|
log::trace!(
|
||||||
|
"trying to merge blockparam v{} with input v{}",
|
||||||
|
to_vreg.index(),
|
||||||
|
from_vreg.index()
|
||||||
|
);
|
||||||
|
let to_bundle = self.ranges[self.vregs[to_vreg.index()].ranges[0].index.index()].bundle;
|
||||||
|
assert!(to_bundle.is_valid());
|
||||||
|
let from_bundle =
|
||||||
|
self.ranges[self.vregs[from_vreg.index()].ranges[0].index.index()].bundle;
|
||||||
|
assert!(from_bundle.is_valid());
|
||||||
|
log::trace!(
|
||||||
|
" -> from bundle{} to bundle{}",
|
||||||
|
from_bundle.index(),
|
||||||
|
to_bundle.index()
|
||||||
|
);
|
||||||
|
self.merge_bundles(from_bundle, to_bundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to merge move srcs/dsts.
|
||||||
|
for i in 0..self.prog_move_merges.len() {
|
||||||
|
let (src, dst) = self.prog_move_merges[i];
|
||||||
|
log::trace!("trying to merge move src LR {:?} to dst LR {:?}", src, dst);
|
||||||
|
let src = self.resolve_merged_lr(src);
|
||||||
|
let dst = self.resolve_merged_lr(dst);
|
||||||
|
log::trace!(
|
||||||
|
"resolved LR-construction merging chains: move-merge is now src LR {:?} to dst LR {:?}",
|
||||||
|
src,
|
||||||
|
dst
|
||||||
|
);
|
||||||
|
|
||||||
|
let dst_vreg = self.vreg_regs[self.ranges[dst.index()].vreg.index()];
|
||||||
|
let src_vreg = self.vreg_regs[self.ranges[src.index()].vreg.index()];
|
||||||
|
if self.vregs[src_vreg.vreg()].is_pinned && self.vregs[dst_vreg.vreg()].is_pinned {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if self.vregs[src_vreg.vreg()].is_pinned {
|
||||||
|
let dest_bundle = self.ranges[dst.index()].bundle;
|
||||||
|
let spillset = self.bundles[dest_bundle.index()].spillset;
|
||||||
|
self.spillsets[spillset.index()].reg_hint =
|
||||||
|
self.func.is_pinned_vreg(src_vreg).unwrap();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if self.vregs[dst_vreg.vreg()].is_pinned {
|
||||||
|
let src_bundle = self.ranges[src.index()].bundle;
|
||||||
|
let spillset = self.bundles[src_bundle.index()].spillset;
|
||||||
|
self.spillsets[spillset.index()].reg_hint =
|
||||||
|
self.func.is_pinned_vreg(dst_vreg).unwrap();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let src_bundle = self.ranges[src.index()].bundle;
|
||||||
|
assert!(src_bundle.is_valid());
|
||||||
|
let dest_bundle = self.ranges[dst.index()].bundle;
|
||||||
|
assert!(dest_bundle.is_valid());
|
||||||
|
self.stats.prog_move_merge_attempt += 1;
|
||||||
|
if self.merge_bundles(/* from */ dest_bundle, /* to */ src_bundle) {
|
||||||
|
self.stats.prog_move_merge_success += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log::trace!("done merging bundles");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve_merged_lr(&self, mut lr: LiveRangeIndex) -> LiveRangeIndex {
|
||||||
|
let mut iter = 0;
|
||||||
|
while iter < 100 && self.ranges[lr.index()].merged_into.is_valid() {
|
||||||
|
lr = self.ranges[lr.index()].merged_into;
|
||||||
|
iter += 1;
|
||||||
|
}
|
||||||
|
lr
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compute_bundle_prio(&self, bundle: LiveBundleIndex) -> u32 {
|
||||||
|
// The priority is simply the total "length" -- the number of
|
||||||
|
// instructions covered by all LiveRanges.
|
||||||
|
let mut total = 0;
|
||||||
|
for entry in &self.bundles[bundle.index()].ranges {
|
||||||
|
total += entry.range.len() as u32;
|
||||||
|
}
|
||||||
|
total
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn queue_bundles(&mut self) {
|
||||||
|
for bundle in 0..self.bundles.len() {
|
||||||
|
log::trace!("enqueueing bundle{}", bundle);
|
||||||
|
if self.bundles[bundle].ranges.is_empty() {
|
||||||
|
log::trace!(" -> no ranges; skipping");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let bundle = LiveBundleIndex::new(bundle);
|
||||||
|
let prio = self.compute_bundle_prio(bundle);
|
||||||
|
log::trace!(" -> prio {}", prio);
|
||||||
|
self.bundles[bundle.index()].prio = prio;
|
||||||
|
self.recompute_bundle_properties(bundle);
|
||||||
|
self.allocation_queue
|
||||||
|
.insert(bundle, prio as usize, PReg::invalid());
|
||||||
|
}
|
||||||
|
self.stats.merged_bundle_count = self.allocation_queue.heap.len();
|
||||||
|
}
|
||||||
|
}
|
||||||
145
src/ion/mod.rs
Normal file
145
src/ion/mod.rs
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
* The following license applies to this file, which was initially
|
||||||
|
* derived from the files `js/src/jit/BacktrackingAllocator.h` and
|
||||||
|
* `js/src/jit/BacktrackingAllocator.cpp` in Mozilla Firefox:
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*
|
||||||
|
* Since the initial port, the design has been substantially evolved
|
||||||
|
* and optimized.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! Backtracking register allocator. See doc/DESIGN.md for details of
|
||||||
|
//! its design.
|
||||||
|
|
||||||
|
use crate::cfg::CFGInfo;
|
||||||
|
use crate::{Function, MachineEnv, Output, PReg, ProgPoint, RegAllocError, RegClass};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
pub(crate) mod data_structures;
|
||||||
|
pub use data_structures::Stats;
|
||||||
|
use data_structures::*;
|
||||||
|
pub(crate) mod reg_traversal;
|
||||||
|
use reg_traversal::*;
|
||||||
|
pub(crate) mod requirement;
|
||||||
|
use requirement::*;
|
||||||
|
pub(crate) mod redundant_moves;
|
||||||
|
use redundant_moves::*;
|
||||||
|
pub(crate) mod liveranges;
|
||||||
|
use liveranges::*;
|
||||||
|
pub(crate) mod merge;
|
||||||
|
pub(crate) mod process;
|
||||||
|
use process::*;
|
||||||
|
pub(crate) mod dump;
|
||||||
|
pub(crate) mod moves;
|
||||||
|
pub(crate) mod spill;
|
||||||
|
pub(crate) mod stackmap;
|
||||||
|
|
||||||
|
impl<'a, F: Function> Env<'a, F> {
|
||||||
|
pub(crate) fn new(
|
||||||
|
func: &'a F,
|
||||||
|
env: &'a MachineEnv,
|
||||||
|
cfginfo: CFGInfo,
|
||||||
|
annotations_enabled: bool,
|
||||||
|
) -> Self {
|
||||||
|
let n = func.num_insts();
|
||||||
|
Self {
|
||||||
|
func,
|
||||||
|
env,
|
||||||
|
cfginfo,
|
||||||
|
|
||||||
|
liveins: Vec::with_capacity(func.num_blocks()),
|
||||||
|
liveouts: Vec::with_capacity(func.num_blocks()),
|
||||||
|
blockparam_outs: vec![],
|
||||||
|
blockparam_ins: vec![],
|
||||||
|
blockparam_allocs: vec![],
|
||||||
|
bundles: Vec::with_capacity(n),
|
||||||
|
ranges: Vec::with_capacity(4 * n),
|
||||||
|
spillsets: Vec::with_capacity(n),
|
||||||
|
vregs: Vec::with_capacity(n),
|
||||||
|
vreg_regs: Vec::with_capacity(n),
|
||||||
|
pregs: vec![],
|
||||||
|
allocation_queue: PrioQueue::new(),
|
||||||
|
clobbers: vec![],
|
||||||
|
safepoints: vec![],
|
||||||
|
safepoints_per_vreg: HashMap::new(),
|
||||||
|
spilled_bundles: vec![],
|
||||||
|
spillslots: vec![],
|
||||||
|
slots_by_size: vec![],
|
||||||
|
allocated_bundle_count: 0,
|
||||||
|
|
||||||
|
extra_spillslot: vec![None, None],
|
||||||
|
|
||||||
|
prog_move_srcs: Vec::with_capacity(n / 2),
|
||||||
|
prog_move_dsts: Vec::with_capacity(n / 2),
|
||||||
|
prog_move_merges: Vec::with_capacity(n / 2),
|
||||||
|
|
||||||
|
multi_fixed_reg_fixups: vec![],
|
||||||
|
inserted_moves: vec![],
|
||||||
|
edits: Vec::with_capacity(n),
|
||||||
|
allocs: Vec::with_capacity(4 * n),
|
||||||
|
inst_alloc_offsets: vec![],
|
||||||
|
num_spillslots: 0,
|
||||||
|
safepoint_slots: vec![],
|
||||||
|
|
||||||
|
stats: Stats::default(),
|
||||||
|
|
||||||
|
debug_annotations: std::collections::HashMap::new(),
|
||||||
|
annotations_enabled,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn init(&mut self) -> Result<(), RegAllocError> {
|
||||||
|
self.create_pregs_and_vregs();
|
||||||
|
self.compute_liveness()?;
|
||||||
|
self.merge_vreg_bundles();
|
||||||
|
self.queue_bundles();
|
||||||
|
if log::log_enabled!(log::Level::Trace) {
|
||||||
|
self.dump_state();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn run(&mut self) -> Result<(), RegAllocError> {
|
||||||
|
self.process_bundles()?;
|
||||||
|
self.try_allocating_regs_for_spilled_bundles();
|
||||||
|
self.allocate_spillslots();
|
||||||
|
self.apply_allocations_and_insert_moves();
|
||||||
|
self.resolve_inserted_moves();
|
||||||
|
self.compute_stackmaps();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run<F: Function>(
|
||||||
|
func: &F,
|
||||||
|
mach_env: &MachineEnv,
|
||||||
|
enable_annotations: bool,
|
||||||
|
) -> Result<Output, RegAllocError> {
|
||||||
|
let cfginfo = CFGInfo::new(func)?;
|
||||||
|
|
||||||
|
let mut env = Env::new(func, mach_env, cfginfo, enable_annotations);
|
||||||
|
env.init()?;
|
||||||
|
|
||||||
|
env.run()?;
|
||||||
|
|
||||||
|
if enable_annotations {
|
||||||
|
env.dump_results();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Output {
|
||||||
|
edits: env
|
||||||
|
.edits
|
||||||
|
.into_iter()
|
||||||
|
.map(|(pos, _, edit)| (ProgPoint::from_index(pos), edit))
|
||||||
|
.collect(),
|
||||||
|
allocs: env.allocs,
|
||||||
|
inst_alloc_offsets: env.inst_alloc_offsets,
|
||||||
|
num_spillslots: env.num_spillslots as usize,
|
||||||
|
debug_locations: vec![],
|
||||||
|
safepoint_slots: env.safepoint_slots,
|
||||||
|
stats: env.stats,
|
||||||
|
})
|
||||||
|
}
|
||||||
1164
src/ion/moves.rs
Normal file
1164
src/ion/moves.rs
Normal file
File diff suppressed because it is too large
Load Diff
1079
src/ion/process.rs
Normal file
1079
src/ion/process.rs
Normal file
File diff suppressed because it is too large
Load Diff
142
src/ion/redundant_moves.rs
Normal file
142
src/ion/redundant_moves.rs
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
//! Redundant-move elimination.
|
||||||
|
|
||||||
|
use crate::{Allocation, VReg};
|
||||||
|
use fxhash::FxHashMap;
|
||||||
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum RedundantMoveState {
|
||||||
|
Copy(Allocation, Option<VReg>),
|
||||||
|
Orig(VReg),
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct RedundantMoveEliminator {
|
||||||
|
allocs: FxHashMap<Allocation, RedundantMoveState>,
|
||||||
|
reverse_allocs: FxHashMap<Allocation, SmallVec<[Allocation; 4]>>,
|
||||||
|
}
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub struct RedundantMoveAction {
|
||||||
|
pub elide: bool,
|
||||||
|
pub def_alloc: Option<(Allocation, VReg)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RedundantMoveEliminator {
|
||||||
|
pub fn process_move(
|
||||||
|
&mut self,
|
||||||
|
from: Allocation,
|
||||||
|
to: Allocation,
|
||||||
|
to_vreg: Option<VReg>,
|
||||||
|
) -> RedundantMoveAction {
|
||||||
|
// Look up the src and dest.
|
||||||
|
let from_state = self
|
||||||
|
.allocs
|
||||||
|
.get(&from)
|
||||||
|
.map(|&p| p)
|
||||||
|
.unwrap_or(RedundantMoveState::None);
|
||||||
|
let to_state = self
|
||||||
|
.allocs
|
||||||
|
.get(&to)
|
||||||
|
.map(|&p| p)
|
||||||
|
.unwrap_or(RedundantMoveState::None);
|
||||||
|
|
||||||
|
log::trace!(
|
||||||
|
" -> redundant move tracker: from {} to {} to_vreg {:?}",
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
to_vreg
|
||||||
|
);
|
||||||
|
log::trace!(
|
||||||
|
" -> from_state {:?} to_state {:?}",
|
||||||
|
from_state,
|
||||||
|
to_state
|
||||||
|
);
|
||||||
|
|
||||||
|
if from == to && to_vreg.is_some() {
|
||||||
|
self.clear_alloc(to);
|
||||||
|
self.allocs
|
||||||
|
.insert(to, RedundantMoveState::Orig(to_vreg.unwrap()));
|
||||||
|
return RedundantMoveAction {
|
||||||
|
elide: true,
|
||||||
|
def_alloc: Some((to, to_vreg.unwrap())),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let src_vreg = match from_state {
|
||||||
|
RedundantMoveState::Copy(_, opt_r) => opt_r,
|
||||||
|
RedundantMoveState::Orig(r) => Some(r),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
log::trace!(" -> src_vreg {:?}", src_vreg);
|
||||||
|
let dst_vreg = to_vreg.or(src_vreg);
|
||||||
|
log::trace!(" -> dst_vreg {:?}", dst_vreg);
|
||||||
|
let existing_dst_vreg = match to_state {
|
||||||
|
RedundantMoveState::Copy(_, opt_r) => opt_r,
|
||||||
|
RedundantMoveState::Orig(r) => Some(r),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
log::trace!(" -> existing_dst_vreg {:?}", existing_dst_vreg);
|
||||||
|
|
||||||
|
let elide = match (from_state, to_state) {
|
||||||
|
(_, RedundantMoveState::Copy(orig_alloc, _)) if orig_alloc == from => true,
|
||||||
|
(RedundantMoveState::Copy(new_alloc, _), _) if new_alloc == to => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
log::trace!(" -> elide {}", elide);
|
||||||
|
|
||||||
|
let def_alloc = if dst_vreg != existing_dst_vreg && dst_vreg.is_some() {
|
||||||
|
Some((to, dst_vreg.unwrap()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
log::trace!(" -> def_alloc {:?}", def_alloc);
|
||||||
|
|
||||||
|
// Invalidate all existing copies of `to` if `to` actually changed value.
|
||||||
|
if !elide {
|
||||||
|
self.clear_alloc(to);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up forward and reverse mapping. Don't track stack-to-stack copies.
|
||||||
|
if from.is_reg() || to.is_reg() {
|
||||||
|
self.allocs
|
||||||
|
.insert(to, RedundantMoveState::Copy(from, dst_vreg));
|
||||||
|
log::trace!(
|
||||||
|
" -> create mapping {} -> {:?}",
|
||||||
|
to,
|
||||||
|
RedundantMoveState::Copy(from, dst_vreg)
|
||||||
|
);
|
||||||
|
self.reverse_allocs
|
||||||
|
.entry(from)
|
||||||
|
.or_insert_with(|| smallvec![])
|
||||||
|
.push(to);
|
||||||
|
}
|
||||||
|
|
||||||
|
RedundantMoveAction { elide, def_alloc }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
log::trace!(" redundant move eliminator cleared");
|
||||||
|
self.allocs.clear();
|
||||||
|
self.reverse_allocs.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_alloc(&mut self, alloc: Allocation) {
|
||||||
|
log::trace!(" redundant move eliminator: clear {:?}", alloc);
|
||||||
|
if let Some(ref mut existing_copies) = self.reverse_allocs.get_mut(&alloc) {
|
||||||
|
for to_inval in existing_copies.iter() {
|
||||||
|
log::trace!(" -> clear existing copy: {:?}", to_inval);
|
||||||
|
if let Some(val) = self.allocs.get_mut(to_inval) {
|
||||||
|
match val {
|
||||||
|
RedundantMoveState::Copy(_, Some(vreg)) => {
|
||||||
|
*val = RedundantMoveState::Orig(*vreg);
|
||||||
|
}
|
||||||
|
_ => *val = RedundantMoveState::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.allocs.remove(to_inval);
|
||||||
|
}
|
||||||
|
existing_copies.clear();
|
||||||
|
}
|
||||||
|
self.allocs.remove(&alloc);
|
||||||
|
}
|
||||||
|
}
|
||||||
123
src/ion/reg_traversal.rs
Normal file
123
src/ion/reg_traversal.rs
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
use crate::{MachineEnv, PReg, RegClass};
|
||||||
|
|
||||||
|
/// This iterator represents a traversal through all allocatable
|
||||||
|
/// registers of a given class, in a certain order designed to
|
||||||
|
/// minimize allocation contention.
|
||||||
|
///
|
||||||
|
/// The order in which we try registers is somewhat complex:
|
||||||
|
/// - First, if there is a hint, we try that.
|
||||||
|
/// - Then, we try registers in a traversal order that is based on an
|
||||||
|
/// "offset" (usually the bundle index) spreading pressure evenly
|
||||||
|
/// among registers to reduce commitment-map contention.
|
||||||
|
/// - Within that scan, we try registers in two groups: first,
|
||||||
|
/// prferred registers; then, non-preferred registers. (In normal
|
||||||
|
/// usage, these consist of caller-save and callee-save registers
|
||||||
|
/// respectively, to minimize clobber-saves; but they need not.)
|
||||||
|
|
||||||
|
pub struct RegTraversalIter<'a> {
|
||||||
|
env: &'a MachineEnv,
|
||||||
|
class: usize,
|
||||||
|
hints: [Option<PReg>; 2],
|
||||||
|
hint_idx: usize,
|
||||||
|
pref_idx: usize,
|
||||||
|
non_pref_idx: usize,
|
||||||
|
offset_pref: usize,
|
||||||
|
offset_non_pref: usize,
|
||||||
|
is_fixed: bool,
|
||||||
|
fixed: Option<PReg>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> RegTraversalIter<'a> {
|
||||||
|
pub fn new(
|
||||||
|
env: &'a MachineEnv,
|
||||||
|
class: RegClass,
|
||||||
|
hint_reg: PReg,
|
||||||
|
hint2_reg: PReg,
|
||||||
|
offset: usize,
|
||||||
|
fixed: Option<PReg>,
|
||||||
|
) -> Self {
|
||||||
|
let mut hint_reg = if hint_reg != PReg::invalid() {
|
||||||
|
Some(hint_reg)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let mut hint2_reg = if hint2_reg != PReg::invalid() {
|
||||||
|
Some(hint2_reg)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if hint_reg.is_none() {
|
||||||
|
hint_reg = hint2_reg;
|
||||||
|
hint2_reg = None;
|
||||||
|
}
|
||||||
|
let hints = [hint_reg, hint2_reg];
|
||||||
|
let class = class as u8 as usize;
|
||||||
|
let offset_pref = if env.preferred_regs_by_class[class].len() > 0 {
|
||||||
|
offset % env.preferred_regs_by_class[class].len()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
let offset_non_pref = if env.non_preferred_regs_by_class[class].len() > 0 {
|
||||||
|
offset % env.non_preferred_regs_by_class[class].len()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
env,
|
||||||
|
class,
|
||||||
|
hints,
|
||||||
|
hint_idx: 0,
|
||||||
|
pref_idx: 0,
|
||||||
|
non_pref_idx: 0,
|
||||||
|
offset_pref,
|
||||||
|
offset_non_pref,
|
||||||
|
is_fixed: fixed.is_some(),
|
||||||
|
fixed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> std::iter::Iterator for RegTraversalIter<'a> {
|
||||||
|
type Item = PReg;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<PReg> {
|
||||||
|
if self.is_fixed {
|
||||||
|
let ret = self.fixed;
|
||||||
|
self.fixed = None;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wrap(idx: usize, limit: usize) -> usize {
|
||||||
|
if idx >= limit {
|
||||||
|
idx - limit
|
||||||
|
} else {
|
||||||
|
idx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.hint_idx < 2 && self.hints[self.hint_idx].is_some() {
|
||||||
|
let h = self.hints[self.hint_idx];
|
||||||
|
self.hint_idx += 1;
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
while self.pref_idx < self.env.preferred_regs_by_class[self.class].len() {
|
||||||
|
let arr = &self.env.preferred_regs_by_class[self.class][..];
|
||||||
|
let r = arr[wrap(self.pref_idx + self.offset_pref, arr.len())];
|
||||||
|
self.pref_idx += 1;
|
||||||
|
if Some(r) == self.hints[0] || Some(r) == self.hints[1] {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return Some(r);
|
||||||
|
}
|
||||||
|
while self.non_pref_idx < self.env.non_preferred_regs_by_class[self.class].len() {
|
||||||
|
let arr = &self.env.non_preferred_regs_by_class[self.class][..];
|
||||||
|
let r = arr[wrap(self.non_pref_idx + self.offset_non_pref, arr.len())];
|
||||||
|
self.non_pref_idx += 1;
|
||||||
|
if Some(r) == self.hints[0] || Some(r) == self.hints[1] {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return Some(r);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
94
src/ion/requirement.rs
Normal file
94
src/ion/requirement.rs
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
//! Requirements computation.
|
||||||
|
|
||||||
|
use super::{Env, LiveBundleIndex};
|
||||||
|
use crate::{Function, Operand, OperandConstraint, PReg, RegClass};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum Requirement {
|
||||||
|
Unknown,
|
||||||
|
Fixed(PReg),
|
||||||
|
Register(RegClass),
|
||||||
|
Stack(RegClass),
|
||||||
|
Any(RegClass),
|
||||||
|
Conflict,
|
||||||
|
}
|
||||||
|
impl Requirement {
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn class(self) -> RegClass {
|
||||||
|
match self {
|
||||||
|
Requirement::Unknown => panic!("No class for unknown Requirement"),
|
||||||
|
Requirement::Fixed(preg) => preg.class(),
|
||||||
|
Requirement::Register(class) | Requirement::Any(class) | Requirement::Stack(class) => {
|
||||||
|
class
|
||||||
|
}
|
||||||
|
Requirement::Conflict => panic!("No class for conflicted Requirement"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn merge(self, other: Requirement) -> Requirement {
|
||||||
|
match (self, other) {
|
||||||
|
(Requirement::Unknown, other) | (other, Requirement::Unknown) => other,
|
||||||
|
(Requirement::Conflict, _) | (_, Requirement::Conflict) => Requirement::Conflict,
|
||||||
|
(other, Requirement::Any(rc)) | (Requirement::Any(rc), other) => {
|
||||||
|
if other.class() == rc {
|
||||||
|
other
|
||||||
|
} else {
|
||||||
|
Requirement::Conflict
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Requirement::Stack(rc1), Requirement::Stack(rc2)) => {
|
||||||
|
if rc1 == rc2 {
|
||||||
|
self
|
||||||
|
} else {
|
||||||
|
Requirement::Conflict
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Requirement::Register(rc), Requirement::Fixed(preg))
|
||||||
|
| (Requirement::Fixed(preg), Requirement::Register(rc)) => {
|
||||||
|
if rc == preg.class() {
|
||||||
|
Requirement::Fixed(preg)
|
||||||
|
} else {
|
||||||
|
Requirement::Conflict
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Requirement::Register(rc1), Requirement::Register(rc2)) => {
|
||||||
|
if rc1 == rc2 {
|
||||||
|
self
|
||||||
|
} else {
|
||||||
|
Requirement::Conflict
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Requirement::Fixed(a), Requirement::Fixed(b)) if a == b => self,
|
||||||
|
_ => Requirement::Conflict,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn from_operand(op: Operand) -> Requirement {
|
||||||
|
match op.constraint() {
|
||||||
|
OperandConstraint::FixedReg(preg) => Requirement::Fixed(preg),
|
||||||
|
OperandConstraint::Reg | OperandConstraint::Reuse(_) => {
|
||||||
|
Requirement::Register(op.class())
|
||||||
|
}
|
||||||
|
OperandConstraint::Stack => Requirement::Stack(op.class()),
|
||||||
|
_ => Requirement::Any(op.class()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, F: Function> Env<'a, F> {
|
||||||
|
pub fn compute_requirement(&self, bundle: LiveBundleIndex) -> Requirement {
|
||||||
|
let mut req = Requirement::Unknown;
|
||||||
|
log::trace!("compute_requirement: {:?}", bundle);
|
||||||
|
for entry in &self.bundles[bundle.index()].ranges {
|
||||||
|
log::trace!(" -> LR {:?}", entry.index);
|
||||||
|
for u in &self.ranges[entry.index.index()].uses {
|
||||||
|
log::trace!(" -> use {:?}", u);
|
||||||
|
let r = Requirement::from_operand(u.operand);
|
||||||
|
req = req.merge(r);
|
||||||
|
log::trace!(" -> req {:?}", req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log::trace!(" -> final: {:?}", req);
|
||||||
|
req
|
||||||
|
}
|
||||||
|
}
|
||||||
218
src/ion/spill.rs
Normal file
218
src/ion/spill.rs
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
/*
|
||||||
|
* The following license applies to this file, which was initially
|
||||||
|
* derived from the files `js/src/jit/BacktrackingAllocator.h` and
|
||||||
|
* `js/src/jit/BacktrackingAllocator.cpp` in Mozilla Firefox:
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*
|
||||||
|
* Since the initial port, the design has been substantially evolved
|
||||||
|
* and optimized.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! Spillslot allocation.
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
AllocRegResult, Env, LiveRangeKey, LiveRangeSet, PReg, PRegIndex, RegClass, RegTraversalIter,
|
||||||
|
SpillSetIndex, SpillSlotData, SpillSlotIndex, SpillSlotList,
|
||||||
|
};
|
||||||
|
use crate::{Allocation, Function, SpillSlot};
|
||||||
|
|
||||||
|
impl<'a, F: Function> Env<'a, F> {
|
||||||
|
pub fn try_allocating_regs_for_spilled_bundles(&mut self) {
|
||||||
|
log::trace!("allocating regs for spilled bundles");
|
||||||
|
for i in 0..self.spilled_bundles.len() {
|
||||||
|
let bundle = self.spilled_bundles[i]; // don't borrow self
|
||||||
|
|
||||||
|
let class = self.spillsets[self.bundles[bundle.index()].spillset.index()].class;
|
||||||
|
let hint = self.spillsets[self.bundles[bundle.index()].spillset.index()].reg_hint;
|
||||||
|
|
||||||
|
// This may be an empty-range bundle whose ranges are not
|
||||||
|
// sorted; sort all range-lists again here.
|
||||||
|
self.bundles[bundle.index()]
|
||||||
|
.ranges
|
||||||
|
.sort_unstable_by_key(|entry| entry.range.from);
|
||||||
|
|
||||||
|
let mut success = false;
|
||||||
|
self.stats.spill_bundle_reg_probes += 1;
|
||||||
|
for preg in
|
||||||
|
RegTraversalIter::new(self.env, class, hint, PReg::invalid(), bundle.index(), None)
|
||||||
|
{
|
||||||
|
log::trace!("trying bundle {:?} to preg {:?}", bundle, preg);
|
||||||
|
let preg_idx = PRegIndex::new(preg.index());
|
||||||
|
if let AllocRegResult::Allocated(_) =
|
||||||
|
self.try_to_allocate_bundle_to_reg(bundle, preg_idx, None)
|
||||||
|
{
|
||||||
|
self.stats.spill_bundle_reg_success += 1;
|
||||||
|
success = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !success {
|
||||||
|
log::trace!(
|
||||||
|
"spilling bundle {:?}: marking spillset {:?} as required",
|
||||||
|
bundle,
|
||||||
|
self.bundles[bundle.index()].spillset
|
||||||
|
);
|
||||||
|
self.spillsets[self.bundles[bundle.index()].spillset.index()].required = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spillslot_can_fit_spillset(
|
||||||
|
&mut self,
|
||||||
|
spillslot: SpillSlotIndex,
|
||||||
|
spillset: SpillSetIndex,
|
||||||
|
) -> bool {
|
||||||
|
for &vreg in &self.spillsets[spillset.index()].vregs {
|
||||||
|
for entry in &self.vregs[vreg.index()].ranges {
|
||||||
|
if self.spillslots[spillslot.index()]
|
||||||
|
.ranges
|
||||||
|
.btree
|
||||||
|
.contains_key(&LiveRangeKey::from_range(&entry.range))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn allocate_spillset_to_spillslot(
|
||||||
|
&mut self,
|
||||||
|
spillset: SpillSetIndex,
|
||||||
|
spillslot: SpillSlotIndex,
|
||||||
|
) {
|
||||||
|
self.spillsets[spillset.index()].slot = spillslot;
|
||||||
|
for i in 0..self.spillsets[spillset.index()].vregs.len() {
|
||||||
|
// don't borrow self
|
||||||
|
let vreg = self.spillsets[spillset.index()].vregs[i];
|
||||||
|
log::trace!(
|
||||||
|
"spillslot {:?} alloc'ed to spillset {:?}: vreg {:?}",
|
||||||
|
spillslot,
|
||||||
|
spillset,
|
||||||
|
vreg,
|
||||||
|
);
|
||||||
|
for entry in &self.vregs[vreg.index()].ranges {
|
||||||
|
log::trace!(
|
||||||
|
"spillslot {:?} getting range {:?} from LR {:?} from vreg {:?}",
|
||||||
|
spillslot,
|
||||||
|
entry.range,
|
||||||
|
entry.index,
|
||||||
|
vreg,
|
||||||
|
);
|
||||||
|
self.spillslots[spillslot.index()]
|
||||||
|
.ranges
|
||||||
|
.btree
|
||||||
|
.insert(LiveRangeKey::from_range(&entry.range), entry.index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn allocate_spillslots(&mut self) {
|
||||||
|
for spillset in 0..self.spillsets.len() {
|
||||||
|
log::trace!("allocate spillslot: {}", spillset);
|
||||||
|
let spillset = SpillSetIndex::new(spillset);
|
||||||
|
if !self.spillsets[spillset.index()].required {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Get or create the spillslot list for this size.
|
||||||
|
let size = self.spillsets[spillset.index()].size as usize;
|
||||||
|
if size >= self.slots_by_size.len() {
|
||||||
|
self.slots_by_size.resize(
|
||||||
|
size + 1,
|
||||||
|
SpillSlotList {
|
||||||
|
first_spillslot: SpillSlotIndex::invalid(),
|
||||||
|
last_spillslot: SpillSlotIndex::invalid(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Try a few existing spillslots.
|
||||||
|
let mut spillslot_iter = self.slots_by_size[size].first_spillslot;
|
||||||
|
let mut first_slot = SpillSlotIndex::invalid();
|
||||||
|
let mut prev = SpillSlotIndex::invalid();
|
||||||
|
let mut success = false;
|
||||||
|
for _attempt in 0..10 {
|
||||||
|
if spillslot_iter.is_invalid() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if spillslot_iter == first_slot {
|
||||||
|
// We've started looking at slots we placed at the end; end search.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if first_slot.is_invalid() {
|
||||||
|
first_slot = spillslot_iter;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.spillslot_can_fit_spillset(spillslot_iter, spillset) {
|
||||||
|
self.allocate_spillset_to_spillslot(spillset, spillslot_iter);
|
||||||
|
success = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Remove the slot and place it at the end of the respective list.
|
||||||
|
let next = self.spillslots[spillslot_iter.index()].next_spillslot;
|
||||||
|
if prev.is_valid() {
|
||||||
|
self.spillslots[prev.index()].next_spillslot = next;
|
||||||
|
} else {
|
||||||
|
self.slots_by_size[size].first_spillslot = next;
|
||||||
|
}
|
||||||
|
if !next.is_valid() {
|
||||||
|
self.slots_by_size[size].last_spillslot = prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
let last = self.slots_by_size[size].last_spillslot;
|
||||||
|
if last.is_valid() {
|
||||||
|
self.spillslots[last.index()].next_spillslot = spillslot_iter;
|
||||||
|
} else {
|
||||||
|
self.slots_by_size[size].first_spillslot = spillslot_iter;
|
||||||
|
}
|
||||||
|
self.slots_by_size[size].last_spillslot = spillslot_iter;
|
||||||
|
|
||||||
|
prev = spillslot_iter;
|
||||||
|
spillslot_iter = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !success {
|
||||||
|
// Allocate a new spillslot.
|
||||||
|
let spillslot = SpillSlotIndex::new(self.spillslots.len());
|
||||||
|
let next = self.slots_by_size[size].first_spillslot;
|
||||||
|
self.spillslots.push(SpillSlotData {
|
||||||
|
ranges: LiveRangeSet::new(),
|
||||||
|
next_spillslot: next,
|
||||||
|
alloc: Allocation::none(),
|
||||||
|
class: self.spillsets[spillset.index()].class,
|
||||||
|
});
|
||||||
|
self.slots_by_size[size].first_spillslot = spillslot;
|
||||||
|
if !next.is_valid() {
|
||||||
|
self.slots_by_size[size].last_spillslot = spillslot;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.allocate_spillset_to_spillslot(spillset, spillslot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign actual slot indices to spillslots.
|
||||||
|
for i in 0..self.spillslots.len() {
|
||||||
|
self.spillslots[i].alloc = self.allocate_spillslot(self.spillslots[i].class);
|
||||||
|
}
|
||||||
|
|
||||||
|
log::trace!("spillslot allocator done");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn allocate_spillslot(&mut self, class: RegClass) -> Allocation {
|
||||||
|
let size = self.func.spillslot_size(class) as u32;
|
||||||
|
let mut offset = self.num_spillslots;
|
||||||
|
// Align up to `size`.
|
||||||
|
debug_assert!(size.is_power_of_two());
|
||||||
|
offset = (offset + size - 1) & !(size - 1);
|
||||||
|
let slot = if self.func.multi_spillslot_named_by_last_slot() {
|
||||||
|
offset + size - 1
|
||||||
|
} else {
|
||||||
|
offset
|
||||||
|
};
|
||||||
|
offset += size;
|
||||||
|
self.num_spillslots = offset;
|
||||||
|
Allocation::stack(SpillSlot::new(slot as usize, class))
|
||||||
|
}
|
||||||
|
}
|
||||||
73
src/ion/stackmap.rs
Normal file
73
src/ion/stackmap.rs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* The following license applies to this file, which was initially
|
||||||
|
* derived from the files `js/src/jit/BacktrackingAllocator.h` and
|
||||||
|
* `js/src/jit/BacktrackingAllocator.cpp` in Mozilla Firefox:
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*
|
||||||
|
* Since the initial port, the design has been substantially evolved
|
||||||
|
* and optimized.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! Stackmap computation.
|
||||||
|
|
||||||
|
use super::{Env, ProgPoint, VRegIndex};
|
||||||
|
use crate::Function;
|
||||||
|
|
||||||
|
impl<'a, F: Function> Env<'a, F> {
|
||||||
|
pub fn compute_stackmaps(&mut self) {
|
||||||
|
// For each ref-typed vreg, iterate through ranges and find
|
||||||
|
// safepoints in-range. Add the SpillSlot to the stackmap.
|
||||||
|
|
||||||
|
if self.func.reftype_vregs().is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given `safepoints_per_vreg` from the liveness computation,
|
||||||
|
// all we have to do is, for each vreg in this map, step
|
||||||
|
// through the LiveRanges along with a sorted list of
|
||||||
|
// safepoints; and for each safepoint in the current range,
|
||||||
|
// emit the allocation into the `safepoint_slots` list.
|
||||||
|
|
||||||
|
log::trace!("safepoints_per_vreg = {:?}", self.safepoints_per_vreg);
|
||||||
|
|
||||||
|
for vreg in self.func.reftype_vregs() {
|
||||||
|
log::trace!("generating safepoint info for vreg {}", vreg);
|
||||||
|
let vreg = VRegIndex::new(vreg.vreg());
|
||||||
|
let mut safepoints: Vec<ProgPoint> = self
|
||||||
|
.safepoints_per_vreg
|
||||||
|
.get(&vreg.index())
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.map(|&inst| ProgPoint::before(inst))
|
||||||
|
.collect();
|
||||||
|
safepoints.sort_unstable();
|
||||||
|
log::trace!(" -> live over safepoints: {:?}", safepoints);
|
||||||
|
|
||||||
|
let mut safepoint_idx = 0;
|
||||||
|
for entry in &self.vregs[vreg.index()].ranges {
|
||||||
|
let range = entry.range;
|
||||||
|
let alloc = self.get_alloc_for_range(entry.index);
|
||||||
|
log::trace!(" -> range {:?}: alloc {}", range, alloc);
|
||||||
|
while safepoint_idx < safepoints.len() && safepoints[safepoint_idx] < range.to {
|
||||||
|
if safepoints[safepoint_idx] < range.from {
|
||||||
|
safepoint_idx += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
log::trace!(" -> covers safepoint {:?}", safepoints[safepoint_idx]);
|
||||||
|
|
||||||
|
let slot = alloc
|
||||||
|
.as_stack()
|
||||||
|
.expect("Reference-typed value not in spillslot at safepoint");
|
||||||
|
self.safepoint_slots.push((safepoints[safepoint_idx], slot));
|
||||||
|
safepoint_idx += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.safepoint_slots.sort_unstable();
|
||||||
|
log::trace!("final safepoint slots info: {:?}", self.safepoint_slots);
|
||||||
|
}
|
||||||
|
}
|
||||||
1262
src/lib.rs
Normal file
1262
src/lib.rs
Normal file
File diff suppressed because it is too large
Load Diff
206
src/moves.rs
Normal file
206
src/moves.rs
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
/*
|
||||||
|
* Released under the terms of the Apache 2.0 license with LLVM
|
||||||
|
* exception. See `LICENSE` for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::Allocation;
|
||||||
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
|
||||||
|
pub type MoveVec<T> = SmallVec<[(Allocation, Allocation, T); 16]>;
|
||||||
|
|
||||||
|
/// A `ParallelMoves` represents a list of alloc-to-alloc moves that
|
||||||
|
/// must happen in parallel -- i.e., all reads of sources semantically
|
||||||
|
/// happen before all writes of destinations, and destinations are
|
||||||
|
/// allowed to overwrite sources. It can compute a list of sequential
|
||||||
|
/// moves that will produce the equivalent data movement, possibly
|
||||||
|
/// using a scratch register if one is necessary.
|
||||||
|
pub struct ParallelMoves<T: Clone + Copy + Default> {
|
||||||
|
parallel_moves: MoveVec<T>,
|
||||||
|
scratch: Allocation,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone + Copy + Default> ParallelMoves<T> {
|
||||||
|
pub fn new(scratch: Allocation) -> Self {
|
||||||
|
Self {
|
||||||
|
parallel_moves: smallvec![],
|
||||||
|
scratch,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(&mut self, from: Allocation, to: Allocation, t: T) {
|
||||||
|
self.parallel_moves.push((from, to, t));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sources_overlap_dests(&self) -> bool {
|
||||||
|
// Assumes `parallel_moves` has already been sorted in `resolve()` below.
|
||||||
|
for &(_, dst, _) in &self.parallel_moves {
|
||||||
|
if self
|
||||||
|
.parallel_moves
|
||||||
|
.binary_search_by_key(&dst, |&(src, _, _)| src)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve(mut self) -> MoveVec<T> {
|
||||||
|
// Easy case: zero or one move. Just return our vec.
|
||||||
|
if self.parallel_moves.len() <= 1 {
|
||||||
|
return self.parallel_moves;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort moves by source so that we can efficiently test for
|
||||||
|
// presence.
|
||||||
|
self.parallel_moves.sort_by_key(|&(src, dst, _)| (src, dst));
|
||||||
|
|
||||||
|
// Do any dests overlap sources? If not, we can also just
|
||||||
|
// return the list.
|
||||||
|
if !self.sources_overlap_dests() {
|
||||||
|
return self.parallel_moves;
|
||||||
|
}
|
||||||
|
|
||||||
|
// General case: some moves overwrite dests that other moves
|
||||||
|
// read as sources. We'll use a general algorithm.
|
||||||
|
//
|
||||||
|
// *Important property*: because we expect that each register
|
||||||
|
// has only one writer (otherwise the effect of the parallel
|
||||||
|
// move is undefined), each move can only block one other move
|
||||||
|
// (with its one source corresponding to the one writer of
|
||||||
|
// that source). Thus, we *can only have simple cycles* (those
|
||||||
|
// that are a ring of nodes, i.e., with only one path from a
|
||||||
|
// node back to itself); there are no SCCs that are more
|
||||||
|
// complex than that. We leverage this fact below to avoid
|
||||||
|
// having to do a full Tarjan SCC DFS (with lowest-index
|
||||||
|
// computation, etc.): instead, as soon as we find a cycle, we
|
||||||
|
// know we have the full cycle and we can do a cyclic move
|
||||||
|
// sequence and continue.
|
||||||
|
|
||||||
|
// Sort moves by destination and check that each destination
|
||||||
|
// has only one writer.
|
||||||
|
self.parallel_moves.sort_by_key(|&(_, dst, _)| dst);
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
let mut last_dst = None;
|
||||||
|
for &(_, dst, _) in &self.parallel_moves {
|
||||||
|
if last_dst.is_some() {
|
||||||
|
debug_assert!(last_dst.unwrap() != dst);
|
||||||
|
}
|
||||||
|
last_dst = Some(dst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct a mapping from move indices to moves they must
|
||||||
|
// come before. Any given move must come before a move that
|
||||||
|
// overwrites its destination; we have moves sorted by dest
|
||||||
|
// above so we can efficiently find such a move, if any.
|
||||||
|
let mut must_come_before: SmallVec<[Option<usize>; 16]> =
|
||||||
|
smallvec![None; self.parallel_moves.len()];
|
||||||
|
for (i, &(src, _, _)) in self.parallel_moves.iter().enumerate() {
|
||||||
|
if let Ok(move_to_dst_idx) = self
|
||||||
|
.parallel_moves
|
||||||
|
.binary_search_by_key(&src, |&(_, dst, _)| dst)
|
||||||
|
{
|
||||||
|
must_come_before[i] = Some(move_to_dst_idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do a simple stack-based DFS and emit moves in postorder,
|
||||||
|
// then reverse at the end for RPO. Unlike Tarjan's SCC
|
||||||
|
// algorithm, we can emit a cycle as soon as we find one, as
|
||||||
|
// noted above.
|
||||||
|
let mut ret: MoveVec<T> = smallvec![];
|
||||||
|
let mut stack: SmallVec<[usize; 16]> = smallvec![];
|
||||||
|
let mut visited: SmallVec<[bool; 16]> = smallvec![false; self.parallel_moves.len()];
|
||||||
|
let mut onstack: SmallVec<[bool; 16]> = smallvec![false; self.parallel_moves.len()];
|
||||||
|
|
||||||
|
stack.push(0);
|
||||||
|
onstack[0] = true;
|
||||||
|
loop {
|
||||||
|
if stack.is_empty() {
|
||||||
|
if let Some(next) = visited.iter().position(|&flag| !flag) {
|
||||||
|
stack.push(next);
|
||||||
|
onstack[next] = true;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let top = *stack.last().unwrap();
|
||||||
|
visited[top] = true;
|
||||||
|
match must_come_before[top] {
|
||||||
|
None => {
|
||||||
|
ret.push(self.parallel_moves[top]);
|
||||||
|
onstack[top] = false;
|
||||||
|
stack.pop();
|
||||||
|
while let Some(top) = stack.pop() {
|
||||||
|
ret.push(self.parallel_moves[top]);
|
||||||
|
onstack[top] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(next) if visited[next] && !onstack[next] => {
|
||||||
|
ret.push(self.parallel_moves[top]);
|
||||||
|
onstack[top] = false;
|
||||||
|
stack.pop();
|
||||||
|
while let Some(top) = stack.pop() {
|
||||||
|
ret.push(self.parallel_moves[top]);
|
||||||
|
onstack[top] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(next) if !visited[next] && !onstack[next] => {
|
||||||
|
stack.push(next);
|
||||||
|
onstack[next] = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Some(next) => {
|
||||||
|
// Found a cycle -- emit a cyclic-move sequence
|
||||||
|
// for the cycle on the top of stack, then normal
|
||||||
|
// moves below it. Recall that these moves will be
|
||||||
|
// reversed in sequence, so from the original
|
||||||
|
// parallel move set
|
||||||
|
//
|
||||||
|
// { B := A, C := B, A := B }
|
||||||
|
//
|
||||||
|
// we will generate something like:
|
||||||
|
//
|
||||||
|
// A := scratch
|
||||||
|
// B := A
|
||||||
|
// C := B
|
||||||
|
// scratch := C
|
||||||
|
//
|
||||||
|
// which will become:
|
||||||
|
//
|
||||||
|
// scratch := C
|
||||||
|
// C := B
|
||||||
|
// B := A
|
||||||
|
// A := scratch
|
||||||
|
let mut last_dst = None;
|
||||||
|
let mut scratch_src = None;
|
||||||
|
while let Some(move_idx) = stack.pop() {
|
||||||
|
onstack[move_idx] = false;
|
||||||
|
let (mut src, dst, dst_t) = self.parallel_moves[move_idx];
|
||||||
|
if last_dst.is_none() {
|
||||||
|
scratch_src = Some(src);
|
||||||
|
src = self.scratch;
|
||||||
|
} else {
|
||||||
|
assert_eq!(last_dst.unwrap(), src);
|
||||||
|
}
|
||||||
|
ret.push((src, dst, dst_t));
|
||||||
|
|
||||||
|
last_dst = Some(dst);
|
||||||
|
|
||||||
|
if move_idx == next {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(src) = scratch_src {
|
||||||
|
ret.push((src, self.scratch, T::default()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.reverse();
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
||||||
56
src/postorder.rs
Normal file
56
src/postorder.rs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Released under the terms of the Apache 2.0 license with LLVM
|
||||||
|
* exception. See `LICENSE` for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! Fast postorder computation.
|
||||||
|
|
||||||
|
use crate::Block;
|
||||||
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
|
||||||
|
pub fn calculate<'a, SuccFn: Fn(Block) -> &'a [Block]>(
|
||||||
|
num_blocks: usize,
|
||||||
|
entry: Block,
|
||||||
|
succ_blocks: SuccFn,
|
||||||
|
) -> Vec<Block> {
|
||||||
|
let mut ret = vec![];
|
||||||
|
|
||||||
|
// State: visited-block map, and explicit DFS stack.
|
||||||
|
let mut visited = vec![];
|
||||||
|
visited.resize(num_blocks, false);
|
||||||
|
|
||||||
|
struct State<'a> {
|
||||||
|
block: Block,
|
||||||
|
succs: &'a [Block],
|
||||||
|
next_succ: usize,
|
||||||
|
}
|
||||||
|
let mut stack: SmallVec<[State; 64]> = smallvec![];
|
||||||
|
|
||||||
|
visited[entry.index()] = true;
|
||||||
|
stack.push(State {
|
||||||
|
block: entry,
|
||||||
|
succs: succ_blocks(entry),
|
||||||
|
next_succ: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
while let Some(ref mut state) = stack.last_mut() {
|
||||||
|
// Perform one action: push to new succ, skip an already-visited succ, or pop.
|
||||||
|
if state.next_succ < state.succs.len() {
|
||||||
|
let succ = state.succs[state.next_succ];
|
||||||
|
state.next_succ += 1;
|
||||||
|
if !visited[succ.index()] {
|
||||||
|
visited[succ.index()] = true;
|
||||||
|
stack.push(State {
|
||||||
|
block: succ,
|
||||||
|
succs: succ_blocks(succ),
|
||||||
|
next_succ: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ret.push(state.block);
|
||||||
|
stack.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret
|
||||||
|
}
|
||||||
98
src/ssa.rs
Normal file
98
src/ssa.rs
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* Released under the terms of the Apache 2.0 license with LLVM
|
||||||
|
* exception. See `LICENSE` for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//! SSA-related utilities.
|
||||||
|
|
||||||
|
use crate::cfg::CFGInfo;
|
||||||
|
|
||||||
|
use crate::{Block, Function, Inst, OperandKind, RegAllocError};
|
||||||
|
|
||||||
|
pub fn validate_ssa<F: Function>(f: &F, cfginfo: &CFGInfo) -> Result<(), RegAllocError> {
|
||||||
|
// Walk the blocks in arbitrary order. Check, for every use, that
|
||||||
|
// the def is either in the same block in an earlier inst, or is
|
||||||
|
// defined (by inst or blockparam) in some other block that
|
||||||
|
// dominates this one. Also check that for every block param and
|
||||||
|
// inst def, that this is the only def.
|
||||||
|
let mut defined = vec![false; f.num_vregs()];
|
||||||
|
for block in 0..f.num_blocks() {
|
||||||
|
let block = Block::new(block);
|
||||||
|
for blockparam in f.block_params(block) {
|
||||||
|
if defined[blockparam.vreg()] {
|
||||||
|
return Err(RegAllocError::SSA(*blockparam, Inst::invalid()));
|
||||||
|
}
|
||||||
|
defined[blockparam.vreg()] = true;
|
||||||
|
}
|
||||||
|
for iix in f.block_insns(block).iter() {
|
||||||
|
let operands = f.inst_operands(iix);
|
||||||
|
for operand in operands {
|
||||||
|
match operand.kind() {
|
||||||
|
OperandKind::Use => {
|
||||||
|
let def_block = if cfginfo.vreg_def_inst[operand.vreg().vreg()].is_valid() {
|
||||||
|
cfginfo.insn_block[cfginfo.vreg_def_inst[operand.vreg().vreg()].index()]
|
||||||
|
} else {
|
||||||
|
cfginfo.vreg_def_blockparam[operand.vreg().vreg()].0
|
||||||
|
};
|
||||||
|
if def_block.is_invalid() {
|
||||||
|
return Err(RegAllocError::SSA(operand.vreg(), iix));
|
||||||
|
}
|
||||||
|
if !cfginfo.dominates(def_block, block) {
|
||||||
|
return Err(RegAllocError::SSA(operand.vreg(), iix));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OperandKind::Def => {
|
||||||
|
if defined[operand.vreg().vreg()] {
|
||||||
|
return Err(RegAllocError::SSA(operand.vreg(), iix));
|
||||||
|
}
|
||||||
|
defined[operand.vreg().vreg()] = true;
|
||||||
|
}
|
||||||
|
OperandKind::Mod => {
|
||||||
|
// Mod (modify) operands are not used in SSA,
|
||||||
|
// but can be used by non-SSA code (e.g. with
|
||||||
|
// the regalloc.rs compatibility shim).
|
||||||
|
return Err(RegAllocError::SSA(operand.vreg(), iix));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the length of branch args matches the sum of the
|
||||||
|
// number of blockparams in their succs, and that the end of every
|
||||||
|
// block ends in this branch or in a ret, and that there are no
|
||||||
|
// other branches or rets in the middle of the block.
|
||||||
|
for block in 0..f.num_blocks() {
|
||||||
|
let block = Block::new(block);
|
||||||
|
let insns = f.block_insns(block);
|
||||||
|
for insn in insns.iter() {
|
||||||
|
if insn == insns.last() {
|
||||||
|
if !(f.is_branch(insn) || f.is_ret(insn)) {
|
||||||
|
return Err(RegAllocError::BB(block));
|
||||||
|
}
|
||||||
|
if f.is_branch(insn) {
|
||||||
|
let expected = f
|
||||||
|
.block_succs(block)
|
||||||
|
.iter()
|
||||||
|
.map(|&succ| f.block_params(succ).len())
|
||||||
|
.sum();
|
||||||
|
if f.inst_operands(insn).len() != expected {
|
||||||
|
return Err(RegAllocError::Branch(insn));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if f.is_branch(insn) || f.is_ret(insn) {
|
||||||
|
return Err(RegAllocError::BB(block));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the entry block has no block args: otherwise it is
|
||||||
|
// undefined what their value would be.
|
||||||
|
if f.block_params(f.entry_block()).len() > 0 {
|
||||||
|
return Err(RegAllocError::BB(f.entry_block()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
28
test/Cargo.toml
Normal file
28
test/Cargo.toml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
[package]
|
||||||
|
name = "regalloc2-test"
|
||||||
|
version = "0.0.1"
|
||||||
|
authors = ["Chris Fallin <chris@cfallin.org>", "Mozilla SpiderMonkey Developers"]
|
||||||
|
edition = "2018"
|
||||||
|
license = "Apache-2.0 WITH LLVM-exception AND MPL-2.0"
|
||||||
|
description = "small test driver for benchmarking regalloc2"
|
||||||
|
repository = "https://github.com/bytecodealliance/regalloc2"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
regalloc2 = { version = "*", path = "../", features = ["fuzzing"] }
|
||||||
|
|
||||||
|
# Keep this in sync with libfuzzer_sys's crate version:
|
||||||
|
arbitrary = { version = "^0.4.6" }
|
||||||
|
rand = { version = "0.8" }
|
||||||
|
rand_chacha = { version = "0.3" }
|
||||||
|
env_logger = { version = "*" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
criterion = "0.3"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
debug = true
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "regalloc"
|
||||||
|
harness = false
|
||||||
|
|
||||||
56
test/benches/regalloc.rs
Normal file
56
test/benches/regalloc.rs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
//! Criterion-based benchmark target that computes insts/second for
|
||||||
|
//! arbitrary inputs.
|
||||||
|
|
||||||
|
use arbitrary::{Arbitrary, Unstructured};
|
||||||
|
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
|
||||||
|
use rand::{Rng, SeedableRng};
|
||||||
|
use rand_chacha::ChaCha8Rng;
|
||||||
|
use regalloc2::fuzzing::func::{machine_env, Func};
|
||||||
|
use regalloc2::ion;
|
||||||
|
use regalloc2::Function;
|
||||||
|
|
||||||
|
fn create_random_func(seed: u64, size: usize) -> Func {
|
||||||
|
let mut bytes: Vec<u8> = vec![];
|
||||||
|
bytes.resize(size, 0);
|
||||||
|
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
||||||
|
rng.fill(&mut bytes[..]);
|
||||||
|
loop {
|
||||||
|
let mut u = Unstructured::new(&bytes[..]);
|
||||||
|
match Func::arbitrary(&mut u) {
|
||||||
|
Ok(f) => {
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
Err(arbitrary::Error::NotEnoughData) => {
|
||||||
|
let len = bytes.len();
|
||||||
|
bytes.resize(len + 1024, 0);
|
||||||
|
rng.fill(&mut bytes[len..]);
|
||||||
|
}
|
||||||
|
Err(e) => panic!("unexpected error: {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_regalloc(c: &mut Criterion) {
|
||||||
|
const SIZE: usize = 1000 * 1000;
|
||||||
|
env_logger::init();
|
||||||
|
let env = machine_env();
|
||||||
|
let mut group = c.benchmark_group("benches");
|
||||||
|
for iter in 0..3 {
|
||||||
|
let func = create_random_func(iter, SIZE);
|
||||||
|
eprintln!("==== {} instructions", func.insts());
|
||||||
|
group.throughput(Throughput::Elements(func.insts() as u64));
|
||||||
|
group.bench_with_input(BenchmarkId::from_parameter(iter), &iter, |b, _| {
|
||||||
|
b.iter(|| {
|
||||||
|
// For fair comparison with regalloc.rs, which needs
|
||||||
|
// to clone its Func on every alloc, we clone
|
||||||
|
// too. Seems to make a few percent difference.
|
||||||
|
let func = func.clone();
|
||||||
|
ion::run(&func, &env).expect("regalloc did not succeed");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(benches, run_regalloc);
|
||||||
|
criterion_main!(benches);
|
||||||
50
test/src/main.rs
Normal file
50
test/src/main.rs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Released under the terms of the Apache 2.0 license with LLVM
|
||||||
|
* exception. See `LICENSE` for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use arbitrary::{Arbitrary, Unstructured};
|
||||||
|
use rand::{Rng, SeedableRng};
|
||||||
|
use rand_chacha::ChaCha8Rng;
|
||||||
|
use regalloc2::fuzzing::func::{machine_env, Func};
|
||||||
|
use regalloc2::ion;
|
||||||
|
use regalloc2::Function;
|
||||||
|
|
||||||
|
fn create_random_func(seed: u64, size: usize) -> Func {
|
||||||
|
let mut bytes: Vec<u8> = vec![];
|
||||||
|
bytes.resize(size, 0);
|
||||||
|
let mut rng = ChaCha8Rng::seed_from_u64(seed);
|
||||||
|
rng.fill(&mut bytes[..]);
|
||||||
|
loop {
|
||||||
|
let mut u = Unstructured::new(&bytes[..]);
|
||||||
|
match Func::arbitrary(&mut u) {
|
||||||
|
Ok(f) => {
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
Err(arbitrary::Error::NotEnoughData) => {
|
||||||
|
let len = bytes.len();
|
||||||
|
bytes.resize(len + 1024, 0);
|
||||||
|
rng.fill(&mut bytes[len..]);
|
||||||
|
}
|
||||||
|
Err(e) => panic!("unexpected error: {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
const SIZE: usize = 1000 * 1000;
|
||||||
|
env_logger::init();
|
||||||
|
let env = machine_env();
|
||||||
|
for iter in 0..3 {
|
||||||
|
let func = create_random_func(iter, SIZE);
|
||||||
|
eprintln!("==== {} instructions", func.insts());
|
||||||
|
let mut stats: ion::Stats = ion::Stats::default();
|
||||||
|
for i in 0..1000 {
|
||||||
|
let out = ion::run(&func, &env).expect("regalloc did not succeed");
|
||||||
|
if i == 0 {
|
||||||
|
stats = out.stats;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
eprintln!("Stats: {:?}", stats);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user