-
-
Notifications
You must be signed in to change notification settings - Fork 101
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
memrchr
implementations may conflict with stacked borrows
#58
Comments
Thanks for the issue! Unfortunately, I don't fully grok the underlying explanation for this. Perhaps you could ELINRJ (that is, Explain Like I'm Not Ralf Jung :-)). While these points/questions don't represent the full extent of my non-understanding, they are perhaps a start:
|
Sure, I will try to answer the immediate questions first.
The phrase says: Under the memory model of stacked borrows, the implementation here is illegal. Since stacked borrows is not the official memory model of Rust (yet?) this doesn't make the code have undefined behaviour but the compiler may be changed so that it has. Since adapting the code to be defined even under stacked borrowed should be possible, this is a future proofing issue.
A part of stacked borrows is 'pointer provenance', the concept that the source of a pointer may influence the allowed operations. In particular, two pointers that compare equal may not be allowed to be used interchangably (for dereferencing). (Some more reading, another blog post by Ralf Jung.) And so, while the pointer of the empty slice at the end equals the pointer offset from the starting pointer, dereferencing the two pointers or other pointers created from them need not be allowed equally. The
Yes, see the above.
The offsetting is allowed but dereferencing/reading from the pointee may be still illegal. Note that the two |
Interesting. Thanks for elaborating. I think I grok this at a surface level, but probably still lack a deeper understanding. I'll mark this as a bug for now. I'd almost slightly prefer to hold off on fixing it until Miri is able to track it, so that we get some confidence that this is the right play. |
To clarify, this is where the mismatch comes in. @HeroicKatora already mentioned pointer provenance, so let me just point you to rust-lang/unsafe-code-guidelines#134 where we track if maybe Stacked Borrows is too strict here (but also, we might lose basically all aliasing-based optimizations if we relax this). Also see this discussion where I explained the issue in some more details:
Basically, |
I think this is the most interesting part to me! My prior mental model is that this pub const fn as_ptr(&self) -> *const T {
self as *const [T] as *const T
} could equivalently be implemented as pub const fn as_ptr(&self) -> *const T {
core::mem::transmute(self)
} But it sounds like that may not be the case since the actual series of steps on goes through via the raw pointer casts seems to matter. I must say that I would enjoy having these restrictions placed on raw pointers, but only if tooling could reliably enforce it. (Presumably through miri.) If tooling couldn't reliably enforce it, then I think my joy would turn to misery. :-) |
Doing this in Miri is not a big issue. (Raw pointers right now are more relaxed mostly because there is some libstd code that is broken, and cannot be fixed without rust-lang/rfcs#2582.) But Miri cannot run on FFI code, or code interacting with the hardware (embedded/kernel stuff), or code that just does lots of stuff (Miri is very slow). I think we could have a valgrind tool that helps detect such issues, but by their very nature valgrind tools are unable to reliably find UB.
Fair. ;) Reference-to-raw transmutes are an interesting open question. This is related to the fact that nobody knows what the exact LLVM semantics are for pointer-to-int transmutes -- making them the same as a cast would kill some quite important optimizations. Also see the discussion of type punning in §8 of this paper. If you want your model of pointer casts shattered, have a look at this LLVM bug. |
@BurntSushi I don't actually understand your two examples (the transmute doesn't seem to be valid because While looking at this I noticed again (playground link) that we don't allow (When someone brought up this issue elsewhere I thought the |
This will be fixed in #82. I know this technically isn't required yet, but I don't see any good reason not to do it. |
This commit primarily adds vectorized substring search routines in a new memmem sub-module. They were originally taken from bstr, but heavily modified to incorporate a variant of the "generic SIMD" algorithm[1]. The main highlights: * We guarantee `O(m + n)` time complexity and constant space complexity. * Two-Way is the primary implementation that can handle all cases. * Vectorized variants handle a number of common cases. * Vectorized code uses a heuristic informed by a frequency background distribution of bytes, originally devised inside the regex crate. This makes it more likely that searching will spend more time in the fast vector loops. While adding memmem to this crate is perhaps a bit of a scope increase, I think it fits well. It also puts a core primitive, substring search, very low in the dependency DAG and therefore making it widely available. For example, it is intended to use these new routines in the regex, aho-corasick and bstr crates. This commit does a number of other things, mainly as a result of convenience. It drastically improves test coverage for substring search (as compared to what bstr had), completely overhauls the benchmark suite to make it more comprehensive and adds `cargo fuzz` support for all API items in the crate. Closes #58, Closes #72 [1] - http://0x80.pl/articles/simd-strfind.html#algorithm-1-generic-simd
This commit primarily adds vectorized substring search routines in a new memmem sub-module. They were originally taken from bstr, but heavily modified to incorporate a variant of the "generic SIMD" algorithm[1]. The main highlights: * We guarantee `O(m + n)` time complexity and constant space complexity. * Two-Way is the primary implementation that can handle all cases. * Vectorized variants handle a number of common cases. * Vectorized code uses a heuristic informed by a frequency background distribution of bytes, originally devised inside the regex crate. This makes it more likely that searching will spend more time in the fast vector loops. While adding memmem to this crate is perhaps a bit of a scope increase, I think it fits well. It also puts a core primitive, substring search, very low in the dependency DAG and therefore making it widely available. For example, it is intended to use these new routines in the regex, aho-corasick and bstr crates. This commit does a number of other things, mainly as a result of convenience. It drastically improves test coverage for substring search (as compared to what bstr had), completely overhauls the benchmark suite to make it more comprehensive and adds `cargo fuzz` support for all API items in the crate. Closes #58, Closes #72 [1] - http://0x80.pl/articles/simd-strfind.html#algorithm-1-generic-simd
This commit primarily adds vectorized substring search routines in a new memmem sub-module. They were originally taken from bstr, but heavily modified to incorporate a variant of the "generic SIMD" algorithm[1]. The main highlights: * We guarantee `O(m + n)` time complexity and constant space complexity. * Two-Way is the primary implementation that can handle all cases. * Vectorized variants handle a number of common cases. * Vectorized code uses a heuristic informed by a frequency background distribution of bytes, originally devised inside the regex crate. This makes it more likely that searching will spend more time in the fast vector loops. While adding memmem to this crate is perhaps a bit of a scope increase, I think it fits well. It also puts a core primitive, substring search, very low in the dependency DAG and therefore making it widely available. For example, it is intended to use these new routines in the regex, aho-corasick and bstr crates. This commit does a number of other things, mainly as a result of convenience. It drastically improves test coverage for substring search (as compared to what bstr had), completely overhauls the benchmark suite to make it more comprehensive and adds `cargo fuzz` support for all API items in the crate. Closes #58, Closes #72 [1] - http://0x80.pl/articles/simd-strfind.html#algorithm-1-generic-simd
This commit primarily adds vectorized substring search routines in a new memmem sub-module. They were originally taken from bstr, but heavily modified to incorporate a variant of the "generic SIMD" algorithm[1]. The main highlights: * We guarantee `O(m + n)` time complexity and constant space complexity. * Two-Way is the primary implementation that can handle all cases. * Vectorized variants handle a number of common cases. * Vectorized code uses a heuristic informed by a frequency background distribution of bytes, originally devised inside the regex crate. This makes it more likely that searching will spend more time in the fast vector loops. While adding memmem to this crate is perhaps a bit of a scope increase, I think it fits well. It also puts a core primitive, substring search, very low in the dependency DAG and therefore making it widely available. For example, it is intended to use these new routines in the regex, aho-corasick and bstr crates. This commit does a number of other things, mainly as a result of convenience. It drastically improves test coverage for substring search (as compared to what bstr had), completely overhauls the benchmark suite to make it more comprehensive and adds `cargo fuzz` support for all API items in the crate. Closes #58, Closes #72 [1] - http://0x80.pl/articles/simd-strfind.html#algorithm-1-generic-simd
The reverse search implementations (
memrchr
) seem illegal under stacked borrows. They all follow the same pattern, so here I'll only annotate one. It retrieves a raw pointer to the end of the haystack from a reference to an empty slice, but then uses that pointer to iterate backwards by offsetting it with negative indices. Under strict rules, that pointer would however only be valid for access to the bytes that the reference covered from which it was cast, i.e. a zero-length slice at the end.To my understanding, this is very likely illegal but not yet caught by MIRI since it does not strictly track the source for raw pointers (^source). @RalfJung might be able to provide more definitive insights.
Relevant code (inserted comments marked as
// !
):Library code reduced to that version of memrchr.
The fix is simple, create
ptr
from manually offsettinghaystack.as_ptr()
which is valid for the whole haystack. I also don't expect any miscompilation.The text was updated successfully, but these errors were encountered: