Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7f22e45
meta: Record alignment of class in dictionary
pcanal Mar 21, 2026
9f51def
meta: Add alignment info for emulated collections
pcanal Apr 1, 2026
d180eb2
io: Micro-optimization in alignment calculation.
pcanal Mar 25, 2026
02c6ed2
io: Factor out alignment operation
pcanal Mar 25, 2026
1fe2306
meta: Use std::max_align_t instead of sizeof(void*)
pcanal Mar 30, 2026
d9e3f1d
io: Explicitly forbid collection with content aligned to more than 4096
pcanal Mar 28, 2026
cd144c3
meta: Add alignment info to TDataType
pcanal Apr 1, 2026
6cde2b8
meta: Remove over alignment of emulated class
pcanal Apr 1, 2026
42bc7e9
meta: Alignment extend code doc
pcanal Apr 13, 2026
5aa8aa5
meta: Adjust reference file from better alignment
pcanal Apr 17, 2026
f5bbec6
meta: Take emulation header in consideration for alignment value
pcanal Apr 13, 2026
b3c24bc
meta: Factor out and standardize alignment assert.
pcanal Apr 13, 2026
b37eb3e
meta: Factor AlignUp function
pcanal Apr 13, 2026
70b4f71
io: Remove TGenCollectionProxy::AlignedAllocator
pcanal Apr 13, 2026
1fc7081
meta: Extend alignment asserts
pcanal Apr 13, 2026
25e6a29
io: Factor alignment calc for NewArray and DeleteArrray
pcanal Apr 13, 2026
5405baf
Meta: Alignment calc for non-current StreamerInfo
pcanal Apr 14, 2026
d624aed
io: Make missing alignment fatal
pcanal Apr 15, 2026
9c634b2
io: BuildOld set alignment for numerical types
pcanal Apr 15, 2026
68ad8eb
io: Add comment on TStreamerInfo size and alignment for non-current T…
pcanal Apr 15, 2026
30bb666
io: Set alignment for empty classes
pcanal Apr 15, 2026
4d9f534
meta: Implement TStreamerElement::GetAlignment.
pcanal Apr 15, 2026
d189cd6
io: Add proper alignment for base classes
pcanal Apr 17, 2026
75a372d
io: Clarify error cases (missing alignment)
pcanal Apr 17, 2026
12ceed0
io: Add comment about matching new and delete for alignment
pcanal Apr 17, 2026
4d54267
meta: Add test of alignment recording
pcanal Mar 23, 2026
556989c
NFC core/io: partial clang-formatting
pcanal Apr 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion core/clingutils/src/TClingUtils.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -1987,7 +1987,8 @@ void ROOT::TMetaUtils::WriteClassInit(std::ostream& finalString,
if (HasCustomStreamerMemberFunction(cl, decl, interp, normCtxt)) {
rootflag = rootflag | TClassTable__kHasCustomStreamerMember;
}
finalString << "isa_proxy, " << rootflag << "," << "\n" << " sizeof(" << csymbol << ") );" << "\n";
finalString << "isa_proxy, " << rootflag << "," << "\n"
<< " sizeof(" << csymbol << "), alignof(" << csymbol << ") );" << "\n";
if (HasIOConstructor(decl, args, ctorTypes, interp)) {
finalString << " instance.SetNew(&new_" << mappedname.c_str() << ");" << "\n";
if (args.size()==0 && NeedDestructor(decl, interp))
Expand Down Expand Up @@ -2049,6 +2050,11 @@ void ROOT::TMetaUtils::WriteClassInit(std::ostream& finalString,
// FIXME Workaround: for the moment we do not generate coll proxies with unique ptrs since
// they imply copies and therefore do not compile.
auto classNameForIO = TClassEdit::GetNameForIO(classname);

finalString << " static_assert(alignof(" << csymbol << "::value_type) <= 4096,\n";
finalString << " \"Class with alignment strictly greater than 4096 are currently not supported in "
"CollectionProxy. \"\n";
finalString << " \"Please report this case to the developers\");\n";
finalString << " instance.AdoptCollectionProxyInfo(TCollectionProxyInfo::Generate(TCollectionProxyInfo::" << methodTCP << "< " << classNameForIO.c_str() << " >()));" << "\n";

needCollectionProxy = true;
Expand Down
1 change: 1 addition & 0 deletions core/foundation/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ set_property(TARGET Core APPEND PROPERTY DICT_HEADERS
TClassEdit.h
TError.h
ThreadLocalStorage.h
ROOT/RAlignmentUtils.hxx
ROOT/RError.hxx
ROOT/RLogger.hxx
ROOT/RNotFn.hxx
Expand Down
41 changes: 41 additions & 0 deletions core/foundation/inc/ROOT/RAlignmentUtils.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// @(#)root/foundation:
// Author: Philippe Canal, April 2026

/*************************************************************************
* Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. *
* All rights reserved. *
* *
* For the licensing terms see $ROOTSYS/LICENSE. *
* For the list of contributors see $ROOTSYS/README/CREDITS. *
*************************************************************************/

#ifndef ROOT_RAlignmentUtils
#define ROOT_RAlignmentUtils

#include <cassert>
#include <cstddef>

namespace ROOT {
namespace Internal {

/// Return true if \p align is a valid C++ alignment value: strictly positive
/// and a power of two. This is the set of values accepted by
/// `::operator new[](n, std::align_val_t(align))`.
inline constexpr bool IsValidAlignment(std::size_t align) noexcept
{
return align > 0 && (align & (align - 1)) == 0;
}

/// Round \p value up to the next multiple of \p align.
/// \p align must be a power of two (asserted at runtime in debug builds).
template <typename T>
inline constexpr T AlignUp(T value, T align) noexcept
{
assert(IsValidAlignment(static_cast<std::size_t>(align))); // must be a power of two
return (value + align - 1) & ~(align - 1);
}

} // namespace Internal
} // namespace ROOT

#endif // ROOT_RAlignmentUtils
3 changes: 3 additions & 0 deletions core/meta/inc/TClass.h
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ friend class TStreamerInfo;
//Wrapper around this class custom conversion Streamer member function.
ClassConvStreamerFunc_t fConvStreamerFunc = nullptr;
Int_t fSizeof = -1; //Sizeof the class.
std::size_t fAlignment = 0; //Alignment of the class (0 for unknown alignment)

std::atomic<Char_t> fCanSplit = -1; ///<!Indicates whether this class can be split or not. Values are -1, 0, 1, 2

Expand Down Expand Up @@ -313,6 +314,7 @@ friend class TStreamerInfo;

void SetClassVersion(Version_t version);
void SetClassSize(Int_t sizof) { fSizeof = sizof; }
void SetClassAlignment(std::size_t align) { fAlignment = align; }
TVirtualStreamerInfo* DetermineCurrentStreamerInfo();

void SetStreamerImpl(Int_t streamerType);
Expand Down Expand Up @@ -435,6 +437,7 @@ friend class TStreamerInfo;
return fClassVersion;
}
Int_t GetClassSize() const { return Size(); }
size_t GetClassAlignment() const;
TDataMember *GetDataMember(const char *datamember) const;
Longptr_t GetDataMemberOffset(const char *membername) const;
const char *GetDeclFileName() const;
Expand Down
7 changes: 5 additions & 2 deletions core/meta/inc/TDataType.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
//////////////////////////////////////////////////////////////////////////

#include "TDictionary.h"

#include <cstddef>

enum EDataType {
kChar_t = 1, kUChar_t = 11, kShort_t = 2, kUShort_t = 12,
Expand All @@ -45,7 +45,8 @@ class TDataType : public TDictionary {

private:
TypedefInfo_t *fInfo; ///<!pointer to CINT typedef info
Int_t fSize; //size of type
Int_t fSize; // size of type
std::size_t fAlignOf; // alignment of type (0 if unknown)
EDataType fType; //type id
Long_t fProperty; //The property information for the (potential) underlying class
TString fTrueName; //Qualified name of the (potential) underlying class, e.g. "MyClass*const*"
Expand All @@ -65,6 +66,8 @@ class TDataType : public TDictionary {
TDataType(const char *typenam);
virtual ~TDataType();
Int_t Size() const;
/// Return the alignment of the type in bytes, or 0 if unknown.
std::size_t GetAlignOf() const { return fAlignOf; }
Int_t GetType() const { return (Int_t)fType; }
TString GetTypeName();
const char *GetFullTypeName() const;
Expand Down
17 changes: 7 additions & 10 deletions core/meta/inc/TGenericClassInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ namespace ROOT {
ClassConvStreamerFunc_t fConvStreamerFunc;
TVirtualCollectionProxy *fCollectionProxy;
Int_t fSizeof;
std::size_t fAlignment;
Int_t fPragmaBits;
std::string fRNTupleSoARecord;
Detail::TCollectionProxyInfo *fCollectionProxyInfo;
Expand All @@ -77,17 +78,13 @@ namespace ROOT {
std::unordered_map<std::string, TMemberStreamer *> fAdoptedMemberStreamers;

public:
TGenericClassInfo(const char *fullClassname,
const char *declFileName, Int_t declFileLine,
const std::type_info &info, const Internal::TInitBehavior *action,
DictFuncPtr_t dictionary,
TVirtualIsAProxy *isa, Int_t pragmabits, Int_t sizof);
TGenericClassInfo(const char *fullClassname, const char *declFileName, Int_t declFileLine,
const std::type_info &info, const Internal::TInitBehavior *action, DictFuncPtr_t dictionary,
TVirtualIsAProxy *isa, Int_t pragmabits, Int_t sizof, std::size_t alignof_ = 0);

TGenericClassInfo(const char *fullClassname, Int_t version,
const char *declFileName, Int_t declFileLine,
const std::type_info &info, const Internal::TInitBehavior *action,
DictFuncPtr_t dictionary,
TVirtualIsAProxy *isa, Int_t pragmabits, Int_t sizof);
TGenericClassInfo(const char *fullClassname, Int_t version, const char *declFileName, Int_t declFileLine,
const std::type_info &info, const Internal::TInitBehavior *action, DictFuncPtr_t dictionary,
TVirtualIsAProxy *isa, Int_t pragmabits, Int_t sizof, std::size_t alignof_ = 0);

TGenericClassInfo(const char *fullClassname, Int_t version,
const char *declFileName, Int_t declFileLine,
Expand Down
1 change: 1 addition & 0 deletions core/meta/inc/TInterpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,7 @@ class TInterpreter : public TNamed {
virtual void *ClassInfo_New(ClassInfo_t * /* info */, void * /* arena */) const {return nullptr;}
virtual Long_t ClassInfo_Property(ClassInfo_t * /* info */) const {return 0;}
virtual int ClassInfo_Size(ClassInfo_t * /* info */) const {return 0;}
virtual size_t ClassInfo_AlignOf(ClassInfo_t * /* info */) const {return 0;}
virtual Longptr_t ClassInfo_Tagnum(ClassInfo_t * /* info */) const {return 0;}
virtual const char *ClassInfo_FileName(ClassInfo_t * /* info */) const {return nullptr;}
virtual const char *ClassInfo_FullName(ClassInfo_t * /* info */) const {return nullptr;}
Expand Down
10 changes: 10 additions & 0 deletions core/meta/inc/TStreamerElement.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ class TStreamerElement : public TNamed {
Int_t GetArrayLength() const {return fArrayLength;}
virtual TClass *GetClassPointer() const;
TClass *GetClass() const {return GetClassPointer();}
virtual std::size_t GetAlignment() const;
virtual Int_t GetExecID() const;
virtual const char *GetFullName() const;
virtual const char *GetInclude() const {return "";}
Expand Down Expand Up @@ -172,6 +173,7 @@ class TStreamerBase : public TStreamerElement {
const char *GetInclude() const override;
TClass *GetNewBaseClass() { return fNewBaseClass; }
ULongptr_t GetMethod() const override {return 0;}
std::size_t GetAlignment() const override;
Int_t GetSize() const override;
TVirtualStreamerInfo *GetBaseStreamerInfo() const { return fStreamerInfo; }
void Init(TVirtualStreamerInfo *obj = nullptr) override;
Expand Down Expand Up @@ -213,6 +215,7 @@ class TStreamerBasicPointer : public TStreamerElement {
const char *GetCountName() const {return fCountName.Data();}
Int_t GetCountVersion() const {return fCountVersion;}
ULongptr_t GetMethod() const override;
std::size_t GetAlignment() const override { return alignof(void *); }
Int_t GetSize() const override;
void Init(TVirtualStreamerInfo *obj = nullptr) override;
Bool_t HasCounter() const override { return fCounter != nullptr; }
Expand Down Expand Up @@ -249,6 +252,7 @@ class TStreamerLoop : public TStreamerElement {
Int_t GetCountVersion() const {return fCountVersion;}
const char *GetInclude() const override;
ULongptr_t GetMethod() const override;
std::size_t GetAlignment() const override;
Int_t GetSize() const override;
void Init(TVirtualStreamerInfo *obj = nullptr) override;
Bool_t IsaPointer() const override {return kTRUE; }
Expand Down Expand Up @@ -297,6 +301,7 @@ class TStreamerObject : public TStreamerElement {
TStreamerObject(const char *name, const char *title, Int_t offset, const char *typeName);
virtual ~TStreamerObject();
const char *GetInclude() const override;
std::size_t GetAlignment() const override;
Int_t GetSize() const override;
void Init(TVirtualStreamerInfo *obj = nullptr) override;

Expand All @@ -316,6 +321,7 @@ class TStreamerObjectAny : public TStreamerElement {
TStreamerObjectAny(const char *name, const char *title, Int_t offset, const char *typeName);
virtual ~TStreamerObjectAny();
const char *GetInclude() const override;
std::size_t GetAlignment() const override;
Int_t GetSize() const override;
void Init(TVirtualStreamerInfo *obj = nullptr) override;

Expand All @@ -335,6 +341,7 @@ class TStreamerObjectPointer : public TStreamerElement {
TStreamerObjectPointer(const char *name, const char *title, Int_t offset, const char *typeName);
virtual ~TStreamerObjectPointer();
const char *GetInclude() const override;
std::size_t GetAlignment() const override { return alignof(void *); }
Int_t GetSize() const override;
void Init(TVirtualStreamerInfo *obj = nullptr) override;
Bool_t IsaPointer() const override {return kTRUE;}
Expand All @@ -356,6 +363,7 @@ class TStreamerObjectAnyPointer : public TStreamerElement {
TStreamerObjectAnyPointer(const char *name, const char *title, Int_t offset, const char *typeName);
virtual ~TStreamerObjectAnyPointer();
const char *GetInclude() const override;
std::size_t GetAlignment() const override { return alignof(void *); }
Int_t GetSize() const override;
void Init(TVirtualStreamerInfo *obj = nullptr) override;
Bool_t IsaPointer() const override { return kTRUE; }
Expand All @@ -377,6 +385,7 @@ class TStreamerString : public TStreamerElement {
TStreamerString(const char *name, const char *title, Int_t offset);
virtual ~TStreamerString();
const char *GetInclude() const override;
std::size_t GetAlignment() const override { return alignof(TString); }
Int_t GetSize() const override;

ClassDefOverride(TStreamerString,2) //Streamer element of type TString
Expand Down Expand Up @@ -408,6 +417,7 @@ class TStreamerSTL : public TStreamerElement {
Int_t GetSTLtype() const {return fSTLtype;}
Int_t GetCtype() const {return fCtype;}
const char *GetInclude() const override;
std::size_t GetAlignment() const override;
Int_t GetSize() const override;
void ls(Option_t *option="") const override;
void SetSTLtype(Int_t t) {fSTLtype = t;}
Expand Down
1 change: 1 addition & 0 deletions core/meta/inc/TVirtualStreamerInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ class TVirtualStreamerInfo : public TNamed {
TVirtualStreamerInfo();
TVirtualStreamerInfo(TClass * /*cl*/);
virtual ~TVirtualStreamerInfo();
virtual size_t GetClassAlignment() const = 0;
virtual void Build(Bool_t isTransient = kFALSE) = 0;
virtual void BuildCheck(TFile *file = nullptr, Bool_t load = kTRUE) = 0;
virtual void BuildEmulated(TFile *file) = 0;
Expand Down
47 changes: 47 additions & 0 deletions core/meta/src/TClass.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ In order to access the name of a class within the ROOT type system, the method T
#include "TClonesArray.h"
#include "TRef.h"
#include "TRefArray.h"
#include "ROOT/RAlignmentUtils.hxx"

using std::multimap, std::make_pair, std::string;

Expand Down Expand Up @@ -2735,6 +2736,13 @@ Int_t TClass::GetBaseClassOffsetRecurse(const TClass *cl)
if (!baseclass) return -1;
Int_t subOffset = baseclass->GetBaseClassOffsetRecurse(cl);
if (subOffset == -2) return -2;
auto align = baseclass->GetClassAlignment();
if (ROOT::Internal::IsValidAlignment(align)) {
offset = ROOT::Internal::AlignUp((size_t)offset, align);
} else {
Error("GetBaseClassOffsetRecurse", "Can not determine alignment for base class %s (got %zu)\n",
baseclass->GetName(), align);
}
if (subOffset != -1) return offset+subOffset;
offset += baseclass->Size();
} else if (element->IsA() == TStreamerSTL::Class()) {
Expand All @@ -2743,6 +2751,13 @@ Int_t TClass::GetBaseClassOffsetRecurse(const TClass *cl)
if (!baseclass) return -1;
Int_t subOffset = baseclass->GetBaseClassOffsetRecurse(cl);
if (subOffset == -2) return -2;
auto align = baseclass->GetClassAlignment();
if (ROOT::Internal::IsValidAlignment(align)) {
offset = ROOT::Internal::AlignUp((size_t)offset, align);
} else {
Error("GetBaseClassOffsetRecurse", "Can not determine alignment for base class %s (got %zu)\n",
baseclass->GetName(), align);
}
if (subOffset != -1) return offset+subOffset;
offset += baseclass->Size();

Expand Down Expand Up @@ -5753,6 +5768,38 @@ void TClass::SetCurrentStreamerInfo(TVirtualStreamerInfo *info)
fCurrentInfo = info;
}

////////////////////////////////////////////////////////////////////////////////
/// Return the alignment requirement (in bytes) for objects of this class.
///
/// Returns (size_t)-1 if the class info is invalid, 0 for a forward-declared
/// class, an enum, a namespace or or a class with no definition. For all other
/// cases the actual alignment obtained from the dictionary or the clang ASTRecordLayout,
/// or the StreamerInfo (in that order of priority) is returned.
///
/// Returns `0` when the alignment cannot be determined.

size_t TClass::GetClassAlignment() const
{
if (fAlignment != 0)
return fAlignment;
if ((fState < kEmulated && !fCollectionProxy) || Property() & (kIsNamespace | kIsEnum))
return 0;
if (HasInterpreterInfo()) {
return gCling->ClassInfo_AlignOf(GetClassInfo());
Comment thread
pcanal marked this conversation as resolved.
Comment thread
pcanal marked this conversation as resolved.
}
if (fCollectionProxy) {
// If the collection proxy has a dictionary, it will have return earlier,
// so we know that the collection proxy is emulated.
if (!(fCollectionProxy->GetProperties() & TVirtualCollectionProxy::kIsEmulated)) {
Fatal("TClass::GetClassAlignment", "Cannot determine alignment for collection proxy of class %s.", GetName());
return 0;
}
return alignof(std::vector<char>);
}
assert(GetStreamerInfo() && GetStreamerInfo()->GetClassAlignment() != 0);
return GetStreamerInfo()->GetClassAlignment();
Comment thread
pcanal marked this conversation as resolved.
}

////////////////////////////////////////////////////////////////////////////////
/// Return size of object of this class.

Expand Down
Loading
Loading