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