Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6448f87
meta: Record alignment of class in dictionary
pcanal Mar 21, 2026
8a958a9
meta: Add alignment info for emulated collections
pcanal Apr 1, 2026
e8578e0
io: Micro-optimization in alignment calculation.
pcanal Mar 25, 2026
cf07ebd
io: Factor out alignment operation
pcanal Mar 25, 2026
d67209c
meta: Use std::max_align_t instead of sizeof(void*)
pcanal Mar 30, 2026
cb6895b
io: Explicitly forbid collection with content aligned to more than 4096
pcanal Mar 28, 2026
b201943
meta: Add alignment info to TDataType
pcanal Apr 1, 2026
37552a5
meta: Remove over alignment of emulated class
pcanal Apr 1, 2026
e86acc1
meta: Alignment extend code doc
pcanal Apr 13, 2026
8ca7406
meta: Take emulation header in consideration for alignment value
pcanal Apr 13, 2026
472d169
meta: Factor out and standardize alignment assert.
pcanal Apr 13, 2026
89f286c
meta: Factor AlignUp function
pcanal Apr 13, 2026
d2e4abc
io: Remove TGenCollectionProxy::AlignedAllocator
pcanal Apr 13, 2026
1f83e57
meta: Extend alignment asserts
pcanal Apr 13, 2026
4eb45e7
io: Factor alignment calc for NewArray and DeleteArrray
pcanal Apr 13, 2026
b2ba7c0
Meta: Alignment calc for non-current StreamerInfo
pcanal Apr 14, 2026
4d1848f
io: Make missing alignment fatal
pcanal Apr 15, 2026
d5823cb
io: BuildOld set alignment for numerical types
pcanal Apr 15, 2026
ce838a6
io: Add comment on TStreamerInfo size and alignment for non-current T…
pcanal Apr 15, 2026
060db86
io: Set alignment for empty classes
pcanal Apr 15, 2026
6d586fd
meta: Implement TStreamerElement::GetAlignment.
pcanal Apr 15, 2026
6a33fa2
io: Add proper alignment for base classes
pcanal Apr 17, 2026
2061400
io: Clarify error cases (missing alignment)
pcanal Apr 17, 2026
d92a6a6
io: Add comment about matching new and delete for alignment
pcanal Apr 17, 2026
d0f4255
[meta] Change type of TDataType::fAlignOf to UInt_t
hahnjo Apr 21, 2026
ddb7512
io: Switch TDataType to TStreamerInfo based streamer
pcanal Apr 21, 2026
9756cf7
meta: prevent (unlikely) narrowing for alignment in TDataType
pcanal Apr 21, 2026
b44dc37
Release notes: add TClass alignment
pcanal Apr 22, 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
7 changes: 6 additions & 1 deletion README/ReleaseNotes/v636/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -703,4 +703,9 @@ The list of issues addressed for this release is the following:
* [[#21159](https://github.com/root-project/root/issues/21159)] - [RF] Unexpected result with RooFFTConvPDF after ROOT 6.32
* [[#16673](https://github.com/root-project/root/issues/16673)] - [RF] Different behaviour of multi-range fit in RooAddPdf and RooProdPdf

## HEAD of the v6-36-00-patches branch
## HEAD of the v6-36-00-patches branch

### Core
* Alignment of classes and numerical types is now recorded in the dictionary and propagated through `TClass`, `TDataType`, and `TStreamerElement`. `TStreamerInfo::BuildOld` uses this information to correctly lay out emulated classes and older-version StreamerInfos (for the input of I/O customization), ensuring proper alignment for over-aligned types.
Fixes [#21667](https://github.com/root-project/root/issues/21667).
(PR [#21669](https://github.com/root-project/root/pull/21669))
6 changes: 5 additions & 1 deletion core/clingutils/src/TClingUtils.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -1978,7 +1978,7 @@ 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 @@ -2040,6 +2040,10 @@ 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
2 changes: 1 addition & 1 deletion core/meta/inc/LinkDef.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
#pragma link C++ class TClassGenerator+;
#pragma link C++ class TDataMember-;
#pragma link C++ class TOptionListItem+;
#pragma link C++ class TDataType;
#pragma link C++ class TDataType+;
#pragma link C++ class TDictionary+;
#pragma link C++ class TEnumConstant+;
#pragma link C++ class TEnum+;
Expand Down
5 changes: 4 additions & 1 deletion core/meta/inc/TClass.h
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,8 @@ friend class TStreamerInfo;
ROOT::DirAutoAdd_t fDirAutoAdd; //pointer which implements the Directory Auto Add feature for this class.']'
ClassStreamerFunc_t fStreamerFunc; //Wrapper around this class custom Streamer member function.
ClassConvStreamerFunc_t fConvStreamerFunc; //Wrapper around this class custom conversion Streamer member function.
Int_t fSizeof; //Sizeof the class.
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; //!Indicates whether this class can be split or not. Values are -1, 0, 1, 2

Expand Down Expand Up @@ -308,6 +309,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 @@ -430,6 +432,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
4 changes: 4 additions & 0 deletions core/meta/inc/TDataType.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
//////////////////////////////////////////////////////////////////////////

#include "TDictionary.h"
#include <cstddef>


enum EDataType {
Expand All @@ -46,6 +47,7 @@ class TDataType : public TDictionary {
private:
TypedefInfo_t *fInfo; //!pointer to CINT typedef info
Int_t fSize; //size of type
UInt_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 +67,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
5 changes: 3 additions & 2 deletions core/meta/inc/TGenericClassInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ namespace ROOT {
ClassConvStreamerFunc_t fConvStreamerFunc;
TVirtualCollectionProxy *fCollectionProxy;
Int_t fSizeof;
std::size_t fAlignment;
Int_t fPragmaBits;
Detail::TCollectionProxyInfo *fCollectionProxyInfo;
Detail::TCollectionProxyInfo *fCollectionStreamerInfo;
Expand All @@ -78,13 +79,13 @@ namespace ROOT {
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);
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);
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 @@ -429,6 +429,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 @@ -143,6 +143,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 @@ -123,6 +123,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 @@ -2835,6 +2836,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 @@ -2843,6 +2851,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 @@ -5838,6 +5853,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());
}
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();
}

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

Expand Down
Loading
Loading