Initial public commit of regalloc2.
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
Cargo.lock
|
||||||
|
target/
|
||||||
|
.*.swp
|
||||||
|
*~
|
||||||
27
Cargo.toml
Normal file
27
Cargo.toml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
[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 ported from IonMonkey"
|
||||||
|
repository = "https://github.com/cfallin/regalloc2"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
log = { version = "0.4.8", default-features = false }
|
||||||
|
smallvec = "1.6.1"
|
||||||
|
# keep this in sync with libfuzzer_sys's crate version:
|
||||||
|
arbitrary = "^0.4.6"
|
||||||
|
rand = "0.8"
|
||||||
|
rand_chacha = "0.3"
|
||||||
|
env_logger = "*"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
criterion = "0.3"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
debug = true
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "regalloc"
|
||||||
|
harness = false
|
||||||
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.
|
||||||
|
|
||||||
159
README.md
Normal file
159
README.md
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
## regalloc2: another register allocator
|
||||||
|
|
||||||
|
This is a register allocator that started life as, and is about 75%
|
||||||
|
still, a port of IonMonkey's backtracking register allocator to
|
||||||
|
Rust. The data structures and invariants have been simplified a little
|
||||||
|
bit, and the interfaces made a little more generic and reusable. In
|
||||||
|
addition, it contains substantial amounts of testing infrastructure
|
||||||
|
(fuzzing harnesses and checkers) that does not exist in the original
|
||||||
|
IonMonkey allocator.
|
||||||
|
|
||||||
|
### Design Overview
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
- SSA with blockparams
|
||||||
|
|
||||||
|
- Operands with constraints, and clobbers, and reused regs; contrast
|
||||||
|
with regalloc.rs approach of vregs and pregs and many moves that get
|
||||||
|
coalesced/elided
|
||||||
|
|
||||||
|
### Differences from IonMonkey Backtracking Allocator
|
||||||
|
|
||||||
|
There are a number of differences between the [IonMonkey
|
||||||
|
allocator](https://searchfox.org/mozilla-central/source/js/src/jit/BacktrackingAllocator.cpp)
|
||||||
|
and this one:
|
||||||
|
|
||||||
|
* Most significantly, there are [fuzz/fuzz_targets/](many different
|
||||||
|
fuzz targets) that exercise the allocator, including a full symbolic
|
||||||
|
checker (`ion_checker` target) based on the [symbolic checker in
|
||||||
|
regalloc.rs](https://cfallin.org/blog/2021/03/15/cranelift-isel-3/)
|
||||||
|
and, e.g., a targetted fuzzer for the parallel move-resolution
|
||||||
|
algorithm (`moves`) and the SSA generator used for generating cases
|
||||||
|
for the other fuzz targets (`ssagen`).
|
||||||
|
|
||||||
|
* The data-structure invariants are simplified. While the IonMonkey
|
||||||
|
allocator allowed for LiveRanges and Bundles to overlap in certain
|
||||||
|
cases, this allocator sticks to a strict invariant: ranges do not
|
||||||
|
overlap in bundles, and bundles do not overlap. There are other
|
||||||
|
examples too: e.g., the definition of minimal bundles is very simple
|
||||||
|
and does not depend on scanning the code at all. In general, we
|
||||||
|
should be able to state simple invariants and see by inspection (as
|
||||||
|
well as fuzzing -- see above) that they hold.
|
||||||
|
|
||||||
|
* Many of the algorithms in the IonMonkey allocator are built with
|
||||||
|
helper functions that do linear scans. These "small quadratic" loops
|
||||||
|
are likely not a huge issue in practice, but nevertheless have the
|
||||||
|
potential to be in corner cases. As much as possible, all work in
|
||||||
|
this allocator is done in linear scans. For example, bundle
|
||||||
|
splitting is done in a single compound scan over a bundle, ranges in
|
||||||
|
the bundle, and a sorted list of split-points.
|
||||||
|
|
||||||
|
* There are novel schemes for solving certain interesting design
|
||||||
|
challenges. One example: in IonMonkey, liveranges are connected
|
||||||
|
across blocks by, when reaching one end of a control-flow edge in a
|
||||||
|
scan, doing a lookup of the allocation at the other end. This is in
|
||||||
|
principle a linear lookup (so quadratic overall). We instead
|
||||||
|
generate a list of "half-moves", keyed on the edge and from/to
|
||||||
|
vregs, with each holding one of the allocations. By sorting and then
|
||||||
|
scanning this list, we can generate all edge moves in one linear
|
||||||
|
scan. There are a number of other examples of simplifications: for
|
||||||
|
example, we handle multiple conflicting
|
||||||
|
physical-register-constrained uses of a vreg in a single instruction
|
||||||
|
by recording a copy to do in a side-table, then removing constraints
|
||||||
|
for the core regalloc. Ion instead has to tweak its definition of
|
||||||
|
minimal bundles and create two liveranges that overlap (!) to
|
||||||
|
represent the two uses.
|
||||||
|
|
||||||
|
* Using block parameters rather than phi-nodes significantly
|
||||||
|
simplifies handling of inter-block data movement. IonMonkey had to
|
||||||
|
special-case phis in many ways because they are actually quite
|
||||||
|
weird: their uses happen semantically in other blocks, and their
|
||||||
|
defs happen in parallel at the top of the block. Block parameters
|
||||||
|
naturally and explicitly reprsent these semantics in a direct way.
|
||||||
|
|
||||||
|
* The allocator supports irreducible control flow and arbitrary block
|
||||||
|
ordering (its only CFG requirement is that critical edges are
|
||||||
|
split). It handles loops during live-range computation in a way that
|
||||||
|
is similar in spirit to IonMonkey's allocator -- in a single pass,
|
||||||
|
when we discover a loop, we just mark the whole loop as a liverange
|
||||||
|
for values live at the top of the loop -- but we find the loop body
|
||||||
|
without the fixpoint workqueue loop that IonMonkey uses, instead
|
||||||
|
doing a single linear scan for backedges and finding the minimal
|
||||||
|
extent that covers all intermingled loops. In order to support
|
||||||
|
arbitrary block order and irreducible control flow, we relax the
|
||||||
|
invariant that the first liverange for a vreg always starts at its
|
||||||
|
def; instead, the def can happen anywhere, and a liverange may
|
||||||
|
overapproximate. It turns out this is not too hard to handle and is
|
||||||
|
a more robust invariant. (It also means that non-SSA code *may* not
|
||||||
|
be too hard to adapt to, though I haven't seriously thought about
|
||||||
|
this.)
|
||||||
|
|
||||||
|
### Rough Performance Comparison with Regalloc.rs
|
||||||
|
|
||||||
|
The allocator has not yet been wired up to a suitable compiler backend
|
||||||
|
(such as Cranelift) to perform a true apples-to-apples compile-time
|
||||||
|
and runtime comparison. However, we can get some idea of compile speed
|
||||||
|
by running suitable test cases through the allocator and measuring
|
||||||
|
*throughput*: that is, instructions per second for which registers are
|
||||||
|
allocated.
|
||||||
|
|
||||||
|
To do so, I measured the `qsort2` benchmark in
|
||||||
|
[regalloc.rs](https://github.com/bytecodealliance/regalloc.rs),
|
||||||
|
register-allocated with default options in that crate's backtracking
|
||||||
|
allocator, using the Criterion benchmark framework to measure ~620K
|
||||||
|
instructions per second:
|
||||||
|
|
||||||
|
|
||||||
|
```plain
|
||||||
|
benches/0 time: [365.68 us 367.36 us 369.04 us]
|
||||||
|
thrpt: [617.82 Kelem/s 620.65 Kelem/s 623.49 Kelem/s]
|
||||||
|
```
|
||||||
|
|
||||||
|
I then measured three different fuzztest-SSA-generator test cases in
|
||||||
|
this allocator, `regalloc2`, measuring between 1.05M and 2.3M
|
||||||
|
instructions per second (closer to the former for larger functions):
|
||||||
|
|
||||||
|
```plain
|
||||||
|
==== 459 instructions
|
||||||
|
benches/0 time: [424.46 us 425.65 us 426.59 us]
|
||||||
|
thrpt: [1.0760 Melem/s 1.0784 Melem/s 1.0814 Melem/s]
|
||||||
|
|
||||||
|
==== 225 instructions
|
||||||
|
benches/1 time: [213.05 us 213.28 us 213.54 us]
|
||||||
|
thrpt: [1.0537 Melem/s 1.0549 Melem/s 1.0561 Melem/s]
|
||||||
|
|
||||||
|
Found 1 outliers among 100 measurements (1.00%)
|
||||||
|
1 (1.00%) high mild
|
||||||
|
==== 21 instructions
|
||||||
|
benches/2 time: [9.0495 us 9.0571 us 9.0641 us]
|
||||||
|
thrpt: [2.3168 Melem/s 2.3186 Melem/s 2.3206 Melem/s]
|
||||||
|
|
||||||
|
Found 4 outliers among 100 measurements (4.00%)
|
||||||
|
2 (2.00%) high mild
|
||||||
|
2 (2.00%) high severe
|
||||||
|
```
|
||||||
|
|
||||||
|
Though not apples-to-apples (SSA vs. non-SSA, completely different
|
||||||
|
code only with similar length), this is at least some evidence that
|
||||||
|
`regalloc2` is likely to lead to at least a compile-time improvement
|
||||||
|
when used in e.g. Cranelift.
|
||||||
|
|
||||||
|
### 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.
|
||||||
56
benches/regalloc.rs
Normal file
56
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);
|
||||||
4
fuzz/.gitignore
vendored
Normal file
4
fuzz/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
target
|
||||||
|
corpus
|
||||||
|
artifacts
|
||||||
54
fuzz/Cargo.toml
Normal file
54
fuzz/Cargo.toml
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
|
||||||
|
[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]
|
||||||
|
libfuzzer-sys = "0.3"
|
||||||
|
arbitrary = { version = "^0.4.6", features = ["derive"] }
|
||||||
|
log = { version = "0.4.8", default-features = false }
|
||||||
|
env_logger = "0.8.3"
|
||||||
|
|
||||||
|
[dependencies.regalloc2]
|
||||||
|
path = ".."
|
||||||
|
|
||||||
|
# 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
|
||||||
128
fuzz/fuzz_targets/domtree.rs
Normal file
128
fuzz/fuzz_targets/domtree.rs
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
#![no_main]
|
||||||
|
use libfuzzer_sys::arbitrary::{Arbitrary, Result, Unstructured};
|
||||||
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use regalloc2::{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);
|
||||||
|
loop {
|
||||||
|
assert!(parent.is_valid());
|
||||||
|
assert!(visited.contains(&parent));
|
||||||
|
domset.insert(parent);
|
||||||
|
let next = idom[parent.index()];
|
||||||
|
if next == parent {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
});
|
||||||
11
fuzz/fuzz_targets/ion.rs
Normal file
11
fuzz/fuzz_targets/ion.rs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#![no_main]
|
||||||
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
|
use regalloc2::fuzzing::func::Func;
|
||||||
|
|
||||||
|
fuzz_target!(|func: Func| {
|
||||||
|
let _ = env_logger::try_init();
|
||||||
|
log::debug!("func:\n{:?}", func);
|
||||||
|
let env = regalloc2::fuzzing::func::machine_env();
|
||||||
|
let _out = regalloc2::ion::run(&func, &env).expect("regalloc did not succeed");
|
||||||
|
});
|
||||||
39
fuzz/fuzz_targets/ion_checker.rs
Normal file
39
fuzz/fuzz_targets/ion_checker.rs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#![no_main]
|
||||||
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
use libfuzzer_sys::arbitrary::{Arbitrary, Unstructured, Result};
|
||||||
|
|
||||||
|
use regalloc2::fuzzing::func::{Func, Options};
|
||||||
|
use regalloc2::checker::Checker;
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
})?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fuzz_target!(|testcase: TestCase| {
|
||||||
|
let func = testcase.func;
|
||||||
|
let _ = env_logger::try_init();
|
||||||
|
log::debug!("func:\n{:?}", func);
|
||||||
|
let env = regalloc2::fuzzing::func::machine_env();
|
||||||
|
let out = regalloc2::ion::run(&func, &env).expect("regalloc did not succeed");
|
||||||
|
|
||||||
|
let mut checker = Checker::new(&func);
|
||||||
|
checker.prepare(&out);
|
||||||
|
checker.run().expect("checker failed");
|
||||||
|
});
|
||||||
76
fuzz/fuzz_targets/moves.rs
Normal file
76
fuzz/fuzz_targets/moves.rs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
#![no_main]
|
||||||
|
use libfuzzer_sys::arbitrary::{Arbitrary, Result, Unstructured};
|
||||||
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
|
use regalloc2::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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
35
fuzz/fuzz_targets/ssagen.rs
Normal file
35
fuzz/fuzz_targets/ssagen.rs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#![no_main]
|
||||||
|
use libfuzzer_sys::arbitrary::{Arbitrary, Result, Unstructured};
|
||||||
|
use libfuzzer_sys::fuzz_target;
|
||||||
|
|
||||||
|
use regalloc2::cfg::CFGInfo;
|
||||||
|
use regalloc2::fuzzing::func::{Func, Options};
|
||||||
|
use regalloc2::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,
|
||||||
|
},
|
||||||
|
)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fuzz_target!(|t: TestCase| {
|
||||||
|
let cfginfo = CFGInfo::new(&t.f);
|
||||||
|
validate_ssa(&t.f, &cfginfo).expect("invalid SSA");
|
||||||
|
});
|
||||||
45
src/bin/test.rs
Normal file
45
src/bin/test.rs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
139
src/bitvec.rs
Normal file
139
src/bitvec.rs
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
//! Bit vectors.
|
||||||
|
|
||||||
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
|
||||||
|
/// A conceptually infinite-length bitvector that allows bitwise operations and
|
||||||
|
/// iteration over set bits efficiently.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct BitVec {
|
||||||
|
bits: SmallVec<[u64; 2]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const BITS_PER_WORD: usize = 64;
|
||||||
|
|
||||||
|
impl BitVec {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { bits: smallvec![] }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_capacity(len: usize) -> Self {
|
||||||
|
let words = (len + BITS_PER_WORD - 1) / BITS_PER_WORD;
|
||||||
|
Self {
|
||||||
|
bits: SmallVec::with_capacity(words),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(never)]
|
||||||
|
fn ensure_idx(&mut self, word: usize) {
|
||||||
|
let mut target_len = std::cmp::max(2, self.bits.len());
|
||||||
|
while word >= target_len {
|
||||||
|
target_len *= 2;
|
||||||
|
}
|
||||||
|
self.bits.resize(target_len, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn set(&mut self, idx: usize, val: bool) {
|
||||||
|
let word = idx / BITS_PER_WORD;
|
||||||
|
let bit = idx % BITS_PER_WORD;
|
||||||
|
if val {
|
||||||
|
if word >= self.bits.len() {
|
||||||
|
self.ensure_idx(word);
|
||||||
|
}
|
||||||
|
self.bits[word] |= 1 << bit;
|
||||||
|
} else {
|
||||||
|
if word < self.bits.len() {
|
||||||
|
self.bits[word] &= !(1 << bit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn get(&mut self, idx: usize) -> bool {
|
||||||
|
let word = idx / BITS_PER_WORD;
|
||||||
|
let bit = idx % BITS_PER_WORD;
|
||||||
|
if word >= self.bits.len() {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
(self.bits[word] & (1 << bit)) != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn or(&mut self, other: &Self) {
|
||||||
|
if other.bits.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let last_idx = other.bits.len() - 1;
|
||||||
|
self.ensure_idx(last_idx);
|
||||||
|
|
||||||
|
for (self_word, other_word) in self.bits.iter_mut().zip(other.bits.iter()) {
|
||||||
|
*self_word |= *other_word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn and(&mut self, other: &Self) {
|
||||||
|
if other.bits.len() < self.bits.len() {
|
||||||
|
self.bits.truncate(other.bits.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (self_word, other_word) in self.bits.iter_mut().zip(other.bits.iter()) {
|
||||||
|
*self_word &= *other_word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter<'a>(&'a self) -> SetBitsIter<'a> {
|
||||||
|
let cur_word = if self.bits.len() > 0 { self.bits[0] } else { 0 };
|
||||||
|
SetBitsIter {
|
||||||
|
words: &self.bits[..],
|
||||||
|
word_idx: 0,
|
||||||
|
cur_word,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SetBitsIter<'a> {
|
||||||
|
words: &'a [u64],
|
||||||
|
word_idx: usize,
|
||||||
|
cur_word: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for SetBitsIter<'a> {
|
||||||
|
type Item = usize;
|
||||||
|
fn next(&mut self) -> Option<usize> {
|
||||||
|
while self.cur_word == 0 {
|
||||||
|
if self.word_idx + 1 >= self.words.len() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.word_idx += 1;
|
||||||
|
self.cur_word = self.words[self.word_idx];
|
||||||
|
}
|
||||||
|
let bitidx = self.cur_word.trailing_zeros();
|
||||||
|
self.cur_word &= !(1 << bitidx);
|
||||||
|
Some(self.word_idx * BITS_PER_WORD + bitidx as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::BitVec;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_set_bits_iter() {
|
||||||
|
let mut vec = BitVec::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);
|
||||||
|
}
|
||||||
|
}
|
||||||
110
src/cfg.rs
Normal file
110
src/cfg.rs
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
//! Lightweight CFG analyses.
|
||||||
|
|
||||||
|
use crate::{domtree, postorder, Block, Function, Inst, OperandKind, ProgPoint};
|
||||||
|
|
||||||
|
#[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 its position in its successor's preds,
|
||||||
|
/// if it has a single successor?
|
||||||
|
///
|
||||||
|
/// (Because we require split critical edges, we always either have a single
|
||||||
|
/// successor (which itself may have multiple preds), or we have multiple
|
||||||
|
/// successors but each successor itself has only one pred; so we can store
|
||||||
|
/// just one value per block and always know any block's position in its
|
||||||
|
/// successors' preds lists.)
|
||||||
|
pub pred_pos: Vec<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CFGInfo {
|
||||||
|
pub fn new<F: Function>(f: &F) -> CFGInfo {
|
||||||
|
let postorder =
|
||||||
|
postorder::calculate(f.blocks(), f.entry_block(), |block| f.block_succs(block));
|
||||||
|
let domtree = domtree::calculate(
|
||||||
|
f.blocks(),
|
||||||
|
|block| f.block_preds(block),
|
||||||
|
&postorder[..],
|
||||||
|
f.entry_block(),
|
||||||
|
);
|
||||||
|
let mut insn_block = vec![Block::invalid(); f.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.blocks()];
|
||||||
|
let mut block_exit = vec![ProgPoint::before(Inst::invalid()); f.blocks()];
|
||||||
|
let mut pred_pos = vec![0; f.blocks()];
|
||||||
|
|
||||||
|
for block in 0..f.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());
|
||||||
|
|
||||||
|
if f.block_preds(block).len() > 1 {
|
||||||
|
for (i, &pred) in f.block_preds(block).iter().enumerate() {
|
||||||
|
// Assert critical edge condition.
|
||||||
|
assert_eq!(
|
||||||
|
f.block_succs(pred).len(),
|
||||||
|
1,
|
||||||
|
"Edge {} -> {} is critical",
|
||||||
|
pred.index(),
|
||||||
|
block.index(),
|
||||||
|
);
|
||||||
|
pred_pos[pred.index()] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CFGInfo {
|
||||||
|
postorder,
|
||||||
|
domtree,
|
||||||
|
insn_block,
|
||||||
|
vreg_def_inst,
|
||||||
|
vreg_def_blockparam,
|
||||||
|
block_entry,
|
||||||
|
block_exit,
|
||||||
|
pred_pos,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dominates(&self, a: Block, b: Block) -> bool {
|
||||||
|
domtree::dominates(&self.domtree[..], a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the position of this block in its successor's predecessor list.
|
||||||
|
///
|
||||||
|
/// Because the CFG must have split critical edges, we actually do not need
|
||||||
|
/// to know *which* successor: if there is more than one, then each
|
||||||
|
/// successor has only one predecessor (that's this block), so the answer is
|
||||||
|
/// `0` no matter which successor we are considering.
|
||||||
|
pub fn pred_position(&self, block: Block) -> usize {
|
||||||
|
self.pred_pos[block.index()]
|
||||||
|
}
|
||||||
|
}
|
||||||
615
src/checker.rs
Normal file
615
src/checker.rs
Normal file
@@ -0,0 +1,615 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
//!
|
||||||
|
//! Operand policies (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:
|
||||||
|
//!
|
||||||
|
//! - `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_j, 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, OperandKind,
|
||||||
|
OperandPolicy, OperandPos, Output, ProgPoint, VReg,
|
||||||
|
};
|
||||||
|
|
||||||
|
use std::collections::{HashMap, VecDeque};
|
||||||
|
use std::default::Default;
|
||||||
|
use std::hash::Hash;
|
||||||
|
use std::result::Result;
|
||||||
|
|
||||||
|
use log::debug;
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
},
|
||||||
|
PolicyViolated {
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 => {
|
||||||
|
CheckerValue::Reg(r1, ref1 || ref2)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
log::debug!("{:?} 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, _) => write!(f, "{}", 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_policy(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.policy(), OperandPolicy::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 OperandPolicy.
|
||||||
|
for (op, alloc) in operands.iter().zip(allocs.iter()) {
|
||||||
|
let is_here = match (op.pos(), pos) {
|
||||||
|
(OperandPos::Before, InstPosition::Before)
|
||||||
|
| (OperandPos::Both, InstPosition::Before) => true,
|
||||||
|
(OperandPos::After, InstPosition::After)
|
||||||
|
| (OperandPos::Both, 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());
|
||||||
|
debug!(
|
||||||
|
"checker: checkinst {:?}: op {:?}, alloc {:?}, checker value {:?}",
|
||||||
|
checkinst, op, alloc, val
|
||||||
|
);
|
||||||
|
self.check_val(inst, *op, *alloc, val, allocs)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update according to instruction.
|
||||||
|
fn update(&mut self, checkinst: &CheckerInst) {
|
||||||
|
match checkinst {
|
||||||
|
&CheckerInst::Move { into, from } => {
|
||||||
|
let val = self
|
||||||
|
.allocations
|
||||||
|
.get(&from)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or(Default::default());
|
||||||
|
debug!(
|
||||||
|
"checker: checkinst {:?} updating: move {:?} -> {:?} val {:?}",
|
||||||
|
checkinst, from, into, val
|
||||||
|
);
|
||||||
|
self.allocations.insert(into, val);
|
||||||
|
}
|
||||||
|
&CheckerInst::Op {
|
||||||
|
ref operands,
|
||||||
|
ref allocs,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
for (op, alloc) in operands.iter().zip(allocs.iter()) {
|
||||||
|
if op.kind() != OperandKind::Def {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
self.allocations
|
||||||
|
.insert(*alloc, CheckerValue::Reg(op.vreg(), false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&CheckerInst::BlockParams {
|
||||||
|
ref vregs,
|
||||||
|
ref allocs,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
for (vreg, alloc) in vregs.iter().zip(allocs.iter()) {
|
||||||
|
self.allocations
|
||||||
|
.insert(*alloc, CheckerValue::Reg(*vreg, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_policy(
|
||||||
|
&self,
|
||||||
|
inst: Inst,
|
||||||
|
op: Operand,
|
||||||
|
alloc: Allocation,
|
||||||
|
allocs: &[Allocation],
|
||||||
|
) -> Result<(), CheckerError> {
|
||||||
|
match op.policy() {
|
||||||
|
OperandPolicy::Any => {}
|
||||||
|
OperandPolicy::Reg => {
|
||||||
|
if alloc.kind() != AllocationKind::Reg {
|
||||||
|
return Err(CheckerError::AllocationIsNotReg { inst, op, alloc });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OperandPolicy::FixedReg(preg) => {
|
||||||
|
if alloc != Allocation::reg(preg) {
|
||||||
|
return Err(CheckerError::AllocationIsNotFixedReg { inst, op, alloc });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OperandPolicy::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>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// The top of a block with blockparams. We define the given vregs
|
||||||
|
/// into the given allocations.
|
||||||
|
BlockParams {
|
||||||
|
block: Block,
|
||||||
|
vregs: Vec<VReg>,
|
||||||
|
allocs: Vec<Allocation>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Checker<'a, F: Function> {
|
||||||
|
f: &'a F,
|
||||||
|
bb_in: HashMap<Block, CheckerState>,
|
||||||
|
bb_insts: HashMap<Block, Vec<CheckerInst>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
for block in 0..f.blocks() {
|
||||||
|
let block = Block::new(block);
|
||||||
|
bb_in.insert(block, Default::default());
|
||||||
|
bb_insts.insert(block, vec![]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Checker { f, bb_in, bb_insts }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the list of checker instructions based on the given func
|
||||||
|
/// and allocation results.
|
||||||
|
pub fn prepare(&mut self, out: &Output) {
|
||||||
|
debug!("checker: out = {:?}", out);
|
||||||
|
// For each original instruction, create an `Op`.
|
||||||
|
let mut last_inst = None;
|
||||||
|
let mut insert_idx = 0;
|
||||||
|
for block in 0..self.f.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));
|
||||||
|
|
||||||
|
// Instruction itself.
|
||||||
|
let operands: Vec<_> = self.f.inst_operands(inst).iter().cloned().collect();
|
||||||
|
let allocs: Vec<_> = out.inst_allocs(inst).iter().cloned().collect();
|
||||||
|
let checkinst = CheckerInst::Op {
|
||||||
|
inst,
|
||||||
|
operands,
|
||||||
|
allocs,
|
||||||
|
};
|
||||||
|
debug!("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;
|
||||||
|
}
|
||||||
|
debug!("checker: adding edit {:?} at pos {:?}", edit, pos);
|
||||||
|
match edit {
|
||||||
|
&Edit::Move { from, to, .. } => {
|
||||||
|
self.bb_insts
|
||||||
|
.get_mut(&block)
|
||||||
|
.unwrap()
|
||||||
|
.push(CheckerInst::Move { into: to, from });
|
||||||
|
}
|
||||||
|
&Edit::BlockParams {
|
||||||
|
ref vregs,
|
||||||
|
ref allocs,
|
||||||
|
} => {
|
||||||
|
let inst = CheckerInst::BlockParams {
|
||||||
|
block,
|
||||||
|
vregs: vregs.clone(),
|
||||||
|
allocs: allocs.clone(),
|
||||||
|
};
|
||||||
|
self.bb_insts.get_mut(&block).unwrap().push(inst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Perform the dataflow analysis to compute checker state at each BB entry.
|
||||||
|
fn analyze(&mut self) {
|
||||||
|
let mut queue = VecDeque::new();
|
||||||
|
queue.push_back(self.f.entry_block());
|
||||||
|
|
||||||
|
while !queue.is_empty() {
|
||||||
|
let block = queue.pop_front().unwrap();
|
||||||
|
let mut state = self.bb_in.get(&block).cloned().unwrap();
|
||||||
|
debug!("analyze: block {} has state {:?}", block.index(), state);
|
||||||
|
for inst in self.bb_insts.get(&block).unwrap() {
|
||||||
|
state.update(inst);
|
||||||
|
debug!("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 {
|
||||||
|
debug!(
|
||||||
|
"analyze: block {} state changed from {:?} to {:?}; pushing onto queue",
|
||||||
|
succ.index(),
|
||||||
|
cur_succ_in,
|
||||||
|
new_state
|
||||||
|
);
|
||||||
|
self.bb_in.insert(succ, new_state);
|
||||||
|
queue.push_back(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) {
|
||||||
|
debug!("Checker error: {:?}", e);
|
||||||
|
errors.push(e);
|
||||||
|
}
|
||||||
|
state.update(inst);
|
||||||
|
if let Err(e) = state.check(InstPosition::After, inst) {
|
||||||
|
debug!("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();
|
||||||
|
|
||||||
|
debug!("=== CHECKER RESULT ===");
|
||||||
|
fn print_state(state: &CheckerState) {
|
||||||
|
let mut s = vec![];
|
||||||
|
for (alloc, state) in &state.allocations {
|
||||||
|
s.push(format!("{} := {}", alloc, state));
|
||||||
|
}
|
||||||
|
debug!(" {{ {} }}", s.join(", "))
|
||||||
|
}
|
||||||
|
for bb in 0..self.f.blocks() {
|
||||||
|
let bb = Block::new(bb);
|
||||||
|
debug!("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,
|
||||||
|
} => {
|
||||||
|
debug!(" inst{}: {:?} ({:?})", inst.index(), operands, allocs);
|
||||||
|
}
|
||||||
|
&CheckerInst::Move { from, into } => {
|
||||||
|
debug!(" {} -> {}", from, into);
|
||||||
|
}
|
||||||
|
&CheckerInst::BlockParams {
|
||||||
|
ref vregs,
|
||||||
|
ref allocs,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let mut args = vec![];
|
||||||
|
for (vreg, alloc) in vregs.iter().zip(allocs.iter()) {
|
||||||
|
args.push(format!("{}:{}", vreg, alloc));
|
||||||
|
}
|
||||||
|
debug!(" blockparams: {}", args.join(", "));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.update(inst);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
idom
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dominates(idom: &[Block], a: Block, mut b: Block) -> bool {
|
||||||
|
loop {
|
||||||
|
if a == b {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if b.is_invalid() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let parent = idom[b.index()];
|
||||||
|
if b == parent {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
b = idom[b.index()];
|
||||||
|
}
|
||||||
|
}
|
||||||
542
src/fuzzing/func.rs
Normal file
542
src/fuzzing/func.rs
Normal file
@@ -0,0 +1,542 @@
|
|||||||
|
use crate::{
|
||||||
|
domtree, postorder, Allocation, Block, Function, Inst, InstRange, MachineEnv, Operand,
|
||||||
|
OperandKind, OperandPolicy, 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>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn ret() -> InstData {
|
||||||
|
InstData {
|
||||||
|
op: InstOpcode::Ret,
|
||||||
|
operands: vec![],
|
||||||
|
clobbers: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Function for Func {
|
||||||
|
fn insts(&self) -> usize {
|
||||||
|
self.insts.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn 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 is_safepoint(&self, _: Inst) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_move(&self, _: Inst) -> Option<(VReg, VReg)> {
|
||||||
|
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, _: VReg) -> 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,
|
||||||
|
},
|
||||||
|
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 OperandPolicy {
|
||||||
|
fn arbitrary(u: &mut Unstructured) -> ArbitraryResult<Self> {
|
||||||
|
Ok(*u.choose(&[OperandPolicy::Any, OperandPolicy::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()] == block {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
block = idom[block.index()];
|
||||||
|
assert!(block.is_valid());
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
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_policy = OperandPolicy::arbitrary(u)?;
|
||||||
|
let def_pos = if bool::arbitrary(u)? {
|
||||||
|
OperandPos::Before
|
||||||
|
} else {
|
||||||
|
OperandPos::After
|
||||||
|
};
|
||||||
|
let mut operands = vec![Operand::new(vreg, def_policy, 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_policy = OperandPolicy::arbitrary(u)?;
|
||||||
|
operands.push(Operand::new(
|
||||||
|
vreg,
|
||||||
|
use_policy,
|
||||||
|
OperandKind::Use,
|
||||||
|
OperandPos::Before,
|
||||||
|
));
|
||||||
|
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(),
|
||||||
|
OperandPolicy::Reuse(reused),
|
||||||
|
op.kind(),
|
||||||
|
OperandPos::After,
|
||||||
|
);
|
||||||
|
} else if opts.fixed_regs && bool::arbitrary(u)? {
|
||||||
|
// Pick an operand and make it a fixed reg.
|
||||||
|
let fixed_reg = PReg::new(u.int_in_range(0..=30)?, RegClass::Int);
|
||||||
|
let i = u.int_in_range(0..=(operands.len() - 1))?;
|
||||||
|
let op = operands[i];
|
||||||
|
operands[i] = Operand::new(
|
||||||
|
op.vreg(),
|
||||||
|
OperandPolicy::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 op = *u.choose(&[InstOpcode::Op, InstOpcode::Call])?;
|
||||||
|
builder.add_inst(
|
||||||
|
Block::new(block),
|
||||||
|
InstData {
|
||||||
|
op,
|
||||||
|
operands,
|
||||||
|
clobbers,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
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 (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() {
|
||||||
|
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 regs_by_class: Vec<Vec<PReg>> = vec![regs.clone(), vec![]];
|
||||||
|
let scratch_by_class: Vec<PReg> =
|
||||||
|
vec![PReg::new(31, RegClass::Int), PReg::new(0, RegClass::Float)];
|
||||||
|
MachineEnv {
|
||||||
|
regs,
|
||||||
|
regs_by_class,
|
||||||
|
scratch_by_class,
|
||||||
|
}
|
||||||
|
}
|
||||||
3
src/fuzzing/mod.rs
Normal file
3
src/fuzzing/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
//! Utilities for fuzzing.
|
||||||
|
|
||||||
|
pub mod func;
|
||||||
176
src/index.rs
Normal file
176
src/index.rs
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
#[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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
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.
|
||||||
3763
src/ion/mod.rs
Normal file
3763
src/ion/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
780
src/lib.rs
Normal file
780
src/lib.rs
Normal file
@@ -0,0 +1,780 @@
|
|||||||
|
/*
|
||||||
|
* The fellowing license applies to this file, which derives many
|
||||||
|
* details (register and constraint definitions, for example) from the
|
||||||
|
* files `BacktrackingAllocator.h`, `BacktrackingAllocator.cpp`,
|
||||||
|
* `LIR.h`, and possibly definitions in other related files in
|
||||||
|
* `js/src/jit/`:
|
||||||
|
*
|
||||||
|
* 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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
pub mod bitvec;
|
||||||
|
pub mod cfg;
|
||||||
|
pub mod domtree;
|
||||||
|
pub mod ion;
|
||||||
|
pub mod moves;
|
||||||
|
pub mod postorder;
|
||||||
|
pub mod ssa;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
pub mod index;
|
||||||
|
pub use index::{Block, Inst, InstRange, InstRangeIter};
|
||||||
|
|
||||||
|
pub mod checker;
|
||||||
|
pub mod fuzzing;
|
||||||
|
|
||||||
|
/// Register classes.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub enum RegClass {
|
||||||
|
Int = 0,
|
||||||
|
Float = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A physical register. Contains a physical register number and a class.
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct PReg(u8, RegClass);
|
||||||
|
|
||||||
|
impl PReg {
|
||||||
|
pub const MAX_BITS: usize = 5;
|
||||||
|
pub const MAX: usize = (1 << Self::MAX_BITS) - 1;
|
||||||
|
|
||||||
|
/// Create a new PReg. The `hw_enc` range is 6 bits.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn new(hw_enc: usize, class: RegClass) -> Self {
|
||||||
|
assert!(hw_enc <= Self::MAX);
|
||||||
|
PReg(hw_enc as u8, class)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The physical register number, as encoded by the ISA for the particular register class.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn hw_enc(self) -> usize {
|
||||||
|
self.0 as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The register class.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn class(self) -> RegClass {
|
||||||
|
self.1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an index into the (not necessarily contiguous) index space of
|
||||||
|
/// all physical registers. Allows one to maintain an array of data for
|
||||||
|
/// all PRegs and index it efficiently.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn index(self) -> usize {
|
||||||
|
((self.1 as u8 as usize) << 6) | (self.0 as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn from_index(index: usize) -> Self {
|
||||||
|
let class = (index >> 6) & 1;
|
||||||
|
let class = match class {
|
||||||
|
0 => RegClass::Int,
|
||||||
|
1 => RegClass::Float,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
let index = index & Self::MAX;
|
||||||
|
PReg::new(index, class)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn invalid() -> Self {
|
||||||
|
PReg::new(Self::MAX, RegClass::Int)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for PReg {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"PReg(hw = {}, class = {:?}, index = {})",
|
||||||
|
self.hw_enc(),
|
||||||
|
self.class(),
|
||||||
|
self.index()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for PReg {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
let class = match self.class() {
|
||||||
|
RegClass::Int => "i",
|
||||||
|
RegClass::Float => "f",
|
||||||
|
};
|
||||||
|
write!(f, "p{}{}", self.hw_enc(), class)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A virtual register. Contains a virtual register number and a class.
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct VReg(u32);
|
||||||
|
|
||||||
|
impl VReg {
|
||||||
|
pub const MAX_BITS: usize = 20;
|
||||||
|
pub const MAX: usize = (1 << Self::MAX_BITS) - 1;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn new(virt_reg: usize, class: RegClass) -> Self {
|
||||||
|
assert!(virt_reg <= Self::MAX);
|
||||||
|
VReg(((virt_reg as u32) << 1) | (class as u8 as u32))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn vreg(self) -> usize {
|
||||||
|
(self.0 >> 1) as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn class(self) -> RegClass {
|
||||||
|
match self.0 & 1 {
|
||||||
|
0 => RegClass::Int,
|
||||||
|
1 => RegClass::Float,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn invalid() -> Self {
|
||||||
|
VReg::new(Self::MAX, RegClass::Int)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for VReg {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"VReg(vreg = {}, class = {:?})",
|
||||||
|
self.vreg(),
|
||||||
|
self.class()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for VReg {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(f, "v{}", self.vreg())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct SpillSlot(u32);
|
||||||
|
|
||||||
|
impl SpillSlot {
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn new(slot: usize, class: RegClass) -> Self {
|
||||||
|
assert!(slot < (1 << 24));
|
||||||
|
SpillSlot((slot as u32) | (class as u8 as u32) << 24)
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn index(self) -> usize {
|
||||||
|
(self.0 & 0x00ffffff) as usize
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn class(self) -> RegClass {
|
||||||
|
match (self.0 >> 24) as u8 {
|
||||||
|
0 => RegClass::Int,
|
||||||
|
1 => RegClass::Float,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn plus(self, offset: usize) -> Self {
|
||||||
|
SpillSlot::new(self.index() + offset, self.class())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for SpillSlot {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(f, "stack{}", self.index())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An `Operand` encodes everything about a mention of a register in
|
||||||
|
/// an instruction: virtual register number, and any constraint/policy
|
||||||
|
/// that applies to the register at this program point.
|
||||||
|
///
|
||||||
|
/// An Operand may be a use or def (this corresponds to `LUse` and
|
||||||
|
/// `LAllocation` in Ion).
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct Operand {
|
||||||
|
/// Bit-pack into 31 bits. This allows a `Reg` to encode an
|
||||||
|
/// `Operand` or an `Allocation` in 32 bits.
|
||||||
|
///
|
||||||
|
/// op-or-alloc:1 pos:2 kind:1 policy:2 class:1 preg:5 vreg:20
|
||||||
|
bits: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Operand {
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn new(vreg: VReg, policy: OperandPolicy, kind: OperandKind, pos: OperandPos) -> Self {
|
||||||
|
let (preg_field, policy_field): (u32, u32) = match policy {
|
||||||
|
OperandPolicy::Any => (0, 0),
|
||||||
|
OperandPolicy::Reg => (0, 1),
|
||||||
|
OperandPolicy::FixedReg(preg) => {
|
||||||
|
assert_eq!(preg.class(), vreg.class());
|
||||||
|
(preg.hw_enc() as u32, 2)
|
||||||
|
}
|
||||||
|
OperandPolicy::Reuse(which) => {
|
||||||
|
assert!(which <= PReg::MAX);
|
||||||
|
(which as u32, 3)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let class_field = vreg.class() as u8 as u32;
|
||||||
|
let pos_field = pos as u8 as u32;
|
||||||
|
let kind_field = kind as u8 as u32;
|
||||||
|
Operand {
|
||||||
|
bits: vreg.vreg() as u32
|
||||||
|
| (preg_field << 20)
|
||||||
|
| (class_field << 25)
|
||||||
|
| (policy_field << 26)
|
||||||
|
| (kind_field << 28)
|
||||||
|
| (pos_field << 29),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn reg_use(vreg: VReg) -> Self {
|
||||||
|
Operand::new(
|
||||||
|
vreg,
|
||||||
|
OperandPolicy::Reg,
|
||||||
|
OperandKind::Use,
|
||||||
|
OperandPos::Before,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn reg_use_at_end(vreg: VReg) -> Self {
|
||||||
|
Operand::new(vreg, OperandPolicy::Reg, OperandKind::Use, OperandPos::Both)
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn reg_def(vreg: VReg) -> Self {
|
||||||
|
Operand::new(
|
||||||
|
vreg,
|
||||||
|
OperandPolicy::Reg,
|
||||||
|
OperandKind::Def,
|
||||||
|
OperandPos::After,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn reg_def_at_start(vreg: VReg) -> Self {
|
||||||
|
Operand::new(vreg, OperandPolicy::Reg, OperandKind::Def, OperandPos::Both)
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn reg_temp(vreg: VReg) -> Self {
|
||||||
|
Operand::new(vreg, OperandPolicy::Reg, OperandKind::Def, OperandPos::Both)
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn reg_reuse_def(vreg: VReg, idx: usize) -> Self {
|
||||||
|
Operand::new(
|
||||||
|
vreg,
|
||||||
|
OperandPolicy::Reuse(idx),
|
||||||
|
OperandKind::Def,
|
||||||
|
OperandPos::Both,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn reg_fixed_use(vreg: VReg, preg: PReg) -> Self {
|
||||||
|
Operand::new(
|
||||||
|
vreg,
|
||||||
|
OperandPolicy::FixedReg(preg),
|
||||||
|
OperandKind::Use,
|
||||||
|
OperandPos::Before,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn reg_fixed_def(vreg: VReg, preg: PReg) -> Self {
|
||||||
|
Operand::new(
|
||||||
|
vreg,
|
||||||
|
OperandPolicy::FixedReg(preg),
|
||||||
|
OperandKind::Def,
|
||||||
|
OperandPos::After,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn vreg(self) -> VReg {
|
||||||
|
let vreg_idx = ((self.bits as usize) & VReg::MAX) as usize;
|
||||||
|
VReg::new(vreg_idx, self.class())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn class(self) -> RegClass {
|
||||||
|
let class_field = (self.bits >> 25) & 1;
|
||||||
|
match class_field {
|
||||||
|
0 => RegClass::Int,
|
||||||
|
1 => RegClass::Float,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn kind(self) -> OperandKind {
|
||||||
|
let kind_field = (self.bits >> 28) & 1;
|
||||||
|
match kind_field {
|
||||||
|
0 => OperandKind::Def,
|
||||||
|
1 => OperandKind::Use,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn pos(self) -> OperandPos {
|
||||||
|
let pos_field = (self.bits >> 29) & 3;
|
||||||
|
match pos_field {
|
||||||
|
0 => OperandPos::Before,
|
||||||
|
1 => OperandPos::After,
|
||||||
|
2 => OperandPos::Both,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn policy(self) -> OperandPolicy {
|
||||||
|
let policy_field = (self.bits >> 26) & 3;
|
||||||
|
let preg_field = ((self.bits >> 20) as usize) & PReg::MAX;
|
||||||
|
match policy_field {
|
||||||
|
0 => OperandPolicy::Any,
|
||||||
|
1 => OperandPolicy::Reg,
|
||||||
|
2 => OperandPolicy::FixedReg(PReg::new(preg_field, self.class())),
|
||||||
|
3 => OperandPolicy::Reuse(preg_field),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn bits(self) -> u32 {
|
||||||
|
self.bits
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn from_bits(bits: u32) -> Self {
|
||||||
|
Operand { bits }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for Operand {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"Operand(vreg = {:?}, class = {:?}, kind = {:?}, pos = {:?}, policy = {:?})",
|
||||||
|
self.vreg().vreg(),
|
||||||
|
self.class(),
|
||||||
|
self.kind(),
|
||||||
|
self.pos(),
|
||||||
|
self.policy()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Operand {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{:?}@{:?}: {} {}",
|
||||||
|
self.kind(),
|
||||||
|
self.pos(),
|
||||||
|
self.vreg(),
|
||||||
|
self.policy()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum OperandPolicy {
|
||||||
|
/// Any location is fine (register or stack slot).
|
||||||
|
Any,
|
||||||
|
/// Operand must be in a register. Register is read-only for Uses.
|
||||||
|
Reg,
|
||||||
|
/// Operand must be in a fixed register.
|
||||||
|
FixedReg(PReg),
|
||||||
|
/// On defs only: reuse a use's register. Which use is given by `preg` field.
|
||||||
|
Reuse(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for OperandPolicy {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Any => write!(f, "any"),
|
||||||
|
Self::Reg => write!(f, "reg"),
|
||||||
|
Self::FixedReg(preg) => write!(f, "fixed({})", preg),
|
||||||
|
Self::Reuse(idx) => write!(f, "reuse({})", idx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum OperandKind {
|
||||||
|
Def = 0,
|
||||||
|
Use = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum OperandPos {
|
||||||
|
Before = 0,
|
||||||
|
After = 1,
|
||||||
|
Both = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An Allocation represents the end result of regalloc for an
|
||||||
|
/// Operand.
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct Allocation {
|
||||||
|
/// Bit-pack in 31 bits:
|
||||||
|
///
|
||||||
|
/// op-or-alloc:1 kind:2 index:29
|
||||||
|
bits: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for Allocation {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"Allocation(kind = {:?}, index = {})",
|
||||||
|
self.kind(),
|
||||||
|
self.index()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Allocation {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
match self.kind() {
|
||||||
|
AllocationKind::None => write!(f, "none"),
|
||||||
|
AllocationKind::Reg => write!(f, "{}", self.as_reg().unwrap()),
|
||||||
|
AllocationKind::Stack => write!(f, "{}", self.as_stack().unwrap()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Allocation {
|
||||||
|
#[inline(always)]
|
||||||
|
pub(crate) fn new(kind: AllocationKind, index: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
bits: ((kind as u8 as u32) << 29) | (index as u32),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn none() -> Allocation {
|
||||||
|
Allocation::new(AllocationKind::None, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn reg(preg: PReg) -> Allocation {
|
||||||
|
Allocation::new(AllocationKind::Reg, preg.index())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn stack(slot: SpillSlot) -> Allocation {
|
||||||
|
Allocation::new(AllocationKind::Stack, slot.0 as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn kind(self) -> AllocationKind {
|
||||||
|
match (self.bits >> 29) & 3 {
|
||||||
|
0 => AllocationKind::None,
|
||||||
|
1 => AllocationKind::Reg,
|
||||||
|
2 => AllocationKind::Stack,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn index(self) -> usize {
|
||||||
|
(self.bits & ((1 << 29) - 1)) as usize
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn as_reg(self) -> Option<PReg> {
|
||||||
|
if self.kind() == AllocationKind::Reg {
|
||||||
|
Some(PReg::from_index(self.index()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn as_stack(self) -> Option<SpillSlot> {
|
||||||
|
if self.kind() == AllocationKind::Stack {
|
||||||
|
Some(SpillSlot(self.index() as u32))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn bits(self) -> u32 {
|
||||||
|
self.bits
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn from_bits(bits: u32) -> Self {
|
||||||
|
Self { bits }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum AllocationKind {
|
||||||
|
None = 0,
|
||||||
|
Reg = 1,
|
||||||
|
Stack = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Allocation {
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn class(self) -> RegClass {
|
||||||
|
match self.kind() {
|
||||||
|
AllocationKind::None => panic!("Allocation::None has no class"),
|
||||||
|
AllocationKind::Reg => self.as_reg().unwrap().class(),
|
||||||
|
AllocationKind::Stack => self.as_stack().unwrap().class(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A trait defined by the regalloc client to provide access to its
|
||||||
|
/// machine-instruction / CFG representation.
|
||||||
|
pub trait Function {
|
||||||
|
// -------------
|
||||||
|
// CFG traversal
|
||||||
|
// -------------
|
||||||
|
|
||||||
|
/// How many instructions are there?
|
||||||
|
fn insts(&self) -> usize;
|
||||||
|
|
||||||
|
/// How many blocks are there?
|
||||||
|
fn blocks(&self) -> usize;
|
||||||
|
|
||||||
|
/// Get the index of the entry block.
|
||||||
|
fn entry_block(&self) -> Block;
|
||||||
|
|
||||||
|
/// Provide the range of instruction indices contained in each block.
|
||||||
|
fn block_insns(&self, block: Block) -> InstRange;
|
||||||
|
|
||||||
|
/// Get CFG successors for a given block.
|
||||||
|
fn block_succs(&self, block: Block) -> &[Block];
|
||||||
|
|
||||||
|
/// Get the CFG predecessors for a given block.
|
||||||
|
fn block_preds(&self, block: Block) -> &[Block];
|
||||||
|
|
||||||
|
/// Get the block parameters for a given block.
|
||||||
|
fn block_params(&self, block: Block) -> &[VReg];
|
||||||
|
|
||||||
|
/// Determine whether an instruction is a call instruction. This is used
|
||||||
|
/// only for splitting heuristics.
|
||||||
|
fn is_call(&self, insn: Inst) -> bool;
|
||||||
|
|
||||||
|
/// Determine whether an instruction is a return instruction.
|
||||||
|
fn is_ret(&self, insn: Inst) -> bool;
|
||||||
|
|
||||||
|
/// Determine whether an instruction is the end-of-block
|
||||||
|
/// branch. If so, its operands *must* be the block parameters for
|
||||||
|
/// each of its block's `block_succs` successor blocks, in order.
|
||||||
|
fn is_branch(&self, insn: Inst) -> bool;
|
||||||
|
|
||||||
|
/// Determine whether an instruction is a safepoint and requires a stackmap.
|
||||||
|
fn is_safepoint(&self, insn: Inst) -> bool;
|
||||||
|
|
||||||
|
/// Determine whether an instruction is a move; if so, return the
|
||||||
|
/// vregs for (src, dst).
|
||||||
|
fn is_move(&self, insn: Inst) -> Option<(VReg, VReg)>;
|
||||||
|
|
||||||
|
// --------------------------
|
||||||
|
// Instruction register slots
|
||||||
|
// --------------------------
|
||||||
|
|
||||||
|
/// Get the Operands for an instruction.
|
||||||
|
fn inst_operands(&self, insn: Inst) -> &[Operand];
|
||||||
|
|
||||||
|
/// Get the clobbers for an instruction.
|
||||||
|
fn inst_clobbers(&self, insn: Inst) -> &[PReg];
|
||||||
|
|
||||||
|
/// Get the precise number of `VReg` in use in this function, to allow
|
||||||
|
/// preallocating data structures. This number *must* be a correct
|
||||||
|
/// lower-bound, otherwise invalid index failures may happen; it is of
|
||||||
|
/// course better if it is exact.
|
||||||
|
fn num_vregs(&self) -> usize;
|
||||||
|
|
||||||
|
// --------------
|
||||||
|
// Spills/reloads
|
||||||
|
// --------------
|
||||||
|
|
||||||
|
/// How many logical spill slots does the given regclass require? E.g., on
|
||||||
|
/// a 64-bit machine, spill slots may nominally be 64-bit words, but a
|
||||||
|
/// 128-bit vector value will require two slots. The regalloc will always
|
||||||
|
/// align on this size.
|
||||||
|
///
|
||||||
|
/// This passes the associated virtual register to the client as well,
|
||||||
|
/// because the way in which we spill a real register may depend on the
|
||||||
|
/// value that we are using it for. E.g., if a machine has V128 registers
|
||||||
|
/// but we also use them for F32 and F64 values, we may use a different
|
||||||
|
/// store-slot size and smaller-operand store/load instructions for an F64
|
||||||
|
/// than for a true V128.
|
||||||
|
fn spillslot_size(&self, regclass: RegClass, for_vreg: VReg) -> usize;
|
||||||
|
|
||||||
|
/// When providing a spillslot number for a multi-slot spillslot,
|
||||||
|
/// do we provide the first or the last? This is usually related
|
||||||
|
/// to which direction the stack grows and different clients may
|
||||||
|
/// have different preferences.
|
||||||
|
fn multi_spillslot_named_by_last_slot(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A position before or after an instruction.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum InstPosition {
|
||||||
|
Before = 0,
|
||||||
|
After = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A program point: a single point before or after a given instruction.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct ProgPoint {
|
||||||
|
pub inst: Inst,
|
||||||
|
pub pos: InstPosition,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProgPoint {
|
||||||
|
pub fn before(inst: Inst) -> Self {
|
||||||
|
Self {
|
||||||
|
inst,
|
||||||
|
pos: InstPosition::Before,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn after(inst: Inst) -> Self {
|
||||||
|
Self {
|
||||||
|
inst,
|
||||||
|
pos: InstPosition::After,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(self) -> ProgPoint {
|
||||||
|
match self.pos {
|
||||||
|
InstPosition::Before => ProgPoint {
|
||||||
|
inst: self.inst,
|
||||||
|
pos: InstPosition::After,
|
||||||
|
},
|
||||||
|
InstPosition::After => ProgPoint {
|
||||||
|
inst: self.inst.next(),
|
||||||
|
pos: InstPosition::Before,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prev(self) -> ProgPoint {
|
||||||
|
match self.pos {
|
||||||
|
InstPosition::Before => ProgPoint {
|
||||||
|
inst: self.inst.prev(),
|
||||||
|
pos: InstPosition::After,
|
||||||
|
},
|
||||||
|
InstPosition::After => ProgPoint {
|
||||||
|
inst: self.inst,
|
||||||
|
pos: InstPosition::Before,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_index(self) -> u32 {
|
||||||
|
debug_assert!(self.inst.index() <= ((1 << 31) - 1));
|
||||||
|
((self.inst.index() as u32) << 1) | (self.pos as u8 as u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_index(index: u32) -> Self {
|
||||||
|
let inst = Inst::new((index >> 1) as usize);
|
||||||
|
let pos = match index & 1 {
|
||||||
|
0 => InstPosition::Before,
|
||||||
|
1 => InstPosition::After,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
Self { inst, pos }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An instruction to insert into the program to perform some data movement.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Edit {
|
||||||
|
/// Move one allocation to another. Each allocation may be a
|
||||||
|
/// register or a stack slot (spillslot).
|
||||||
|
Move { from: Allocation, to: Allocation },
|
||||||
|
/// Define blockparams' locations. Note that this is not typically
|
||||||
|
/// turned into machine code, but can be useful metadata (e.g. for
|
||||||
|
/// the checker).
|
||||||
|
BlockParams {
|
||||||
|
vregs: Vec<VReg>,
|
||||||
|
allocs: Vec<Allocation>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A machine envrionment tells the register allocator which registers
|
||||||
|
/// are available to allocate and what register may be used as a
|
||||||
|
/// scratch register for each class, and some other miscellaneous info
|
||||||
|
/// as well.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct MachineEnv {
|
||||||
|
regs: Vec<PReg>,
|
||||||
|
regs_by_class: Vec<Vec<PReg>>,
|
||||||
|
scratch_by_class: Vec<PReg>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The output of the register allocator.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Output {
|
||||||
|
/// How many spillslots are needed in the frame?
|
||||||
|
pub num_spillslots: usize,
|
||||||
|
/// Edits (insertions or removals). Guaranteed to be sorted by
|
||||||
|
/// program point.
|
||||||
|
pub edits: Vec<(ProgPoint, Edit)>,
|
||||||
|
/// Allocations for each operand. Mapping from instruction to
|
||||||
|
/// allocations provided by `inst_alloc_offsets` below.
|
||||||
|
pub allocs: Vec<Allocation>,
|
||||||
|
/// Allocation offset in `allocs` for each instruction.
|
||||||
|
pub inst_alloc_offsets: Vec<u32>,
|
||||||
|
|
||||||
|
/// Internal stats from the allocator.
|
||||||
|
pub stats: ion::Stats,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Output {
|
||||||
|
pub fn inst_allocs(&self, inst: Inst) -> &[Allocation] {
|
||||||
|
let start = self.inst_alloc_offsets[inst.index()] as usize;
|
||||||
|
let end = if inst.index() + 1 == self.inst_alloc_offsets.len() {
|
||||||
|
self.allocs.len()
|
||||||
|
} else {
|
||||||
|
self.inst_alloc_offsets[inst.index() + 1] as usize
|
||||||
|
};
|
||||||
|
&self.allocs[start..end]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An error that prevents allocation.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum RegAllocError {
|
||||||
|
/// Invalid SSA for given vreg at given inst: multiple defs or
|
||||||
|
/// illegal use. `inst` may be `Inst::invalid()` if this concerns
|
||||||
|
/// a block param.
|
||||||
|
SSA(VReg, Inst),
|
||||||
|
/// Invalid basic block: does not end in branch/ret, or contains a
|
||||||
|
/// branch/ret in the middle.
|
||||||
|
BB(Block),
|
||||||
|
/// Invalid branch: operand count does not match sum of block
|
||||||
|
/// params of successor blocks.
|
||||||
|
Branch(Inst),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for RegAllocError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(f, "{:?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for RegAllocError {}
|
||||||
|
|
||||||
|
pub fn run<F: Function>(func: &F, env: &MachineEnv) -> Result<Output, RegAllocError> {
|
||||||
|
ion::run(func, env)
|
||||||
|
}
|
||||||
199
src/moves.rs
Normal file
199
src/moves.rs
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
use crate::Allocation;
|
||||||
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
|
||||||
|
pub type MoveVec = SmallVec<[(Allocation, Allocation); 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 {
|
||||||
|
parallel_moves: MoveVec,
|
||||||
|
scratch: Allocation,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParallelMoves {
|
||||||
|
pub fn new(scratch: Allocation) -> Self {
|
||||||
|
Self {
|
||||||
|
parallel_moves: smallvec![],
|
||||||
|
scratch,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(&mut self, from: Allocation, to: Allocation) {
|
||||||
|
self.parallel_moves.push((from, to));
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// 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*: 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) {
|
||||||
|
let mut last_dst = None;
|
||||||
|
for &(_, dst) in &self.parallel_moves {
|
||||||
|
if last_dst.is_some() {
|
||||||
|
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 = 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) = 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));
|
||||||
|
|
||||||
|
last_dst = Some(dst);
|
||||||
|
|
||||||
|
if move_idx == next {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(src) = scratch_src {
|
||||||
|
ret.push((src, self.scratch));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.reverse();
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
}
|
||||||
51
src/postorder.rs
Normal file
51
src/postorder.rs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
//! Fast postorder computation with no allocations (aside from result).
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
87
src/ssa.rs
Normal file
87
src/ssa.rs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
//! 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.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.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(())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user