-
-
Notifications
You must be signed in to change notification settings - Fork 509
Get rid of new_uninitialized and new_uninitialized_generic. #556
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
Comments
new_uninitialized
and new_uninitialized_generic
.
I'm interested in working on this, as I want non-Copy scalars (#282) for matrices of gmp::Mpz for some cryptanalysis tooling that I'm writing. I've gotten the benchmarks running locally (requires PR #678) so I can measure performance impact. It looks like currently like In order to support non-Copy scalars while still using the above scheme for Copy scalars (for performance), it seems necessary to add another dimension to this type-case (e.g. go from I've tried the following sketches of some trait-level approaches to allowing this branching, but neither quite work: mod bools {
pub enum True {}
pub enum False {}
pub trait TypeLevelBool {}
impl TypeLevelBool for True {}
impl TypeLevelBool for False {}
pub trait TraitLevelTrue: TypeLevelBool {}
impl TraitLevelTrue for True {}
pub trait TraitLevelFalse: TypeLevelBool {}
impl TraitLevelFalse for False {}
}
use bools::{TypeLevelBool, True, False};
pub unsafe trait IsUninitializable { type Value: TypeLevelBool; }
unsafe impl IsUninitializable for u8 { type Value = True; }
unsafe impl IsUninitializable for Vec<u8> { type Value = False; }
pub trait AllocatorIsh<S: IsUninitializable> {
unsafe fn allocate_maybe_uninitialized(n: usize) -> Vec<S>;
}
pub struct DefaultAllocatorIsh;
// the following attempt fails because equality bounds aren't yet supported, but if they were, this would guarantee non-overlap
#[cfg(attempt1)]
mod attempt1 {
use super::*;
impl<S: IsUninitializable> AllocatorIsh<S> for DefaultAllocatorIsh where <S as IsUninitializable>::Value = True {
unsafe fn allocate_maybe_uninitialized(n: usize) -> Vec<S> {
let mut ret = Vec::new();
ret.reserve_exact(n);
ret.set_len(n);
ret
}
}
impl AllocatorIsh<Vec<u8>> for DefaultAllocatorIsh where <Vec<u8> as IsUninitializable>::Value = False {
unsafe fn allocate_maybe_uninitialized(n: usize) -> Vec<Vec<u8>> {
vec![vec![]; n]
}
}
}
// the following attempt fails to compile because from rustc's point of view, bools could semver-compatibly implement TraitLevelFalse for True, so this doesn't guarantee non-overlap
#[cfg(attempt2)]
mod attempt2 {
use super::*;
use bools::{TraitLevelTrue, TraitLevelFalse};
impl<S: IsUninitializable> AllocatorIsh<S> for DefaultAllocatorIsh where <S as IsUninitializable>::Value: TraitLevelTrue {
unsafe fn allocate_maybe_uninitialized(n: usize) -> Vec<S> {
let mut ret = Vec::new();
ret.reserve_exact(n);
ret.set_len(n);
ret
}
}
impl AllocatorIsh<Vec<u8>> for DefaultAllocatorIsh where <Vec<u8> as IsUninitializable>::Value: TraitLevelFalse {
unsafe fn allocate_maybe_uninitialized(n: usize) -> Vec<Vec<u8>> {
vec![vec![]; n]
}
}
}
fn main() {} Does this seem like a good approach, in the sense that if the trait metaprogramming can be made to work, I'm not missing any performance/safety concerns? |
I got a variant of the first approach working, and added more comments: mod bools {
pub enum True {}
pub enum False {}
pub trait TypeLevelBool {}
impl TypeLevelBool for True {}
impl TypeLevelBool for False {}
}
use bools::{TypeLevelBool, True, False};
pub unsafe trait IsUninitializable { type Value: TypeLevelBool; }
// examples of Copy types that should use uninitialized
unsafe impl IsUninitializable for u8 { type Value = True; }
unsafe impl IsUninitializable for u16 { type Value = True; }
// example of a !Copy type that shouldn't use uninitialized
unsafe impl IsUninitializable for Vec<u8> { type Value = False; }
// AllocatiorIsh is Allocator specialized to the dynamic case, as mem::uninitialized isn't used explicitly, but is implicit in the use of set_len
// this should extend to cross-product impls for static and dynamic dimensions straightforwardly
pub trait AllocatorIsh<S: IsUninitializable> {
unsafe fn allocate_maybe_uninitialized(n: usize) -> Vec<S>;
}
pub struct DefaultAllocatorIsh;
mod attempt3 {
use super::*;
// any type can opt into the optimized case by `unsafe impl`-ing IsInitializable<Value=True>
impl<S: IsUninitializable<Value=True>> AllocatorIsh<S> for DefaultAllocatorIsh {
unsafe fn allocate_maybe_uninitialized(n: usize) -> Vec<S> {
let mut ret = Vec::new();
ret.reserve_exact(n);
ret.set_len(n);
ret
}
}
// for IsUninitializable<Value=False> types, instances can be given explicitly, with ad-hoc initialization, by downstream crates
impl AllocatorIsh<Vec<u8>> for DefaultAllocatorIsh {
unsafe fn allocate_maybe_uninitialized(n: usize) -> Vec<Vec<u8>> {
vec![vec![]; n]
}
}
}
fn main() {} |
On further reflection, it looks like the point of |
@aweinstock314 The proposal based on The actual solution would be to find a way to leverage the
Returning a buffer with a scalar type equal to |
#684 supports non-Copy scalars well enough to typecheck some (currently private) code that uses |
Could I get feedback on the current design? The core change is adding an With respect to the soundness of converting between The definition of Most of the rest of the changes already done to this branch involve getting This is currently blocked waiting for a fix for a rustc ICE (probably a duplicate of rust-lang/rust#68090, which already has a fix in progress). |
This looks like a great approach! Once the trait architecture woks, the main challenge will be to make the methods from the |
Heads-up, in rustc we are trying to better detect UB due to misuse of uninitialized memory (rust-lang/rust#71274), and I think this issue came up as a regression for the next stage of that check. Specifically, this crater regression looks to me like it is caused by these |
Ah, but you are using the dreadful |
…allocate_uninitialized` and `Matrix::new_uninitialized_generic`. Most call sites still invoke UB through `assume_init`. Said call sites instead invoke `unimplemented!()` if the `no_unsound_assume_init` feature is enabled, to make it easier to gradually fix them. Progress towards dimforge#556.
…allocate_uninitialized` and `Matrix::new_uninitialized_generic`. Most call sites still invoke UB through `assume_init`. Said call sites instead invoke `unimplemented!()` if the `no_unsound_assume_init` feature is enabled, to make it easier to gradually fix them. Progress towards dimforge#556.
…allocate_uninitialized` and `Matrix::new_uninitialized_generic`. Most call sites still invoke UB through `assume_init`. Said call sites instead invoke `unimplemented!()` if the `no_unsound_assume_init` feature is enabled, to make it easier to gradually fix them. Progress towards dimforge#556.
…allocate_uninitialized` and `Matrix::new_uninitialized_generic`. Most call sites still invoke UB through `assume_init`. Said call sites instead invoke `unimplemented!()` if the `no_unsound_assume_init` feature is enabled, to make it easier to gradually fix them. Progress towards dimforge#556.
Uh oh!
There was an error while loading. Please reload this page.
The two unsafe functions Matrix::new_uninitialized and Matrix::new_uninitialized_generic are used when we don't care about the initial value of a matrix/vector because they will be overwritten by the result of other operations. We use them mostly for performance reasons to avoid an unnecessary initialization step.
However, this is the most significant blocker for #282. So if we ever want to support non-Copy types, this is the first thing to address in a way that does not hurt performance for Copy types.
The text was updated successfully, but these errors were encountered: