Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix overlapping references in BTree #58431

Merged
merged 2 commits into from
Feb 23, 2019
Merged

fix overlapping references in BTree #58431

merged 2 commits into from
Feb 23, 2019

Conversation

RalfJung
Copy link
Member

This fixes two kinds of overlapping references in BTree (both found by running the BTree test suite in Miri).

In into_slices_mut, we did k.into_key_slice_mut() followed by self.into_val_slice_mut() (where k is a copy of self). Calling into_val_slice_mut calls self.len(), which creates a shared reference to NodeHeader, which unfortunately (due to padding) overlaps with the mutable reference returned by into_key_slice_mut. Hence the key slice got (partially) invalidated. The fix is to avoid creating an &NodeHeader after the first slice got created.

In the iterators, we used to first create the references that will be returned, and then perform the walk on the tree. Walking the tree creates references (such as &mut InternalNode) that overlap with all of the keys and values stored in a pointer; in particular, they overlap with the references the iterator will later return. This is fixed by reordering the operations of walking the tree and obtaining the inner references.

The test suite still passes (and it passes in Miri now!), but there is a lot of code here that I do not understand...

@rust-highfive
Copy link
Collaborator

r? @Kimundi

(rust_highfive has picked a reviewer for you, use r? to override)

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Feb 13, 2019
@Mark-Simulacrum
Copy link
Member

Since the ordering here changed, it might be worth trying to run some benchmarks as well (just to make sure we're not obviously regressing). I'm not sure if we have any in-tree though.

@RalfJung
Copy link
Member Author

Without the patch:

test btree::map::find_rand_100                           ... bench:          20 ns/iter (+/- 4)
test btree::map::find_rand_10_000                        ... bench:          75 ns/iter (+/- 10)
test btree::map::find_seq_100                            ... bench:          20 ns/iter (+/- 9)
test btree::map::find_seq_10_000                         ... bench:          55 ns/iter (+/- 8)
test btree::map::insert_rand_100                         ... bench:          50 ns/iter (+/- 12)
test btree::map::insert_rand_10_000                      ... bench:          49 ns/iter (+/- 14)
test btree::map::insert_seq_100                          ... bench:          67 ns/iter (+/- 15)
test btree::map::insert_seq_10_000                       ... bench:         128 ns/iter (+/- 15)
test btree::map::iter_1000                               ... bench:       4,206 ns/iter (+/- 578)
test btree::map::iter_100000                             ... bench:     551,540 ns/iter (+/- 78,192)
test btree::map::iter_20                                 ... bench:          69 ns/iter (+/- 16)

With the patch:

running 11 tests
test btree::map::find_rand_100                           ... bench:          20 ns/iter (+/- 4)
test btree::map::find_rand_10_000                        ... bench:          78 ns/iter (+/- 31)
test btree::map::find_seq_100                            ... bench:          20 ns/iter (+/- 6)
test btree::map::find_seq_10_000                         ... bench:          55 ns/iter (+/- 7)
test btree::map::insert_rand_100                         ... bench:          50 ns/iter (+/- 11)
test btree::map::insert_rand_10_000                      ... bench:          50 ns/iter (+/- 17)
test btree::map::insert_seq_100                          ... bench:          63 ns/iter (+/- 16)
test btree::map::insert_seq_10_000                       ... bench:         131 ns/iter (+/- 12)
test btree::map::iter_1000                               ... bench:       4,132 ns/iter (+/- 448)
test btree::map::iter_100000                             ... bench:     555,513 ns/iter (+/- 86,294)
test btree::map::iter_20                                 ... bench:          70 ns/iter (+/- 21)

(I discarded a previous run because it reported a huge variance of >200,000ns)

Looks like no change to me.

@Mark-Simulacrum
Copy link
Member

@bors r+

Code changes look reasonable (and, well, don't actually change anything).

@bors
Copy link
Contributor

bors commented Feb 20, 2019

📌 Commit f0bef49 has been approved by Mark-Simulacrum

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Feb 20, 2019
Centril added a commit to Centril/rust that referenced this pull request Feb 22, 2019
fix overlapping references in BTree

This fixes two kinds of overlapping references in BTree (both found by running the BTree test suite in Miri).

In `into_slices_mut`, we did `k.into_key_slice_mut()` followed by `self.into_val_slice_mut()` (where `k` is a copy of `self`). Calling `into_val_slice_mut` calls `self.len()`, which creates a shared reference to `NodeHeader`, which unfortunately (due to padding) overlaps with the mutable reference returned by `into_key_slice_mut`.  Hence the key slice got (partially) invalidated.  The fix is to avoid creating an `&NodeHeader` after the first slice got created.

In the iterators, we used to first create the references that will be returned, and then perform the walk on the tree.  Walking the tree creates references (such as `&mut InternalNode`) that overlap with all of the keys and values stored in a pointer; in particular, they overlap with the references the iterator will later return. This is fixed by reordering the operations of walking the tree and obtaining the inner references.

The test suite still passes (and it passes in Miri now!), but there is a lot of code here that I do not understand...
bors added a commit that referenced this pull request Feb 22, 2019
Rollup of 17 pull requests

Successful merges:

 - #57656 (Deprecate the unstable Vec::resize_default)
 - #58059 (deprecate before_exec in favor of unsafe pre_exec)
 - #58064 (override `VecDeque::try_rfold`, also update iterator)
 - #58198 (Suggest removing parentheses surrounding lifetimes)
 - #58431 (fix overlapping references in BTree)
 - #58555 (Add a note about 2018e if someone uses `try {` in 2015e)
 - #58588 (remove a bit of dead code)
 - #58589 (cleanup macro after 2018 transition)
 - #58591 (Dedup a rustdoc diagnostic construction)
 - #58600 (fix small documentation typo)
 - #58601 (Search for target_triple.json only if builtin target not found)
 - #58606 (Docs: put Future trait into spotlight)
 - #58607 (Fixes #58586: Make E0505 erronous example fail for the 2018 edition)
 - #58615 (miri: explain why we use static alignment in ref-to-place conversion)
 - #58620 (introduce benchmarks of BTreeSet.intersection)
 - #58621 (Update miri links)
 - #58632 (Make std feature list sorted)

Failed merges:

r? @ghost
@bors bors merged commit f0bef49 into rust-lang:master Feb 23, 2019
@RalfJung RalfJung deleted the btree branch June 10, 2019 11:03
return (k, v);
self.front = ptr::read(&kv).right_edge();
// Doing the descend invalidates the references returned by `into_kv_mut`,
// so we have to do this last.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I struggle with this comment because there's no descend here, right? Maybe the right_edge also invalidates references, or the change is just here to match the code below where a descend happens.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, there's still a call to right_edge so I wanted to be sure that wouldn't cause any problems, without actually checking in detail what that function does. The comment is probably a copy-paste mistake though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've taken the plunge and collapsed the two copies in each iterator in my #66747 PR

bors added a commit that referenced this pull request Jan 31, 2020
Bundle and document 6 BTreeMap navigation algorithms

- Expose a function to step through trees, without necessarily extracting the KV pair, that helps future operations like drain/retain (as demonstrated in [this drain_filter implementation](https://github.com/ssomers/rust/compare/btree_navigation_v3...ssomers:btree_drain_filter?expand=1))
- ~~Also aligns the implementation of the 2 x 3 iterators already using such navigation:~~
  - ~~Delay the moment the K,V references are plucked from the tree, for the 4 iterators on immutable and owned maps, just for symmetry. The same had to be done to the two iterators for mutable maps in #58431.~~
  - ~~Always explicitly use ptr::read to derive two handles from one handle. While the existing implementations for immutable maps (i.e. Range::next_unchecked and Range::next_back_unchecked) rely on implicit copying. There's no change in unsafe tags because these two functions were already (erroneously? prophetically?) tagged unsafe. I don't know whether they should be tagged unsafe. I guess they should be for mutable and owned maps, because you can change the map through one handle and leave the other handle invalid.~~
  - Preserve the way two handles are (temporarily) derived from one handle: implementations for immutable maps (i.e. Range::next_unchecked and Range::next_back_unchecked) rely on copying (implicitly before, explicitly now) and the others do `ptr::read`.
  - ~~I think the functions that support iterators on immutable trees (i.e. `Range::next_unchecked` and `Range::next_back_unchecked`) are erroneously tagged unsafe since you can already create multiple instances of such ranges, thus obtain multiple handles into the same tree. I did not change that but removed unsafe from the functions underneath.~~

Tested with miri in liballoc/tests/btree, except those that should_panic.

cargo benchcmp the best of 3 samples of all btree benchmarks before and after this PR:
```
name                                           old1.txt ns/iter  new2.txt ns/iter  diff ns/iter  diff %  speedup
btree::map::find_rand_100                      17                17                           0   0.00%   x 1.00
btree::map::find_rand_10_000                   57                55                          -2  -3.51%   x 1.04
btree::map::find_seq_100                       17                17                           0   0.00%   x 1.00
btree::map::find_seq_10_000                    42                39                          -3  -7.14%   x 1.08
btree::map::first_and_last_0                   14                14                           0   0.00%   x 1.00
btree::map::first_and_last_100                 36                37                           1   2.78%   x 0.97
btree::map::first_and_last_10k                 52                52                           0   0.00%   x 1.00
btree::map::insert_rand_100                    34                34                           0   0.00%   x 1.00
btree::map::insert_rand_10_000                 34                34                           0   0.00%   x 1.00
btree::map::insert_seq_100                     46                46                           0   0.00%   x 1.00
btree::map::insert_seq_10_000                  90                89                          -1  -1.11%   x 1.01
btree::map::iter_1000                          2,811             2,771                      -40  -1.42%   x 1.01
btree::map::iter_100000                        360,635           355,995                 -4,640  -1.29%   x 1.01
btree::map::iter_20                            39                42                           3   7.69%   x 0.93
btree::map::iter_mut_1000                      2,662             2,864                      202   7.59%   x 0.93
btree::map::iter_mut_100000                    336,825           346,550                  9,725   2.89%   x 0.97
btree::map::iter_mut_20                        40                43                           3   7.50%   x 0.93
btree::set::build_and_clear                    4,184             3,994                     -190  -4.54%   x 1.05
btree::set::build_and_drop                     4,151             3,976                     -175  -4.22%   x 1.04
btree::set::build_and_into_iter                4,196             4,155                      -41  -0.98%   x 1.01
btree::set::build_and_pop_all                  5,176             5,241                       65   1.26%   x 0.99
btree::set::build_and_remove_all               6,868             6,903                       35   0.51%   x 0.99
btree::set::difference_random_100_vs_100       721               691                        -30  -4.16%   x 1.04
btree::set::difference_random_100_vs_10k       2,420             2,411                       -9  -0.37%   x 1.00
btree::set::difference_random_10k_vs_100       54,726            52,215                  -2,511  -4.59%   x 1.05
btree::set::difference_random_10k_vs_10k       164,384           170,256                  5,872   3.57%   x 0.97
btree::set::difference_staggered_100_vs_100    739               709                        -30  -4.06%   x 1.04
btree::set::difference_staggered_100_vs_10k    2,320             2,265                      -55  -2.37%   x 1.02
btree::set::difference_staggered_10k_vs_10k    68,020            70,246                   2,226   3.27%   x 0.97
btree::set::intersection_100_neg_vs_100_pos    23                24                           1   4.35%   x 0.96
btree::set::intersection_100_neg_vs_10k_pos    28                29                           1   3.57%   x 0.97
btree::set::intersection_100_pos_vs_100_neg    24                24                           0   0.00%   x 1.00
btree::set::intersection_100_pos_vs_10k_neg    28                28                           0   0.00%   x 1.00
btree::set::intersection_10k_neg_vs_100_pos    27                27                           0   0.00%   x 1.00
btree::set::intersection_10k_neg_vs_10k_pos    30                29                          -1  -3.33%   x 1.03
btree::set::intersection_10k_pos_vs_100_neg    27                28                           1   3.70%   x 0.96
btree::set::intersection_10k_pos_vs_10k_neg    29                29                           0   0.00%   x 1.00
btree::set::intersection_random_100_vs_100     592               572                        -20  -3.38%   x 1.03
btree::set::intersection_random_100_vs_10k     2,271             2,269                       -2  -0.09%   x 1.00
btree::set::intersection_random_10k_vs_100     2,301             2,333                       32   1.39%   x 0.99
btree::set::intersection_random_10k_vs_10k     147,879           150,148                  2,269   1.53%   x 0.98
btree::set::intersection_staggered_100_vs_100  622               632                         10   1.61%   x 0.98
btree::set::intersection_staggered_100_vs_10k  2,101             2,032                      -69  -3.28%   x 1.03
btree::set::intersection_staggered_10k_vs_10k  60,341            61,834                   1,493   2.47%   x 0.98
btree::set::is_subset_100_vs_100               417               426                          9   2.16%   x 0.98
btree::set::is_subset_100_vs_10k               1,281             1,324                       43   3.36%   x 0.97
btree::set::is_subset_10k_vs_100               2                 2                            0   0.00%   x 1.00
btree::set::is_subset_10k_vs_10k               41,054            41,612                     558   1.36%   x 0.99
```

r? cuviper
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants