Skip to content

Commit 317b212

Browse files
authored
Rollup merge of #54420 - nnethercote:PackedRWU-Vec, r=nikomatsakis
Compress `Liveness` data some more. Profiling shows that the `(reader, writer, used)` triples used by liveness analysis almost always have invalid `reader` and `writer` fields. We can take advantage of this knowledge to use a compressed representation for them, falling back to a secondary table for the uncommon cases. This change reduces instruction counts on numerous benchmarks, the best by 16%. It also reduces max-rss on numerous benchmarks, the best by 38%. The patch also renames these triples from `Users` to `RWU`, because it's confusing having a type whose name is plural and then used within vectors whose names are also plural. r? @nikomatsakis
2 parents 7c34cf7 + b2f25e3 commit 317b212

File tree

1 file changed

+149
-58
lines changed

1 file changed

+149
-58
lines changed

src/librustc/middle/liveness.rs

+149-58
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,10 @@
6464
//! methods. It effectively does a reverse walk of the AST; whenever we
6565
//! reach a loop node, we iterate until a fixed point is reached.
6666
//!
67-
//! ## The `users_*` fields
67+
//! ## The `RWU` struct
6868
//!
6969
//! At each live node `N`, we track three pieces of information for each
70-
//! variable `V` (these are in the `users_*` fields):
70+
//! variable `V` (these are encapsulated in the `RWU` struct):
7171
//!
7272
//! - `reader`: the `LiveNode` ID of some node which will read the value
7373
//! that `V` holds on entry to `N`. Formally: a node `M` such
@@ -536,6 +536,112 @@ fn visit_expr<'a, 'tcx>(ir: &mut IrMaps<'a, 'tcx>, expr: &'tcx Expr) {
536536
// Actually we compute just a bit more than just liveness, but we use
537537
// the same basic propagation framework in all cases.
538538

539+
#[derive(Clone, Copy)]
540+
struct RWU {
541+
reader: LiveNode,
542+
writer: LiveNode,
543+
used: bool
544+
}
545+
546+
/// Conceptually, this is like a `Vec<RWU>`. But the number of `RWU`s can get
547+
/// very large, so it uses a more compact representation that takes advantage
548+
/// of the fact that when the number of `RWU`s is large, most of them have an
549+
/// invalid reader and an invalid writer.
550+
struct RWUTable {
551+
/// Each entry in `packed_rwus` is either INV_INV_FALSE, INV_INV_TRUE, or
552+
/// an index into `unpacked_rwus`. In the common cases, this compacts the
553+
/// 65 bits of data into 32; in the uncommon cases, it expands the 65 bits
554+
/// in 96.
555+
///
556+
/// More compact representations are possible -- e.g. use only 2 bits per
557+
/// packed `RWU` and make the secondary table a HashMap that maps from
558+
/// indices to `RWU`s -- but this one strikes a good balance between size
559+
/// and speed.
560+
packed_rwus: Vec<u32>,
561+
unpacked_rwus: Vec<RWU>,
562+
}
563+
564+
// A constant representing `RWU { reader: invalid_node(); writer: invalid_node(); used: false }`.
565+
const INV_INV_FALSE: u32 = u32::MAX;
566+
567+
// A constant representing `RWU { reader: invalid_node(); writer: invalid_node(); used: true }`.
568+
const INV_INV_TRUE: u32 = u32::MAX - 1;
569+
570+
impl RWUTable {
571+
fn new(num_rwus: usize) -> RWUTable {
572+
Self {
573+
packed_rwus: vec![INV_INV_FALSE; num_rwus],
574+
unpacked_rwus: vec![],
575+
}
576+
}
577+
578+
fn get(&self, idx: usize) -> RWU {
579+
let packed_rwu = self.packed_rwus[idx];
580+
match packed_rwu {
581+
INV_INV_FALSE => RWU { reader: invalid_node(), writer: invalid_node(), used: false },
582+
INV_INV_TRUE => RWU { reader: invalid_node(), writer: invalid_node(), used: true },
583+
_ => self.unpacked_rwus[packed_rwu as usize],
584+
}
585+
}
586+
587+
fn get_reader(&self, idx: usize) -> LiveNode {
588+
let packed_rwu = self.packed_rwus[idx];
589+
match packed_rwu {
590+
INV_INV_FALSE | INV_INV_TRUE => invalid_node(),
591+
_ => self.unpacked_rwus[packed_rwu as usize].reader,
592+
}
593+
}
594+
595+
fn get_writer(&self, idx: usize) -> LiveNode {
596+
let packed_rwu = self.packed_rwus[idx];
597+
match packed_rwu {
598+
INV_INV_FALSE | INV_INV_TRUE => invalid_node(),
599+
_ => self.unpacked_rwus[packed_rwu as usize].writer,
600+
}
601+
}
602+
603+
fn get_used(&self, idx: usize) -> bool {
604+
let packed_rwu = self.packed_rwus[idx];
605+
match packed_rwu {
606+
INV_INV_FALSE => false,
607+
INV_INV_TRUE => true,
608+
_ => self.unpacked_rwus[packed_rwu as usize].used,
609+
}
610+
}
611+
612+
#[inline]
613+
fn copy_packed(&mut self, dst_idx: usize, src_idx: usize) {
614+
self.packed_rwus[dst_idx] = self.packed_rwus[src_idx];
615+
}
616+
617+
fn assign_unpacked(&mut self, idx: usize, rwu: RWU) {
618+
if rwu.reader == invalid_node() && rwu.writer == invalid_node() {
619+
// When we overwrite an indexing entry in `self.packed_rwus` with
620+
// `INV_INV_{TRUE,FALSE}` we don't remove the corresponding entry
621+
// from `self.unpacked_rwus`; it's not worth the effort, and we
622+
// can't have entries shifting around anyway.
623+
self.packed_rwus[idx] = if rwu.used {
624+
INV_INV_TRUE
625+
} else {
626+
INV_INV_FALSE
627+
}
628+
} else {
629+
// Add a new RWU to `unpacked_rwus` and make `packed_rwus[idx]`
630+
// point to it.
631+
self.packed_rwus[idx] = self.unpacked_rwus.len() as u32;
632+
self.unpacked_rwus.push(rwu);
633+
}
634+
}
635+
636+
fn assign_inv_inv(&mut self, idx: usize) {
637+
self.packed_rwus[idx] = if self.get_used(idx) {
638+
INV_INV_TRUE
639+
} else {
640+
INV_INV_FALSE
641+
};
642+
}
643+
}
644+
539645
#[derive(Copy, Clone)]
540646
struct Specials {
541647
exit_ln: LiveNode,
@@ -552,14 +658,7 @@ struct Liveness<'a, 'tcx: 'a> {
552658
tables: &'a ty::TypeckTables<'tcx>,
553659
s: Specials,
554660
successors: Vec<LiveNode>,
555-
556-
// We used to have a single `users: Vec<Users>` field here, where `Users`
557-
// had `reader`, `writer` and `used` fields. But the number of users can
558-
// get very large, and it's more compact to store the data in three
559-
// separate `Vec`s so that no space is wasted for padding.
560-
users_reader: Vec<LiveNode>,
561-
users_writer: Vec<LiveNode>,
562-
users_used: Vec<bool>,
661+
rwu_table: RWUTable,
563662

564663
// mappings from loop node ID to LiveNode
565664
// ("break" label should map to loop node ID,
@@ -584,16 +683,13 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
584683

585684
let num_live_nodes = ir.num_live_nodes;
586685
let num_vars = ir.num_vars;
587-
let num_users = num_live_nodes * num_vars;
588686

589687
Liveness {
590688
ir,
591689
tables,
592690
s: specials,
593691
successors: vec![invalid_node(); num_live_nodes],
594-
users_reader: vec![invalid_node(); num_users],
595-
users_writer: vec![invalid_node(); num_users],
596-
users_used: vec![false; num_users],
692+
rwu_table: RWUTable::new(num_live_nodes * num_vars),
597693
break_ln: NodeMap(),
598694
cont_ln: NodeMap(),
599695
}
@@ -657,16 +753,13 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
657753
ln.get() * self.ir.num_vars + var.get()
658754
}
659755

660-
fn live_on_entry(&self, ln: LiveNode, var: Variable)
661-
-> Option<LiveNodeKind> {
756+
fn live_on_entry(&self, ln: LiveNode, var: Variable) -> Option<LiveNodeKind> {
662757
assert!(ln.is_valid());
663-
let reader = self.users_reader[self.idx(ln, var)];
664-
if reader.is_valid() {Some(self.ir.lnk(reader))} else {None}
758+
let reader = self.rwu_table.get_reader(self.idx(ln, var));
759+
if reader.is_valid() { Some(self.ir.lnk(reader)) } else { None }
665760
}
666761

667-
/*
668-
Is this variable live on entry to any of its successor nodes?
669-
*/
762+
// Is this variable live on entry to any of its successor nodes?
670763
fn live_on_exit(&self, ln: LiveNode, var: Variable)
671764
-> Option<LiveNodeKind> {
672765
let successor = self.successors[ln.get()];
@@ -675,14 +768,14 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
675768

676769
fn used_on_entry(&self, ln: LiveNode, var: Variable) -> bool {
677770
assert!(ln.is_valid());
678-
self.users_used[self.idx(ln, var)]
771+
self.rwu_table.get_used(self.idx(ln, var))
679772
}
680773

681774
fn assigned_on_entry(&self, ln: LiveNode, var: Variable)
682775
-> Option<LiveNodeKind> {
683776
assert!(ln.is_valid());
684-
let writer = self.users_writer[self.idx(ln, var)];
685-
if writer.is_valid() {Some(self.ir.lnk(writer))} else {None}
777+
let writer = self.rwu_table.get_writer(self.idx(ln, var));
778+
if writer.is_valid() { Some(self.ir.lnk(writer)) } else { None }
686779
}
687780

688781
fn assigned_on_exit(&self, ln: LiveNode, var: Variable)
@@ -725,9 +818,9 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
725818
{
726819
let wr = &mut wr as &mut dyn Write;
727820
write!(wr, "[ln({:?}) of kind {:?} reads", ln.get(), self.ir.lnk(ln));
728-
self.write_vars(wr, ln, |idx| self.users_reader[idx]);
821+
self.write_vars(wr, ln, |idx| self.rwu_table.get_reader(idx));
729822
write!(wr, " writes");
730-
self.write_vars(wr, ln, |idx| self.users_writer[idx]);
823+
self.write_vars(wr, ln, |idx| self.rwu_table.get_writer(idx));
731824
write!(wr, " precedes {:?}]", self.successors[ln.get()]);
732825
}
733826
String::from_utf8(wr).unwrap()
@@ -736,26 +829,17 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
736829
fn init_empty(&mut self, ln: LiveNode, succ_ln: LiveNode) {
737830
self.successors[ln.get()] = succ_ln;
738831

739-
// It is not necessary to initialize the
740-
// values to empty because this is the value
741-
// they have when they are created, and the sets
742-
// only grow during iterations.
743-
//
744-
// self.indices(ln) { |idx|
745-
// self.users_reader[idx] = invalid_node();
746-
// self.users_writer[idx] = invalid_node();
747-
// self.users_used[idx] = false;
748-
// }
832+
// It is not necessary to initialize the RWUs here because they are all
833+
// set to INV_INV_FALSE when they are created, and the sets only grow
834+
// during iterations.
749835
}
750836

751837
fn init_from_succ(&mut self, ln: LiveNode, succ_ln: LiveNode) {
752838
// more efficient version of init_empty() / merge_from_succ()
753839
self.successors[ln.get()] = succ_ln;
754840

755841
self.indices2(ln, succ_ln, |this, idx, succ_idx| {
756-
this.users_reader[idx] = this.users_reader[succ_idx];
757-
this.users_writer[idx] = this.users_writer[succ_idx];
758-
this.users_used[idx] = this.users_used[succ_idx];
842+
this.rwu_table.copy_packed(idx, succ_idx);
759843
});
760844
debug!("init_from_succ(ln={}, succ={})",
761845
self.ln_str(ln), self.ln_str(succ_ln));
@@ -770,35 +854,39 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
770854

771855
let mut changed = false;
772856
self.indices2(ln, succ_ln, |this, idx, succ_idx| {
773-
changed |= copy_if_invalid(this.users_reader[succ_idx], &mut this.users_reader[idx]);
774-
changed |= copy_if_invalid(this.users_writer[succ_idx], &mut this.users_writer[idx]);
775-
if this.users_used[succ_idx] && !this.users_used[idx] {
776-
this.users_used[idx] = true;
857+
let mut rwu = this.rwu_table.get(idx);
858+
let succ_rwu = this.rwu_table.get(succ_idx);
859+
if succ_rwu.reader.is_valid() && !rwu.reader.is_valid() {
860+
rwu.reader = succ_rwu.reader;
861+
changed = true
862+
}
863+
864+
if succ_rwu.writer.is_valid() && !rwu.writer.is_valid() {
865+
rwu.writer = succ_rwu.writer;
866+
changed = true
867+
}
868+
869+
if succ_rwu.used && !rwu.used {
870+
rwu.used = true;
777871
changed = true;
778872
}
873+
874+
if changed {
875+
this.rwu_table.assign_unpacked(idx, rwu);
876+
}
779877
});
780878

781879
debug!("merge_from_succ(ln={:?}, succ={}, first_merge={}, changed={})",
782880
ln, self.ln_str(succ_ln), first_merge, changed);
783881
return changed;
784-
785-
fn copy_if_invalid(src: LiveNode, dst: &mut LiveNode) -> bool {
786-
if src.is_valid() && !dst.is_valid() {
787-
*dst = src;
788-
true
789-
} else {
790-
false
791-
}
792-
}
793882
}
794883

795884
// Indicates that a local variable was *defined*; we know that no
796885
// uses of the variable can precede the definition (resolve checks
797886
// this) so we just clear out all the data.
798887
fn define(&mut self, writer: LiveNode, var: Variable) {
799888
let idx = self.idx(writer, var);
800-
self.users_reader[idx] = invalid_node();
801-
self.users_writer[idx] = invalid_node();
889+
self.rwu_table.assign_inv_inv(idx);
802890

803891
debug!("{:?} defines {:?} (idx={}): {}", writer, var,
804892
idx, self.ln_str(writer));
@@ -810,21 +898,24 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
810898
ln, acc, var, self.ln_str(ln));
811899

812900
let idx = self.idx(ln, var);
901+
let mut rwu = self.rwu_table.get(idx);
813902

814903
if (acc & ACC_WRITE) != 0 {
815-
self.users_reader[idx] = invalid_node();
816-
self.users_writer[idx] = ln;
904+
rwu.reader = invalid_node();
905+
rwu.writer = ln;
817906
}
818907

819908
// Important: if we both read/write, must do read second
820909
// or else the write will override.
821910
if (acc & ACC_READ) != 0 {
822-
self.users_reader[idx] = ln;
911+
rwu.reader = ln;
823912
}
824913

825914
if (acc & ACC_USE) != 0 {
826-
self.users_used[idx] = true;
915+
rwu.used = true;
827916
}
917+
918+
self.rwu_table.assign_unpacked(idx, rwu);
828919
}
829920

830921
// _______________________________________________________________________

0 commit comments

Comments
 (0)