If you use the G_DECLARE_FINAL_TYPE() macro, you can have a situation where the header cast causes an alignment warning if after the GObject we have something that is 8 bytes (like a gint64 or gdouble).
This is breaking sysprof builds on armhf/armhf/mipsel on Debian.
See also sysprof bug #775909 for some build logs.
Like bug 712370, we probably need to just add an extra (gpointer)/(void*) cast in the type cast macros to silence the warning and should be safe (since we are guaranteed to get a 2-pointer aligned allocation from GSlice anyway).
(since we are guaranteed to get a 2-pointer aligned allocation from GSlice
anyway).
Really? The documentation for g_slice_alloc() says that the returned address:
can be expected to be aligned to at least 1 * sizeof (void*), though in general slices are 2 * sizeof (void*) bytes aligned, if a malloc() fallback implementation is used instead, the alignment may be reduced in a libc dependent fashion.
I haven’t checked the GSlice code, but if that’s true then our alignment might potentially not be correct for objects like this.
Currently, on 32-bit there is a strong chance you'll get a warning for this, because the gdouble increases the alignment requirement to 8, but the ref_count field from GObject is only 4 (and the header forward-declaration of the type will not know about the double).
If it's not 2sizeof(void), it should at least be >=sizeof(gdouble) since
malloc is guaranteed to return a pointer that can store the basic types.
Right, but sizeof(double) is undefined in the C standard, iirc. It’s almost always 8 bytes (guaranteed to be so on x86/x86_64 platforms, since they’re guaranteed to use IEEE 754 floating point; but might differ on other platforms).
So we should at least mark the alignment as 8 instead of 4. This will fix
situations like:
/* in header */
G_DECLARE_FINAL_TYPE (FooBar, fooBar, FOO, BAR, GObject)
Currently, on 32-bit there is a strong chance you'll get a warning for this,
because the gdouble increases the alignment requirement to 8, but the
ref_count field from GObject is only 4 (and the header forward-declaration
of the type will not know about the double).
Yeah. If we marked the alignment of GObject as 8, that wouldn’t be a significant performance hit anywhere, would it? It might mean an extra 4 bytes of allocation padding on some platforms (but would mean no difference on most platforms). It would easily solve all the problems like this. I’d be in favour of that if there really are no downsides.
Yeah. If we marked the alignment of GObject as 8, that wouldn’t be a
significant performance hit anywhere, would it? It might mean an extra 4
bytes of allocation padding on some platforms (but would mean no difference
on most platforms). It would easily solve all the problems like this. I’d be
in favour of that if there really are no downsides.
Performance-wise, I'd expect it to only ever be better since you reduce your chances of reading data across alignment boundaries (which could be a SIGBUS on some architectures like SPARC anyway).
GSlice first categorizes the allocation into a few categories:
1 - allocate from magazine
2 - allocate from slab
3 - fallback to malloc
For 3, we don't need to do anything, since we're guaranteed to get something back that can store a double.
For 1 and 2, they use the chunk_size, which is the nearest ^2 from the requested allocation size (gslice.c:1005). Any power of two that is >=8 will have a natural alignment that is safe for double (and thereby, anything containing a double would be safe using natural packing alignments).
Since the fallback for gslice (G_SLICE=always-malloc) will always be aligned for double storage, I believe this is safe to just add an alignment == sizeof(double) (and that might require a configure time check instead of 8, but I doubt it anywhere we run today).
GSlice first categorizes the allocation into a few categories:
1 - allocate from magazine
2 - allocate from slab
3 - fallback to malloc
For 3, we don't need to do anything, since we're guaranteed to get something
back that can store a double.
That would give sufficient alignment to fix the original bug here, but it doesn’t strictly translate into a guarantee that everything is at least 8-byte aligned.
Do we want to give a guarantee that everything is 8-byte aligned, or a guarantee that everything is aligned to the largest basic type?
That would give sufficient alignment to fix the original bug here, but it
doesn’t strictly translate into a guarantee that everything is at least
8-byte aligned.
But it does, because if we look at the GObject struct:
On 32-bit systems, that will be 12-bytes, meaning we use the 16-byte slab/magazine in GSlice. So it will always have a natural alignment of at least a 16-byte boundary.
For malloc based allocations, we are guaranteed to have something that can store a long long/uint64_t/etc. So we maintain at least an 8-byte alignment.
Do we want to give a guarantee that everything is 8-byte aligned, or a
guarantee that everything is aligned to the largest basic type?
See the reasoning in the patch for why we believe GObjects are
(already) as aligned as the basic types.
We want to make this guarantee so that it’s guaranteed to be safe for
people to ignore -Wcast-align warnings for GObjects which contain basic
types. This typically happens with gdouble on 32-bit ARM platforms.
The checks are slightly complicated by the need to support GObjects with
custom constructors. We should expect that a custom construction
function will chain up to g_object_constructor (which calls
g_type_create_instance() as normal), but it’s possible that someone has
done something crazy and uses a custom allocator which doesn’t return
with the same alignment as GSlice. Hand them a warning in that case. If
that is true, the code which uses their custom-constructed GObject can
presumably already deal with the alignment it gets given.
Regardless of the actual alignment of the GTypeInstance in question,
these do a runtime check on the type, so if the type was originally
aligned correctly when allocated, it should be aligned correctly if the
type check succeeds. -Wcast-align is meant to warn about casts between
types, which this isn’t (if the check succeeds).
We now guarantee that GObjects will always be allocated at least as
aligned as the basic types. If you want to put an element in your
GObject which has higher alignment requirements, we can’t guarantee it
will be aligned*. If you need it to be aligned, you’ll need to put it on
the heap (aligned appropriately), or add appropriate padding in your
GObject struct.
*Actually, GSlice will guarantee that the whole GObject is aligned to at
least the power of 2 greater than or equal to the size of the GObject,
which means any element in the GObject struct should always be
appropriate aligned if the compiler pads it appropriately. If malloc()
is used, however, it doesn’t make that guarantee, so we can’t make that
guarantee overall.
Add a runtime assert that GObject is at least as aligned as gdouble/guint64.
Cast through (void*) in G_TYPE_CHECK_INSTANCE_CAST. Since we’re doing a runtime type check, it doesn’t matter what the alignment actually is: if the object’s been aligned correctly on allocation, any successful cast to it is guaranteed to be aligned. I haven’t made the corresponding change on the optimised version of G_TYPE_CHECK_INSTANCE_CAST which doesn’t do the runtime check, since it doesn’t cast through GTypeInstance*. Maybe I should, for consistency with the first version? I could see there being warnings emitted when building against a non-debug GLib which aren’t emitted with debug GLib then.
Document what people are supposed to do if they want a more-aligned member in a GObject struct: allocate the member on the heap, or ensure their struct is appropriately padded.
Writing the last patch made me wonder whether perhaps we should instead be making stronger guarantees about the alignments of GObjects: for example, the GSlice guarantee that they’re aligned to the power of 2 greater than or equal to the struct size; or a guarantee that they’re always as aligned as their member with the largest alignment.
I was also wondering about how to expose this runtime alignment information to the compiler (to tell it that GObject*s are guaranteed to be aligned to at least the alignment of the largest basic type). Would adding an architecture-dependent __attribute__((aligned(x))) to struct GObject do that (where x is calculated at configure time as the maximum alignof() all the basic types)? I can’t test it (no suitable ARM box at the moment), so I haven’t made the change. (On that note, I haven’t tested any of these patches on ARM.)
Writing the last patch made me wonder whether perhaps we should instead be
making stronger guarantees about the alignments of GObjects: for example,
the GSlice guarantee that they’re aligned to the power of 2 greater than or
equal to the struct size; or a guarantee that they’re always as aligned as
their member with the largest alignment.
I was also wondering about how to expose this runtime alignment information
to the compiler (to tell it that GObject*s are guaranteed to be aligned to
at least the alignment of the largest basic type). Would adding an
architecture-dependent __attribute__((aligned(x))) to struct GObject do that
(where x is calculated at configure time as the maximum alignof() all the
basic types)? I can’t test it (no suitable ARM box at the moment), so I
haven’t made the change. (On that note, I haven’t tested any of these
patches on ARM.)
Sorry, forgot to respond here.
For attribute alignment, __attribute__((aligned(8)) (or whatever our max aligned type) isn't enough, because that will only work for GCC/Clang/Mingw. We need both an alignment prefix and suffix so we can support MSVC.
Writing the last patch made me wonder whether perhaps we should instead be
making stronger guarantees about the alignments of GObjects: for example,
the GSlice guarantee that they’re aligned to the power of 2 greater than or
equal to the struct size; or a guarantee that they’re always as aligned as
their member with the largest alignment.
I’d also like Colin’s and Matthias’ opinions on this.
It would be good if someone with access to an ARM/macOS machine could take this on, as I can’t reproduce it and have so far been unable to find time to take it on. Unassigning myself.