Change the type of [Int]PointTyped::[x|y] back to [Int]CoordTyped
Categories
(Core :: Graphics: Layers, defect)
Tracking
()
Tracking | Status | |
---|---|---|
firefox108 | --- | fixed |
People
(Reporter: botond, Assigned: rzvncj)
References
Details
Attachments
(1 file)
Reporter | ||
Comment 1•10 years ago
|
||
Comment 2•9 years ago
|
||
Updated•2 years ago
|
Assignee | ||
Comment 3•2 years ago
|
||
So to make sure I got this right, you want all [Int]PointTyped
classes gone from gfx/2d/Point.h, correct?
Reporter | ||
Comment 4•2 years ago
|
||
The idea is not to remove the [Int]PointTyped
classes, but rather to change the type of the x
and y
fields (stored in the base class BasePoint
) from a raw numerical type (T
in BasePoint
is instantiated with either int32_t
, float
, or double
) to a strongly-typed wrapper. The wrapper type is passed in to BasePoint
as the Coord
template parameter (IntPointTyped
passes IntCoordTyped
, and PointTyped
passes CoordTyped
).
Per comment 1, we should start by checking that this refactoring does not change the size of the structures in question (e.g. we should check sizeof(CSSPoint)
and sizeof(CSSIntPoint)
and make sure they're the same before and after; we may even want to try and static_assert
this somehow).
Assignee | ||
Comment 5•2 years ago
|
||
(In reply to Botond Ballo [:botond] from comment #4)
Per comment 1, we should start by checking that this refactoring does not change the size of the structures in question (e.g. we should check
sizeof(CSSPoint)
andsizeof(CSSIntPoint)
and make sure they're the same before and after; we may even want to try andstatic_assert
this somehow).
I assume that this is because they're being serialized somewhere?
Reporter | ||
Comment 6•2 years ago
|
||
(In reply to Razvan Cojocaru from comment #5)
I assume that this is because they're being serialized somewhere?
No, it's more just that these types are widely used and stored, and the benefits of stronger typing that we would gain from this refactoring are probably not worth a memory usage regression.
Assignee | ||
Comment 7•2 years ago
|
||
(In reply to Botond Ballo [:botond] from comment #4)
Per comment 1, we should start by checking that this refactoring does not change the size of the structures in question (e.g. we should check
sizeof(CSSPoint)
andsizeof(CSSIntPoint)
and make sure they're the same before and after; we may even want to try andstatic_assert
this somehow).
Is the static_assert
not already enforced here and here? It looks like any size change will trip it.
Reporter | ||
Comment 8•2 years ago
|
||
(In reply to Razvan Cojocaru from comment #7)
Is the
static_assert
not already enforced here and here? It looks like any size change will trip it.
Ah, good find! Yeah, those are suitable, so we just need to make sure our changes pass them.
Assignee | ||
Comment 9•2 years ago
|
||
Welp, so much for this patch I suppose:
0:10.75 In file included from Unified_cpp_accessible_aom0.cpp:2:
0:10.75 In file included from /mnt/usb/work/debug/mozilla-unified/accessible/aom/AccessibleNode.cpp:11:
0:10.76 In file included from /mnt/usb/work/debug/mozilla-unified/obj-x86_64-pc-linux-gnu/dist/include/nsContentUtils.h:26:
0:10.76 In file included from /mnt/usb/work/debug/mozilla-unified/obj-x86_64-pc-linux-gnu/dist/include/Units.h:13:
0:10.76 /mnt/usb/work/debug/mozilla-unified/obj-x86_64-pc-linux-gnu/dist/include/mozilla/gfx/Point.h:137:5: error: static_assert failed due to requirement 'sizeof(mozilla::gfx::PointTyped<mozilla::LayoutDevicePixel, float>) == sizeof(float) * 2' "Would be unfortunate otherwise!"
0:10.76 static_assert(sizeof(PointTyped) == sizeof(F) * 2,
0:10.76 ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0:10.76 /mnt/usb/work/debug/mozilla-unified/obj-x86_64-pc-linux-gnu/dist/include/nsIWidget.h:1326:13: note: in instantiation of member function 'mozilla::gfx::PointTyped<mozilla::LayoutDevicePixel>::PointTyped' requested here
0:10.76 mozilla::LayoutDevicePoint()));
0:10.76 ^
0:10.76 In file included from Unified_cpp_accessible_aom0.cpp:2:
0:10.76 In file included from /mnt/usb/work/debug/mozilla-unified/accessible/aom/AccessibleNode.cpp:11:
0:10.76 In file included from /mnt/usb/work/debug/mozilla-unified/obj-x86_64-pc-linux-gnu/dist/include/nsContentUtils.h:26:
0:10.76 In file included from /mnt/usb/work/debug/mozilla-unified/obj-x86_64-pc-linux-gnu/dist/include/Units.h:13:
0:10.76 /mnt/usb/work/debug/mozilla-unified/obj-x86_64-pc-linux-gnu/dist/include/mozilla/gfx/Point.h:84:5: error: static_assert failed due to requirement 'sizeof(mozilla::gfx::IntPointTyped<mozilla::LayoutDevicePixel>) == sizeof(int) * 2' "Would be unfortunate otherwise!"
0:10.76 static_assert(sizeof(IntPointTyped) == sizeof(int32_t) * 2,
0:10.76 ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0:10.76 /mnt/usb/work/debug/mozilla-unified/obj-x86_64-pc-linux-gnu/dist/include/mozilla/BasicEvents.h:519:3: note: in instantiation of member function 'mozilla::gfx::IntPointTyped<mozilla::LayoutDevicePixel>::IntPointTyped' requested here
0:10.76 WidgetEvent() : WidgetEventTime(), mPath(nullptr) {
0:10.76 ^
0:10.76 In file included from Unified_cpp_accessible_aom0.cpp:2:
0:10.76 In file included from /mnt/usb/work/debug/mozilla-unified/accessible/aom/AccessibleNode.cpp:11:
0:10.76 In file included from /mnt/usb/work/debug/mozilla-unified/obj-x86_64-pc-linux-gnu/dist/include/nsContentUtils.h:26:
0:10.76 In file included from /mnt/usb/work/debug/mozilla-unified/obj-x86_64-pc-linux-gnu/dist/include/Units.h:13:
0:10.76 /mnt/usb/work/debug/mozilla-unified/obj-x86_64-pc-linux-gnu/dist/include/mozilla/gfx/Point.h:137:5: error: static_assert failed due to requirement 'sizeof(mozilla::gfx::PointTyped<mozilla::gfx::UnknownUnits, float>) == sizeof(float) * 2' "Would be unfortunate otherwise!"
0:10.77 static_assert(sizeof(PointTyped) == sizeof(F) * 2,
0:10.77 ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Reporter | ||
Comment 10•2 years ago
|
||
Ok, so a couple of next steps:
- What are the actual numbers (sizes) in question (on the platform you're building on locally)?
- Does (for example)
IntCoordTyped<CSSPixel>
have a different size fromint32_t
, or is the discrepancy only introduced when putting two values together intoBasePoint
?
Finally, once we know which structure (BaseCoord or BasePoint) is at issue:
- Can we get the compiler to lay the structure out without increasing its size, for example using the packed attribute?
Assignee | ||
Comment 11•2 years ago
•
|
||
Doesn't look like an __attribute__ ((__packed__))
issue. Here's a crass simplification of the Mozilla code:
#include <cstdint>
#include <iostream>
template <class T, class Sub, class Coord = T>
struct BasePoint {
union {
struct {
Coord x, y;
} s;
Coord components[2] = {};
} u;
constexpr BasePoint() = default;
};
template <class T, class Sub>
struct BaseCoord {
T value;
};
template <class Units, class Rep>
struct IntCoordTyped
: public BaseCoord<Rep, IntCoordTyped<Units, Rep>>,
public Units {
};
template <class Units>
struct IntPointTyped
: public BasePoint<int32_t, IntPointTyped<Units>, IntCoordTyped<Units, int32_t> >,
public Units {
constexpr IntPointTyped() {
std::cout << "sizeof(IntPointTyped): " << sizeof(IntPointTyped)
<< "\nsizeof(int32_t) * 2: " << sizeof(int32_t) * 2
<< "\nsizeof(u.s.x): " << sizeof(this->u.s.x)
<< "\nsizeof(u.s): " << sizeof(this->u.s)
<< "\nsizeof(u.components): " << sizeof(this->u.components)
<< "\nsizeof(Units): " << sizeof(Units)
<< std::endl;
}
};
struct UnknownUnits {};
typedef IntPointTyped<UnknownUnits> IntPoint;
int main()
{
IntPoint ip;
}
This outputs:
sizeof(IntPointTyped): 12
sizeof(int32_t) * 2: 8
sizeof(u.s.x): 4
sizeof(u.s): 8
sizeof(u.components): 8
sizeof(Units): 1
The problem is that IntPointTyped
inherits from the empty base Units
, as does IntCoordTyped
, and the C++ standard apparently wants the compiler to have different addresses for the subobjects. The transformation from the extra 1 byte in size to 4 is just padding.
Here's the relevant part of the ISO C++ standard: "A base class subobject can be of zero size; however, two subobjects that have the same class type and that belong to the same most derived object cannot be allocated at the same address."
If either IntPointTyped
or IntCoordTyped
stops inheriting Units
, the problem goes away (in my test, at least).
Assignee | ||
Comment 12•2 years ago
|
||
Here's an even simpler and hopefully clearer example:
#include <iostream>
#include <cstdint>
using namespace std;
struct Base {
};
struct Derived1 : public Base {
int32_t member;
};
struct Derived2 : public Base {
Derived1 member1;
int32_t member2;
};
struct NotDerived {
Derived1 member1;
int32_t member2;
};
int main()
{
// We expect this to print 4 (sizeof(int32_t), and it does.
cout << "sizeof(Derived1): " << sizeof(Derived1) << endl;
// We might expect this to print 8 (2 * sizeof(int32_t),
// but it doesn't. Instead, it prints 12.
cout << "sizeof(Derived2): " << sizeof(Derived2) << endl;
// Finally, we expect this to print 8, and it does.
cout << "sizeof(NotDerived): " << sizeof(NotDerived) << endl;
}
Reporter | ||
Comment 13•2 years ago
•
|
||
Thanks for the investigation and reduced example.
(In reply to Razvan Cojocaru from comment #11)
Here's the relevant part of the ISO C++ standard: "A base class subobject can be of zero size; however, two subobjects that have the same class type and that belong to the same most derived object cannot be allocated at the same address."
I wouldn't have thought that the two Base
subobjects in Derived2
"belong to the same most derived object" (in my mind, the "most derived object" that they belong to are, respectively, the Derived2
object and the member1
subobject).
However, looking at the normative wording that this note is based on:
[...] Two objects with overlapping lifetimes that are not bit-fields may have the same address if one is nested within the other, or if at least one is a subobject of zero size and they are of different types; otherwise, they have distinct addresses and occupy disjoint bytes of storage.
this does more clearly suggest that the two Base
subobjects in this scenario are required to have distinct addresses.
So next, let's evaluate whether we need both PointTyped
and CoordTyped
to derive from Units
.
The reason PointTyped
derives from Units
is that the unit types define some static member functions like this one, and we like to be able to call these as e.g. CSSPoint::FromAppUnits()
rather than CSSPixel::FromAppUnits()
(because CSSPoint::FromAppUnits()
suggests the CSSPoint
return type more clearly).
We do have some methods that return Coord
types as well, however, in this case:
- Writing
CSSCoord::FromAppUnits()
instead ofCSSPixel::FromAppUnits()
doesn't make much of a difference for readability - Looking through the call sites, they already use the
CSSPixel::FromAppUnits()
form, with one exception
So, I think it would be reasonable to change CoordTyped
and IntCoordTyped
to stop inheriting from Units
(and change that one affected call site).
Assignee | ||
Comment 14•2 years ago
|
||
One last thing: BaseRect::ClampPoint uses .x
and .y
of the template type Point
, and is being used once with a CSSPoint
(for which we need the change to .x.value
and .y.value
), and once in ExtractRectFromOffset, where Point
is a nsPoint
, for which we need to continue to use .x
and .y
directly.
How would you prefer we handle this?
Reporter | ||
Comment 15•2 years ago
|
||
(In reply to Razvan Cojocaru from comment #14)
One last thing: BaseRect::ClampPoint uses
.x
and.y
of the template typePoint
, and is being used once with aCSSPoint
(for which we need the change to.x.value
and.y.value
), and once in ExtractRectFromOffset, wherePoint
is ansPoint
, for which we need to continue to use.x
and.y
directly.How would you prefer we handle this?
Maybe we can change the std::min
and std::max
calls in the implementation of ClampPoint
to calls to new functions CoordMin()
and CoordMax()
, which have both a generic T
overload (that dispatches to std::min
/std::max
), and a BaseCoord<T, Sub>
overload (that unwraps the .value
)? We could place this function in BaseCoord.h at namespace scope.
Reporter | ||
Comment 16•2 years ago
|
||
(In reply to Botond Ballo [:botond] from comment #15)
[...] which have both a generic
T
overload [...] and aBaseCoord<T, Sub>
overload [...]
Oh, I guess to make the overload resolution work out we may need overloads for CoordTyped
and IntCoordTyped
instead of BaseCoord
(in which case, we can place them in Coord.h).
Assignee | ||
Comment 17•2 years ago
|
||
(In reply to Botond Ballo [:botond] from comment #16)
(In reply to Botond Ballo [:botond] from comment #15)
[...] which have both a generic
T
overload [...] and aBaseCoord<T, Sub>
overload [...]Oh, I guess to make the overload resolution work out we may need overloads for
CoordTyped
andIntCoordTyped
instead ofBaseCoord
(in which case, we can place them in Coord.h).
Right. So you want to keep both uses (I was asking because I thought maybe you'd want to eliminate the nsPoint
case altogether).
I've actually done the following, which I thought was the smallest modification that satisfies your requirement:
diff --git a/gfx/2d/BaseRect.h b/gfx/2d/BaseRect.h
--- a/gfx/2d/BaseRect.h
+++ b/gfx/2d/BaseRect.h
@@ -673,8 +673,10 @@ struct BaseRect {
* edge of the rectangle.
*/
[[nodiscard]] Point ClampPoint(const Point& aPoint) const {
- return Point(std::max(x, std::min(XMost(), aPoint.x)),
- std::max(y, std::min(YMost(), aPoint.y)));
+ return Point(std::max(decltype(aPoint.x)(x),
+ std::min(decltype(aPoint.x)(XMost()), aPoint.x)),
+ std::max(decltype(aPoint.y)(y),
+ std::min(decltype(aPoint.y)(YMost()), aPoint.y)));
}
/**
Hope you're OK with it, otherwise I'll switch to specialization / overloading. Just thought we can do without further helper functions adding to the project line count.
Assignee | ||
Comment 18•2 years ago
|
||
[Int]CoordTyped no longer inherits Units because otherwise
instances of [Int]IntPointTyped may get one Base subobject because
it inherits Units, and others because of BasePoint's Coord members,
which end up increasing the [Int]CoordTyped's objects size (since
according to the ISO C++ standard, different Base subobject are
required to have different addresses).
Updated•2 years ago
|
Reporter | ||
Comment 19•2 years ago
|
||
(In reply to Razvan Cojocaru from comment #17)
I've actually done the following, which I thought was the smallest modification that satisfies your requirement:
diff --git a/gfx/2d/BaseRect.h b/gfx/2d/BaseRect.h --- a/gfx/2d/BaseRect.h +++ b/gfx/2d/BaseRect.h @@ -673,8 +673,10 @@ struct BaseRect { * edge of the rectangle. */ [[nodiscard]] Point ClampPoint(const Point& aPoint) const { - return Point(std::max(x, std::min(XMost(), aPoint.x)), - std::max(y, std::min(YMost(), aPoint.y))); + return Point(std::max(decltype(aPoint.x)(x), + std::min(decltype(aPoint.x)(XMost()), aPoint.x)), + std::max(decltype(aPoint.y)(y), + std::min(decltype(aPoint.y)(YMost()), aPoint.y))); } /**
Ah, I see, I misunderstood the problem. I thought the problem was that std::min
/std::max
did not work with Coord
parameters (they actually do, since Coord
overloads operator<
), but the actual problem is that calls to std::min
/std::max
with mixed Coord/primitive argument types are ambiguous. Avoiding mixing argument types seems fine to me.
Comment 20•2 years ago
|
||
Comment 21•2 years ago
|
||
bugherder |
Description
•