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

Add AlignType and use it for buffer allocations #141

Closed
wants to merge 2 commits into from

Conversation

elichai
Copy link
Member

@elichai elichai commented Aug 8, 2019

This addresses #138
In rust 1.22, we cannot get any type with alignment bigger than 8.
so we take u64 for any platform that is lower than 64bit and usize for any platform that is u64 and up.

I'm not entirely sure if the assertions are needed, I mostly use them as sanity checks and I hope that they're optimized out by the compiler(they can be easily checked at compile time), but if anyone feel differently I can remove some/all of them.

@@ -572,7 +574,15 @@ impl<C: Context> Secp256k1<C> {

/// Returns the required memory for a preallocated context buffer in a generic manner(sign/verify/all)
Copy link
Contributor

@laanwj laanwj Aug 8, 2019

Choose a reason for hiding this comment

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

please add comment that this returns the size in AlignType units

(also for the specific methods Secp256k1::preallocate_size_XX(), otherwise someone might think they return bytes!)

Copy link
Member Author

Choose a reason for hiding this comment

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

Good Point!

Copy link
Member

Choose a reason for hiding this comment

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

I think maybe they should return bytes.

Copy link
Member Author

Choose a reason for hiding this comment

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

why?

src/lib.rs Outdated Show resolved Hide resolved
@elichai
Copy link
Member Author

elichai commented Aug 8, 2019

(I might split the commit into code, and tests+doc)

Copy link
Collaborator

@real-or-random real-or-random left a comment

Choose a reason for hiding this comment

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

I think 8 may hurt us, libsecp256k1 uses uint128_t on gcc, which has alignment 16 on x86_64 for example:
https://godbolt.org/z/6-TVaY

edit: Oh I should have opened IRC earlier:

<wumpus> secp256k1 never stores 128 bit integers though
<wumpus> (it uses it for multiplication in one place, but only on the stack)

That's true, nevermind.

src/context.rs Outdated Show resolved Hide resolved
src/context.rs Outdated Show resolved Hide resolved
/// ```rust
/// # use secp256k1::*;
/// let buf_size = Secp256k1::preallocate_size();
/// let mut buf = vec![0; buf_size];
Copy link
Collaborator

Choose a reason for hiding this comment

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

0 as AlignType?

Copy link
Member Author

Choose a reason for hiding this comment

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

rust automatically assign the right type because it's then passed to Secp256k1::preallocated_new which accepts [AlignType]

@elichai
Copy link
Member Author

elichai commented Aug 8, 2019

Ha, I don't like the fact that gcc's uint128_t alignment is 16. it means that the second upstream writes it to context then we have a problem.
It's also so weird to me that clang and rust use different alignments for a uint128 clang: https://godbolt.org/z/WyzeRz rust: https://godbolt.org/z/hGjfLQ

@elichai elichai force-pushed the 2019-08-16-alignment branch 2 times, most recently from 8477d9f to 07460fd Compare August 8, 2019 17:59
@real-or-random
Copy link
Collaborator

It's also so weird to me that clang and rust use different alignments for a uint128 clang: https://godbolt.org/z/WyzeRz rust: https://godbolt.org/z/eJqq12

I think that's expected. I guess usize is u64 on that system?

src/context.rs Outdated
/// Uses the ffi `secp256k1_context_preallocated_size` to check the memory size needed for the context
/// Returns the required memory for a preallocated context buffer in a generic manner(sign/verify/all)
///
/// Notice that the memory returned is in [AlignedType](type.AlignType.html)
Copy link
Collaborator

Choose a reason for hiding this comment

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

s/AlignedType/AlignType

To be honest, I don't like that name too much anyway. But I don't have better suggestions. PreallocatedMemory? Maybe better but not great either.

@elichai
Copy link
Member Author

elichai commented Aug 8, 2019

It's also so weird to me that clang and rust use different alignments for a uint128 clang: godbolt.org/z/WyzeRz rust: godbolt.org/z/eJqq12

I think that's expected. I guess usize is u64 on that system?

Ops, I meant to do https://godbolt.org/z/hGjfLQ

@elichai
Copy link
Member Author

elichai commented Aug 8, 2019

#142 Is a blocker to making the tests here pass.
I have no idea why only now this problem had come up, maybe a new rust compiler version changed the behavior of ZST in emscripten (which is allowed).

@elichai
Copy link
Member Author

elichai commented Aug 22, 2019

Now that #142 is solved the tests pass here :)

@RalfJung
Copy link

It's also so weird to me that clang and rust use different alignments for a uint128 clang

Due to an LVLM bug, i128/u128 in Rust are currently not FFI-safe. So, you cannot use these types when doing Rust <-> C FFI. See rust-lang/rust#54341 for further details.

The "FFI safety" lint should thus warn about i128/u128 on FFI boundaries, does it not?

src/context.rs Outdated
/// Trying to match what `malloc` does in C, this should be aligned enough to contain pointers too.
/// This type can have different size/alignment depending on the architecture.
#[cfg(any(target_pointer_width = "32", target_pointer_width = "16", target_pointer_width = "8"))]
pub type AlignType = u64;

Choose a reason for hiding this comment

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

Uh, based on what documentation are you assuming that this is a type with "the biggest alignment we can get"? I don't think such a type can even exist, since we can do things like

#[repr(align(256))]
struct A(u8);

This type is 256-aligned.

I am not sure what you are trying to do here, but it seems extremely fishy.

Copy link
Member Author

@elichai elichai Sep 16, 2019

Choose a reason for hiding this comment

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

I'll start with the purpose and then with "the biggest".
The purpose is creating a pointer that is valid for a C function contract that requires a pointer from malloc:

The pointer returned if the allocation succeeds is suitably aligned so that it may be assigned to a pointer to any type of object with a fundamental alignment requirement and then used to access such an object or an array of such objects in the space allocated (until the space is explicitly deallocated).

Now malloc doesn't define what is this. and currently pointers in 64bit architecture are u64 so we know that we need at least this much alignment because the C library does write pointers into that memory.
The only thing currently that can align more than that is __uint128_t which is 16 bytes alignment.

Now to why this value was chosen.
we support rust 1.22.0 here (last point: https://github.com/rust-bitcoin/rust-secp256k1#contributing) so we can't use #[repr(align(N))], and even if we could i'm not sure what would be the right value to put there.
but currently as we don't have support for that, and we know that we need at most 8 bytes alignment we use u64 for alignment.
I agree that's not a good solution, but because rust will never have max_align_t and we don't have access to C's max alignment (as it's not defined AFAIK), so currently the only thing we can do is maintain an alignment as big as the biggest primitive's alignment that we can check.

Choose a reason for hiding this comment

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

Okay, so what you really want is C's alignof(max_align_t)? The Rust libstd has a table for this that I copied into Miri at some point. I don't think it agrees with the value you have been using here, though. In particular, on 64bit x86, max_align_t is 16-byte aligned.

However, how far does this "requires a pointer from malloc" go? Does the pointer have to be freeable with free? If yes, you have to call malloc; the pointer you get from Rust's alloactor might or might not come from malloc. Even if no, it might be easiest to just call malloc?

@gnzlbg does libc expose max_align_t, or is there some other way to get that information though libc?

I am still confused why you need this as a type though, as opposed to just having to know the alignment and then using that (together with the size, which I assume is supplied separately) when allocating.

Copy link

Choose a reason for hiding this comment

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

@gnzlbg does libc expose max_align_t, or is there some other way to get that information though libc?

Not yet but this seems a reasonable thing to implement.

Copy link

Choose a reason for hiding this comment

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

I am still confused why you need this as a type though, as opposed to just having to know the alignment and then using that (together with the size, which I assume is supplied separately) when allocating.

I suppose that people wanted to be able to use C's alignof(<type>) to compute the maximing alignment, so instead of creating a MAX_ALIGN constant, they decided to over engineer it and make it a type.

If we expose this from libc, in Rust, you can just do const MAX_ALIGN: usize = align_of::<libc::max_align_t>(); and use that. In jemalloc-sys we use a hardcoded MAX_ALIGN constant for that.

Copy link
Member Author

@elichai elichai Sep 17, 2019

Choose a reason for hiding this comment

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

@RalfJung the exact definition from upstream is:

The caller must provide a pointer to a rewritable contiguous block of memory of size at least secp256k1_context_preallocated_size(flags) bytes, suitably aligned to hold an object of any type.

So being able to pass it to free() isn't a requirement, being aligned as max is.
https://github.com/bitcoin-core/secp256k1/blob/master/include/secp256k1_preallocated.h#L41

is it possible to achieve alignment 16 without repr(align(N))?

Copy link

Choose a reason for hiding this comment

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

is it possible to achieve alignment 16 without repr(align(N))?

core::arch::...::__m128 have alignment of 16.

Copy link

@RalfJung RalfJung Sep 17, 2019

Choose a reason for hiding this comment

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

the exact definition from upstream is

I see. "object of any type" is certainly not true even in C with compiler extensions, as C also has a way to increase alignment similar to #[repr(align)] in Rust. So probably upstream means max_align_t but it might be a good idea to ask them to clarify their docs.

So, why do you need a type that has that alignment, as opposed to just using alloc::alloc? Is Rust 1.28 (more than a year old at this point!) too "new"?

EDIT: Ah yes, you mentioned Rust 1.22 above (released in 2017, wow). Well I'm afraid if you are restricting yourself to support ancient unsupported versions of Rust, things might become messy. I think it's a mistake to not use a better version when it exists, this is holding back the ecosystem... but that's up to you. I just don't have a lot of motivation to review code that is awful for entirely self-inflicted reasons.

EDIT2: Sorry for venting, you probably didn't even come up with this policy. It's just frustrating to see how policies I consider misguided hold back language development.

Copy link

Choose a reason for hiding this comment

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

"object of any type" is certainly not true even in C with compiler extensions,

That's because that's incorrect. The requirement is that the allocation must be suited for any type with fundamental alignment, where "fundamental" means that you did not use anything like repr(align()) anywhere on it, among other things.

If you use repr(align()) on it in such a way that the alignment of the type is larger than the largest alignment of such a "fundamental" type, then you can't use malloc to allocate its memory, and need to use an API that accepts an alignment instead.

Copy link
Member Author

Choose a reason for hiding this comment

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

You're right when it comes to modern C.
But upstream uses C89, so there's no max_align_t and no _Alignas, and AFAIK there's no sense of "fundamental type".
But it still may be confusing so i'll ask upstream what people think. Thanks.

@elichai
Copy link
Member Author

elichai commented Aug 24, 2020

I think we should postpone this until we bump MSRV and then replace the AlignType typedef with a struct of size+align of 16, which aligns with https://github.com/rust-lang/rust/blob/2c31b45ae878b821975c4ebd94cc1e49f6073fd0/library/std/src/sys_common/alloc.rs
and we can test it against the alignment of https://docs.rs/libc/0.2.76/libc/struct.max_align_t.html

@apoelstra
Copy link
Member

Now that our MSRV is 1.29 I think we can do this.

@real-or-random
Copy link
Collaborator

With 1.29, I think all of this can be done much simpler using https://doc.rust-lang.org/std/alloc/fn.alloc.html and https://doc.rust-lang.org/std/alloc/struct.Layout.html#method.from_size_align. Then we're responsible for deallocation but we don't need to create artifical types to abuse Vec to do the right thing.

@elichai
Copy link
Member Author

elichai commented Aug 28, 2020

With 1.29, I think all of this can be done much simpler using https://doc.rust-lang.org/std/alloc/fn.alloc.html and https://doc.rust-lang.org/std/alloc/struct.Layout.html#method.from_size_align. Then we're responsible for deallocation but we don't need to create artifical types to abuse Vec to do the right thing.

Yep. I already started working on a few alternatives, the main "problem" is the preallocated_*(&mut [u8]) functions
where we'll need to switch [u8] with something more aligned and store it.
(if we bumped to 1.30 we could've done something like this https://doc.rust-lang.org/std/primitive.slice.html#method.align_to but it could also cause problems in actually knowing what size you need beforehand)

@real-or-random
Copy link
Collaborator

real-or-random commented Aug 28, 2020

the main "problem" is the preallocated_*(&mut [u8]) functions
where we'll need to switch [u8] with something more aligned and store it.

Uff, I wasn't aware that we offer these functions. Aren't these terribly broken even now if you pass an unaliged buffer there?

The docs don't even say anything about alignment.

@elichai
Copy link
Member Author

elichai commented Dec 9, 2020

succeeded by #233

@elichai elichai closed this Dec 9, 2020
apoelstra added a commit that referenced this pull request Dec 21, 2020
Making sure everything is aligned correctly. Succeeder of #141
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants