diff --git a/core/clingutils/src/TClingUtils.cxx b/core/clingutils/src/TClingUtils.cxx index acf3d65686be4..1b0651c80350e 100644 --- a/core/clingutils/src/TClingUtils.cxx +++ b/core/clingutils/src/TClingUtils.cxx @@ -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)) @@ -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; diff --git a/core/foundation/CMakeLists.txt b/core/foundation/CMakeLists.txt index c2d8b61a40fc6..320a5db3bb2bf 100644 --- a/core/foundation/CMakeLists.txt +++ b/core/foundation/CMakeLists.txt @@ -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 diff --git a/core/foundation/inc/ROOT/RAlignmentUtils.hxx b/core/foundation/inc/ROOT/RAlignmentUtils.hxx new file mode 100644 index 0000000000000..422958d338793 --- /dev/null +++ b/core/foundation/inc/ROOT/RAlignmentUtils.hxx @@ -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 +#include + +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 +inline constexpr T AlignUp(T value, T align) noexcept +{ + assert(IsValidAlignment(static_cast(align))); // must be a power of two + return (value + align - 1) & ~(align - 1); +} + +} // namespace Internal +} // namespace ROOT + +#endif // ROOT_RAlignmentUtils diff --git a/core/meta/inc/TClass.h b/core/meta/inc/TClass.h index d1e298cddcbe9..fec709736ea2b 100644 --- a/core/meta/inc/TClass.h +++ b/core/meta/inc/TClass.h @@ -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 fCanSplit = -1; /// enum EDataType { kChar_t = 1, kUChar_t = 11, kShort_t = 2, kUShort_t = 12, @@ -45,7 +45,8 @@ class TDataType : public TDictionary { private: TypedefInfo_t *fInfo; /// 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, diff --git a/core/meta/inc/TInterpreter.h b/core/meta/inc/TInterpreter.h index 94980fdb0a7fc..436406f72eaf1 100644 --- a/core/meta/inc/TInterpreter.h +++ b/core/meta/inc/TInterpreter.h @@ -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;} diff --git a/core/meta/inc/TStreamerElement.h b/core/meta/inc/TStreamerElement.h index 90363ecc9eec2..840ab8842b279 100644 --- a/core/meta/inc/TStreamerElement.h +++ b/core/meta/inc/TStreamerElement.h @@ -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 "";} @@ -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; @@ -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; } @@ -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; } @@ -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; @@ -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; @@ -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;} @@ -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; } @@ -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 @@ -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;} diff --git a/core/meta/inc/TVirtualStreamerInfo.h b/core/meta/inc/TVirtualStreamerInfo.h index 24144d85aecfa..9c227e39a0489 100644 --- a/core/meta/inc/TVirtualStreamerInfo.h +++ b/core/meta/inc/TVirtualStreamerInfo.h @@ -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; diff --git a/core/meta/src/TClass.cxx b/core/meta/src/TClass.cxx index 137f09b0bcd8c..6c6b13cbdae1d 100644 --- a/core/meta/src/TClass.cxx +++ b/core/meta/src/TClass.cxx @@ -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; @@ -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()) { @@ -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(); @@ -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()); + } + 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); + } + assert(GetStreamerInfo() && GetStreamerInfo()->GetClassAlignment() != 0); + return GetStreamerInfo()->GetClassAlignment(); +} + //////////////////////////////////////////////////////////////////////////////// /// Return size of object of this class. diff --git a/core/meta/src/TDataType.cxx b/core/meta/src/TDataType.cxx index 6215649b262f6..ab469b4f6b49c 100644 --- a/core/meta/src/TDataType.cxx +++ b/core/meta/src/TDataType.cxx @@ -41,6 +41,7 @@ TDataType::TDataType(TypedefInfo_t *info) : TDictionary(), R__LOCKGUARD(gInterpreterMutex); SetName(gCling->TypedefInfo_Name(fInfo)); SetTitle(gCling->TypedefInfo_Title(fInfo)); + // SetType sets fTrueName, fType , fSize and fAlignOf. SetType(gCling->TypedefInfo_TrueName(fInfo)); fProperty = gCling->TypedefInfo_Property(fInfo); fSize = gCling->TypedefInfo_Size(fInfo); @@ -48,6 +49,7 @@ TDataType::TDataType(TypedefInfo_t *info) : TDictionary(), SetTitle("Builtin basic type"); fProperty = 0; fSize = 0; + fAlignOf = 0; fType = kNoType_t; } } @@ -68,14 +70,16 @@ TDataType::TDataType(const char *typenam) : fInfo(nullptr), fProperty(kIsFundame //////////////////////////////////////////////////////////////////////////////// ///copy constructor -TDataType::TDataType(const TDataType& dt) : - TDictionary(dt), - fInfo(gCling->TypedefInfo_FactoryCopy(dt.fInfo)), - fSize(dt.fSize), - fType(dt.fType), - fProperty(dt.fProperty), - fTrueName(dt.fTrueName), - fTypeNameIdx(dt.fTypeNameIdx), fTypeNameLen(dt.fTypeNameLen) +TDataType::TDataType(const TDataType &dt) + : TDictionary(dt), + fInfo(gCling->TypedefInfo_FactoryCopy(dt.fInfo)), + fSize(dt.fSize), + fAlignOf(dt.fAlignOf), + fType(dt.fType), + fProperty(dt.fProperty), + fTrueName(dt.fTrueName), + fTypeNameIdx(dt.fTypeNameIdx), + fTypeNameLen(dt.fTypeNameLen) { } @@ -89,6 +93,7 @@ TDataType& TDataType::operator=(const TDataType& dt) gCling->TypedefInfo_Delete(fInfo); fInfo=gCling->TypedefInfo_FactoryCopy(dt.fInfo); fSize=dt.fSize; + fAlignOf=dt.fAlignOf; fType=dt.fType; fProperty=dt.fProperty; fTrueName=dt.fTrueName; @@ -301,69 +306,89 @@ void TDataType::SetType(const char *name) fTrueName = name; fType = kOther_t; fSize = 0; + fAlignOf = 0; if (name==nullptr) { return; } else if (!strcmp("unsigned int", name)) { fType = kUInt_t; fSize = sizeof(UInt_t); + fAlignOf = alignof(UInt_t); } else if (!strcmp("unsigned", name)) { fType = kUInt_t; fSize = sizeof(UInt_t); + fAlignOf = alignof(UInt_t); } else if (!strcmp("int", name)) { fType = kInt_t; fSize = sizeof(Int_t); + fAlignOf = alignof(Int_t); } else if (!strcmp("unsigned long", name)) { fType = kULong_t; fSize = sizeof(ULong_t); + fAlignOf = alignof(ULong_t); } else if (!strcmp("long", name)) { fType = kLong_t; fSize = sizeof(Long_t); + fAlignOf = alignof(Long_t); } else if (!strcmp("unsigned long long", name) || !strcmp("ULong64_t",name)) { fType = kULong64_t; fSize = sizeof(ULong64_t); + fAlignOf = alignof(ULong64_t); } else if (!strcmp("long long", name) || !strcmp("Long64_t",name)) { fType = kLong64_t; fSize = sizeof(Long64_t); + fAlignOf = alignof(Long64_t); } else if (!strcmp("unsigned short", name)) { fType = kUShort_t; fSize = sizeof(UShort_t); + fAlignOf = alignof(UShort_t); } else if (!strcmp("short", name)) { fType = kShort_t; fSize = sizeof(Short_t); + fAlignOf = alignof(Short_t); } else if (!strcmp("unsigned char", name)) { fType = kUChar_t; fSize = sizeof(UChar_t); + fAlignOf = alignof(UChar_t); } else if (!strcmp("char", name)) { fType = kChar_t; fSize = sizeof(Char_t); + fAlignOf = alignof(Char_t); } else if (!strcmp("bool", name)) { fType = kBool_t; fSize = sizeof(Bool_t); + fAlignOf = alignof(Bool_t); } else if (!strcmp("float", name)) { fType = kFloat_t; fSize = sizeof(Float_t); + fAlignOf = alignof(Float_t); } else if (!strcmp("double", name)) { fType = kDouble_t; fSize = sizeof(Double_t); + fAlignOf = alignof(Double_t); } else if (!strcmp("signed char", name)) { fType = kChar_t; // kDataTypeAliasSignedChar_t; fSize = sizeof(Char_t); + fAlignOf = alignof(Char_t); } else if (!strcmp("void", name)) { fType = kVoid_t; fSize = 0; + fAlignOf = 0; } if (!strcmp("Float16_t", fName.Data())) { fSize = sizeof(Float16_t); + fAlignOf = alignof(Float16_t); fType = kFloat16_t; } if (!strcmp("Double32_t", fName.Data())) { fSize = sizeof(Double32_t); + fAlignOf = alignof(Double32_t); fType = kDouble32_t; } if (!strcmp("char*",fName.Data())) { fType = kCharStar; + fAlignOf = alignof(char *); } // kCounter = 6, kBits = 15 } diff --git a/core/meta/src/TGenericClassInfo.cxx b/core/meta/src/TGenericClassInfo.cxx index adada20b23b3c..b0dd17db3fd4e 100644 --- a/core/meta/src/TGenericClassInfo.cxx +++ b/core/meta/src/TGenericClassInfo.cxx @@ -82,63 +82,116 @@ namespace Internal { } } // Internal +TGenericClassInfo::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_) + : fAction(action), + fClass(nullptr), + fClassName(fullClassname), + fDeclFileName(declFileName), + fDeclFileLine(declFileLine), + fDictionary(dictionary), + fInfo(info), + fImplFileName(nullptr), + fImplFileLine(0), + fIsA(isa), + fVersion(1), + fMerge(nullptr), + fResetAfterMerge(nullptr), + fNew(nullptr), + fNewArray(nullptr), + fDelete(nullptr), + fDeleteArray(nullptr), + fDestructor(nullptr), + fDirAutoAdd(nullptr), + fStreamer(nullptr), + fStreamerFunc(nullptr), + fConvStreamerFunc(nullptr), + fCollectionProxy(nullptr), + fSizeof(sizof), + fAlignment(alignof_), + fPragmaBits(pragmabits), + fCollectionProxyInfo(nullptr), + fCollectionStreamerInfo(nullptr) +{ + // Constructor. + + Init(pragmabits); +} - TGenericClassInfo::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) - : fAction(action), fClass(nullptr), fClassName(fullClassname), - fDeclFileName(declFileName), fDeclFileLine(declFileLine), - fDictionary(dictionary), fInfo(info), - fImplFileName(nullptr), fImplFileLine(0), - fIsA(isa), - fVersion(1), - fMerge(nullptr),fResetAfterMerge(nullptr),fNew(nullptr),fNewArray(nullptr),fDelete(nullptr),fDeleteArray(nullptr),fDestructor(nullptr), fDirAutoAdd(nullptr), fStreamer(nullptr), - fStreamerFunc(nullptr), fConvStreamerFunc(nullptr), fCollectionProxy(nullptr), fSizeof(sizof), fPragmaBits(pragmabits), - fCollectionProxyInfo(nullptr), fCollectionStreamerInfo(nullptr) - { - // Constructor. - - Init(pragmabits); - } - - TGenericClassInfo::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) - : fAction(action), fClass(nullptr), fClassName(fullClassname), - fDeclFileName(declFileName), fDeclFileLine(declFileLine), - fDictionary(dictionary), fInfo(info), - fImplFileName(nullptr), fImplFileLine(0), - fIsA(isa), - fVersion(version), - fMerge(nullptr),fResetAfterMerge(nullptr),fNew(nullptr),fNewArray(nullptr),fDelete(nullptr),fDeleteArray(nullptr),fDestructor(nullptr), fDirAutoAdd(nullptr), fStreamer(nullptr), - fStreamerFunc(nullptr), fConvStreamerFunc(nullptr), fCollectionProxy(nullptr), fSizeof(sizof), fPragmaBits(pragmabits), - fCollectionProxyInfo(nullptr), fCollectionStreamerInfo(nullptr) - - { - // Constructor with version number and no showmembers. - - Init(pragmabits); - } +TGenericClassInfo::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_) + : fAction(action), + fClass(nullptr), + fClassName(fullClassname), + fDeclFileName(declFileName), + fDeclFileLine(declFileLine), + fDictionary(dictionary), + fInfo(info), + fImplFileName(nullptr), + fImplFileLine(0), + fIsA(isa), + fVersion(version), + fMerge(nullptr), + fResetAfterMerge(nullptr), + fNew(nullptr), + fNewArray(nullptr), + fDelete(nullptr), + fDeleteArray(nullptr), + fDestructor(nullptr), + fDirAutoAdd(nullptr), + fStreamer(nullptr), + fStreamerFunc(nullptr), + fConvStreamerFunc(nullptr), + fCollectionProxy(nullptr), + fSizeof(sizof), + fAlignment(alignof_), + fPragmaBits(pragmabits), + fCollectionProxyInfo(nullptr), + fCollectionStreamerInfo(nullptr) + +{ + // Constructor with version number and no showmembers. + + Init(pragmabits); +} class TForNamespace {}; // Dummy class to give a typeid to namespace (See also TClassTable.cc) - TGenericClassInfo::TGenericClassInfo(const char *fullClassname, Int_t version, - const char *declFileName, Int_t declFileLine, - const Internal::TInitBehavior *action, + TGenericClassInfo::TGenericClassInfo(const char *fullClassname, Int_t version, const char *declFileName, + Int_t declFileLine, const Internal::TInitBehavior *action, DictFuncPtr_t dictionary, Int_t pragmabits) - : fAction(action), fClass(nullptr), fClassName(fullClassname), - fDeclFileName(declFileName), fDeclFileLine(declFileLine), - fDictionary(dictionary), fInfo(typeid(TForNamespace)), - fImplFileName(nullptr), fImplFileLine(0), + : fAction(action), + fClass(nullptr), + fClassName(fullClassname), + fDeclFileName(declFileName), + fDeclFileLine(declFileLine), + fDictionary(dictionary), + fInfo(typeid(TForNamespace)), + fImplFileName(nullptr), + fImplFileLine(0), fIsA(nullptr), fVersion(version), - fMerge(nullptr),fResetAfterMerge(nullptr),fNew(nullptr),fNewArray(nullptr),fDelete(nullptr),fDeleteArray(nullptr),fDestructor(nullptr), fDirAutoAdd(nullptr), fStreamer(nullptr), - fStreamerFunc(nullptr), fConvStreamerFunc(nullptr), fCollectionProxy(nullptr), fSizeof(0), fPragmaBits(pragmabits), - fCollectionProxyInfo(nullptr), fCollectionStreamerInfo(nullptr) + fMerge(nullptr), + fResetAfterMerge(nullptr), + fNew(nullptr), + fNewArray(nullptr), + fDelete(nullptr), + fDeleteArray(nullptr), + fDestructor(nullptr), + fDirAutoAdd(nullptr), + fStreamer(nullptr), + fStreamerFunc(nullptr), + fConvStreamerFunc(nullptr), + fCollectionProxy(nullptr), + fSizeof(0), + fAlignment(0), + fPragmaBits(pragmabits), + fCollectionProxyInfo(nullptr), + fCollectionStreamerInfo(nullptr) { // Constructor for namespace @@ -295,6 +348,7 @@ namespace Internal { } } fClass->SetClassSize(fSizeof); + fClass->SetClassAlignment(fAlignment); //--------------------------------------------------------------------- // Attach the schema evolution information diff --git a/core/meta/src/TStreamerElement.cxx b/core/meta/src/TStreamerElement.cxx index f4a60b19b08f6..305485e512748 100644 --- a/core/meta/src/TStreamerElement.cxx +++ b/core/meta/src/TStreamerElement.cxx @@ -20,6 +20,7 @@ #include "TBaseClass.h" #include "TDataMember.h" #include "TDataType.h" +#include "ROOT/RAlignmentUtils.hxx" #include "TEnum.h" #include "TRealData.h" #include "ThreadLocalStorage.h" @@ -389,6 +390,24 @@ Int_t TStreamerElement::GetSize() const return fSize; } +//////////////////////////////////////////////////////////////////////////////// +/// Returns the alignment of this element in bytes. +/// The bare underlying type (stripping kOffsetL / kOffsetP array/pointer markers) +/// is used to determine the alignment from TDataType. + +std::size_t TStreamerElement::GetAlignment() const +{ + // Strip kOffsetL / kOffsetP markers to recover the bare type id. + const EDataType bareType = (EDataType)(fType % TVirtualStreamerInfo::kOffsetL); + if (bareType == kCounter || bareType == kBits) + return sizeof(UInt_t); + if (auto *dt = TDataType::GetDataType(bareType); dt && dt->GetAlignOf()) + return dt->GetAlignOf(); + Error("TStreamerElement::GetAlignment", "Cannot determine alignment for type %d (bare type %d) for element %s", + fType, bareType, GetName()); + return alignof(std::max_align_t); +} + //////////////////////////////////////////////////////////////////////////////// /// Return the local streamer object. @@ -693,6 +712,21 @@ Int_t TStreamerBase::GetSize() const return 0; } +//////////////////////////////////////////////////////////////////////////////// +/// Returns the alignment of the base class in bytes. + +std::size_t TStreamerBase::GetAlignment() const +{ + TClass *cl = GetNewClass(); + if (!cl) + cl = GetClassPointer(); + if (cl && cl->GetClassAlignment()) + return cl->GetClassAlignment(); + // The caller will complains if the missing alignment information + // causes a problem. + return 0; +} + //////////////////////////////////////////////////////////////////////////////// /// Setup the element. @@ -1055,6 +1089,24 @@ TStreamerLoop::~TStreamerLoop() { } +//////////////////////////////////////////////////////////////////////////////// +/// Returns the alignment of this element in bytes. +/// The bare underlying type (stripping kOffsetL / kOffsetP array/pointer markers) +/// is used to determine the alignment from TDataType. + +std::size_t TStreamerLoop::GetAlignment() const +{ + // Strip kOffsetL / kOffsetP markers to recover the bare type id. + const EDataType bareType = (EDataType)(fType % TVirtualStreamerInfo::kOffsetL); + if (bareType == kCounter || bareType == kBits) + return sizeof(UInt_t); + if (auto *dt = TDataType::GetDataType(bareType); dt && dt->GetAlignOf()) + return dt->GetAlignOf(); + Error("TStreamerLoop::GetAlignment", "Cannot determine alignment for type %d (bare type %d) for element %s", fType, + bareType, GetName()); + return alignof(std::max_align_t); +} + //////////////////////////////////////////////////////////////////////////////// /// return address of counter @@ -1300,6 +1352,21 @@ Int_t TStreamerObject::GetSize() const return classSize; } +//////////////////////////////////////////////////////////////////////////////// +/// Returns the alignment of the object class in bytes. + +std::size_t TStreamerObject::GetAlignment() const +{ + TClass *cl = GetNewClass(); + if (!cl) + cl = GetClassPointer(); + if (cl && cl->GetClassAlignment()) + return cl->GetClassAlignment(); + // The caller will complains if the missing alignment information + // causes a problem. + return 0; +} + //////////////////////////////////////////////////////////////////////////////// /// Stream an object of class TStreamerObject. @@ -1396,6 +1463,21 @@ Int_t TStreamerObjectAny::GetSize() const return classSize; } +//////////////////////////////////////////////////////////////////////////////// +/// Returns the alignment of the object (non-TObject) class in bytes. + +std::size_t TStreamerObjectAny::GetAlignment() const +{ + TClass *cl = GetNewClass(); + if (!cl) + cl = GetClassPointer(); + if (cl && cl->GetClassAlignment()) + return cl->GetClassAlignment(); + // The caller will complains if the missing alignment information + // causes a problem. + return 0; +} + //////////////////////////////////////////////////////////////////////////////// /// Stream an object of class TStreamerObjectAny. @@ -1933,6 +2015,21 @@ Int_t TStreamerSTL::GetSize() const return size; } +//////////////////////////////////////////////////////////////////////////////// +/// Returns the alignment of the STL container in bytes. + +std::size_t TStreamerSTL::GetAlignment() const +{ + TClass *cl = GetNewClass(); + if (!cl) + cl = GetClassPointer(); + if (cl && cl->GetClassAlignment()) + return cl->GetClassAlignment(); + // The caller will complains if the missing alignment information + // causes a problem. + return 0; +} + //////////////////////////////////////////////////////////////////////////////// /// Print the content of the element. diff --git a/core/metacling/src/TCling.cxx b/core/metacling/src/TCling.cxx index 2184303c5fe51..d4ff40c5349e9 100644 --- a/core/metacling/src/TCling.cxx +++ b/core/metacling/src/TCling.cxx @@ -8260,6 +8260,14 @@ std::string TCling::CallFunc_GetWrapperCode(CallFunc_t *func) const // ClassInfo interface // +//////////////////////////////////////////////////////////////////////////////// + +size_t TCling::ClassInfo_AlignOf(ClassInfo_t *cinfo) const +{ + TClingClassInfo *TClinginfo = (TClingClassInfo *)cinfo; + return TClinginfo->GetAlignOf(); +} + //////////////////////////////////////////////////////////////////////////////// /// Return true if the entity pointed to by 'declid' is declared in /// the context described by 'info'. If info is null, look into the diff --git a/core/metacling/src/TCling.h b/core/metacling/src/TCling.h index 36b1bce8812e1..66e32f6502516 100644 --- a/core/metacling/src/TCling.h +++ b/core/metacling/src/TCling.h @@ -447,6 +447,7 @@ class TCling final : public TInterpreter { void* ClassInfo_New(ClassInfo_t* info, void* arena) const final; Long_t ClassInfo_Property(ClassInfo_t* info) const final; int ClassInfo_Size(ClassInfo_t* info) const final; + size_t ClassInfo_AlignOf(ClassInfo_t* info) const final; Longptr_t ClassInfo_Tagnum(ClassInfo_t* info) const final; const char* ClassInfo_FileName(ClassInfo_t* info) const final; const char* ClassInfo_FullName(ClassInfo_t* info) const final; diff --git a/core/metacling/src/TClingClassInfo.cxx b/core/metacling/src/TClingClassInfo.cxx index 005c17ffcaeaa..61403c9f5fe39 100644 --- a/core/metacling/src/TClingClassInfo.cxx +++ b/core/metacling/src/TClingClassInfo.cxx @@ -51,6 +51,8 @@ but the class metadata comes from the Clang C++ compiler, not CINT. #include "llvm/Support/Casting.h" #include "llvm/Support/raw_ostream.h" +#include "ROOT/RAlignmentUtils.hxx" + #include #include @@ -1317,6 +1319,12 @@ int TClingClassInfo::RootFlag() const return 0; } +/// Return the size of the class in bytes as reported by clang. +/// +/// Returns -1 if the class info is invalid, 0 for a forward-declared class, +/// an enum, or a class with no definition, and 1 for a namespace (a special +/// value inherited from CINT). For all other cases the actual byte size +/// obtained from the clang ASTRecordLayout is returned. int TClingClassInfo::Size() const { if (!IsValid()) { @@ -1355,6 +1363,46 @@ int TClingClassInfo::Size() const return clang_size; } +/// Return the alignment of the class in bytes as reported by clang. +/// +/// 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 clang ASTRecordLayout is +/// returned. +size_t TClingClassInfo::GetAlignOf() const +{ + if (!IsValid()) { + return -1; + } + if (!GetDecl()) { + // A forward declared class. + return 0; + } + + R__LOCKGUARD(gInterpreterMutex); + + Decl::Kind DK = GetDecl()->getKind(); + if (DK == Decl::Namespace) { + return 0; + } else if (DK == Decl::Enum) { + return 0; + } + const RecordDecl *RD = llvm::dyn_cast(GetDecl()); + if (!RD) { + return -1; + } + if (!RD->getDefinition()) { + // Forward-declared class. + return 0; + } + ASTContext &Context = GetDecl()->getASTContext(); + cling::Interpreter::PushTransactionRAII RAII(fInterp); + const ASTRecordLayout &Layout = Context.getASTRecordLayout(RD); + auto align = Layout.getAlignment().getQuantity(); + assert(ROOT::Internal::IsValidAlignment(align)); + return align; +} + Longptr_t TClingClassInfo::Tagnum() const { if (!IsValid()) { diff --git a/core/metacling/src/TClingClassInfo.h b/core/metacling/src/TClingClassInfo.h index c08fa1bdae1c9..a4a9e96e0d0f3 100644 --- a/core/metacling/src/TClingClassInfo.h +++ b/core/metacling/src/TClingClassInfo.h @@ -181,6 +181,7 @@ class TClingClassInfo final : public TClingDeclInfo { long Property() const; int RootFlag() const; int Size() const; + size_t GetAlignOf() const; Longptr_t Tagnum() const; const char *FileName(); void FullName(std::string &output, const ROOT::TMetaUtils::TNormalizedCtxt &normCtxt) const; diff --git a/io/io/inc/TEmulatedCollectionProxy.h b/io/io/inc/TEmulatedCollectionProxy.h index c179f81f46fdc..eae8404029eac 100644 --- a/io/io/inc/TEmulatedCollectionProxy.h +++ b/io/io/inc/TEmulatedCollectionProxy.h @@ -12,7 +12,9 @@ #define ROOT_TEmulatedCollectionProxy #include "TGenCollectionProxy.h" +#include "ROOT/RAlignmentUtils.hxx" +#include #include class TEmulatedCollectionProxy : public TGenCollectionProxy { @@ -21,10 +23,90 @@ class TEmulatedCollectionProxy : public TGenCollectionProxy { friend class TCollectionProxy; public: - // Container type definition - typedef std::vector Cont_t; - // Pointer to container type - typedef Cont_t *PCont_t; + /// Storage type whose alignment matches \a Align bytes. + /// Used to instantiate std::vector specializations with guaranteed buffer alignment. + template + struct alignas(Align) AlignedStorage { + char data[Align] = {}; + }; + + // Convenience vector aliases for each supported alignment. + using Cont1_t = std::vector>; + using Cont2_t = std::vector>; + using Cont4_t = std::vector>; + using Cont8_t = std::vector>; + using Cont16_t = std::vector>; + using Cont32_t = std::vector>; + using Cont64_t = std::vector>; + using Cont128_t = std::vector>; + using Cont256_t = std::vector>; + using Cont512_t = std::vector>; + using Cont1024_t = std::vector>; + using Cont2048_t = std::vector>; + using Cont4096_t = std::vector>; + + // Canonical container type (used for sizeof/typeid; actual alignment is + // selected at runtime via the alignment switch in each method). + using Cont_t = std::vector; + using PCont_t = Cont_t *; + + /// Invoke \a fn(typed_ptr, elemSize) where typed_ptr is the container + /// pointer cast to the correct AlignedStorage* for the value class + /// alignment. \a fn receives the element size (N) as a second argument + /// so it can convert byte counts to element counts. + template + void WithCont(void *obj, F &&fn) const + { + auto *vcl = GetValueClass(); + std::size_t align = alignof(std::max_align_t); + if (!fKey && (fVal->fCase & kIsPointer)) { + // If the collection contains pointers, we need to use the alignment of a pointer, not of the value class. + align = alignof(void*); + } else if (vcl) { + assert(ROOT::Internal::IsValidAlignment(vcl->GetClassAlignment())); + align = vcl->GetClassAlignment(); + } else { + switch( int(fVal->fKind) ) { + case kChar_t: + case kUChar_t: align = alignof(char); break; + case kShort_t: + case kUShort_t: align = alignof(short); break; + case kInt_t: + case kUInt_t: align = alignof(int); break; + case kLong_t: + case kULong_t: align = alignof(long); break; + case kLong64_t: + case kULong64_t:align = alignof(long long); break; + case kFloat16_t: + case kFloat_t: align = alignof(float); break; + case kDouble32_t: + case kDouble_t: align = alignof(double); break; + default: + Fatal("TEmulatedCollectionProxy::WithCont", "Unsupported value type %d for value class %s", fVal->fKind, + vcl ? vcl->GetName() : ""); + } + } + switch (align) { + // When adding new cases here, also update the static_assert in TClingUtils.cxx + // to explicitly allow the new alignment and to update the error message accordingly. + case 4096: fn(reinterpret_cast(obj), std::size_t(4096)); break; + case 2048: fn(reinterpret_cast(obj), std::size_t(2048)); break; + case 1024: fn(reinterpret_cast(obj), std::size_t(1024)); break; + case 512: fn(reinterpret_cast(obj), std::size_t( 512)); break; + case 256: fn(reinterpret_cast(obj), std::size_t( 256)); break; + case 128: fn(reinterpret_cast(obj), std::size_t( 128)); break; + case 64: fn(reinterpret_cast(obj), std::size_t( 64)); break; + case 32: fn(reinterpret_cast(obj), std::size_t( 32)); break; + case 16: fn(reinterpret_cast(obj), std::size_t( 16)); break; + case 8: fn(reinterpret_cast(obj), std::size_t( 8)); break; + case 4: fn(reinterpret_cast(obj), std::size_t( 4)); break; + case 2: fn(reinterpret_cast(obj), std::size_t( 2)); break; + case 1: fn(reinterpret_cast(obj), std::size_t( 1)); break; + default: + Fatal("TEmulatedCollectionProxy::WithCont", "Unsupported alignment %zu for value class %s", + align, vcl ? vcl->GetName() : ""); + } + } protected: // Some hack to avoid const-ness @@ -59,28 +141,53 @@ class TEmulatedCollectionProxy : public TGenCollectionProxy { ~TEmulatedCollectionProxy() override; // Virtual constructor - void* New() const override { return new Cont_t; } + void *New() const override + { + void *mem = ::operator new(sizeof(Cont_t)); + WithCont(mem, [](auto *c, std::size_t) { new (c) std::decay_t(); }); + return mem; + } // Virtual in-place constructor - void* New(void* memory) const override { return new(memory) Cont_t; } + void *New(void *memory) const override + { + WithCont(memory, [](auto *c, std::size_t) { new (c) std::decay_t(); }); + return memory; + } // Virtual constructor - TClass::ObjectPtr NewObject() const override { return {new Cont_t, nullptr}; } + TClass::ObjectPtr NewObject() const override { return {New(), nullptr}; } // Virtual in-place constructor - TClass::ObjectPtr NewObject(void* memory) const override { return {new(memory) Cont_t, nullptr}; } + TClass::ObjectPtr NewObject(void *memory) const override { return {New(memory), nullptr}; } // Virtual array constructor - void* NewArray(Int_t nElements) const override { return new Cont_t[nElements]; } + void *NewArray(Int_t nElements) const override + { + void *arr = ::operator new(nElements * sizeof(Cont_t)); + for (Int_t i = 0; i < nElements; ++i) + WithCont(static_cast(arr) + i * sizeof(Cont_t), + [](auto *c, std::size_t) { new (c) std::decay_t(); }); + return arr; + } - // Virtual in-place constructor - void* NewArray(Int_t nElements, void* memory) const override { return new(memory) Cont_t[nElements]; } + // Virtual in-place array constructor + void *NewArray(Int_t nElements, void *memory) const override + { + for (Int_t i = 0; i < nElements; ++i) + WithCont(static_cast(memory) + i * sizeof(Cont_t), + [](auto *c, std::size_t) { new (c) std::decay_t(); }); + return memory; + } // Virtual array constructor - TClass::ObjectPtr NewObjectArray(Int_t nElements) const override { return {new Cont_t[nElements], nullptr}; } + TClass::ObjectPtr NewObjectArray(Int_t nElements) const override { return {NewArray(nElements), nullptr}; } - // Virtual in-place constructor - TClass::ObjectPtr NewObjectArray(Int_t nElements, void* memory) const override { return {new(memory) Cont_t[nElements], nullptr}; } + // Virtual in-place array constructor + TClass::ObjectPtr NewObjectArray(Int_t nElements, void *memory) const override + { + return {NewArray(nElements, memory), nullptr}; + } // Virtual destructor void Destructor(void* p, Bool_t dtorOnly = kFALSE) const override; diff --git a/io/io/inc/TGenCollectionProxy.h b/io/io/inc/TGenCollectionProxy.h index 310f8a1020bb0..da0cced70c750 100644 --- a/io/io/inc/TGenCollectionProxy.h +++ b/io/io/inc/TGenCollectionProxy.h @@ -17,11 +17,15 @@ #include "TCollectionProxyInfo.h" +#include "ROOT/RAlignmentUtils.hxx" + #include #include #include #include +#include #include +#include class TObjArray; class TCollectionProxyFactory; diff --git a/io/io/inc/TStreamerInfo.h b/io/io/inc/TStreamerInfo.h index a604a3cc196ea..ffe008fd308b0 100644 --- a/io/io/inc/TStreamerInfo.h +++ b/io/io/inc/TStreamerInfo.h @@ -90,6 +90,7 @@ class TStreamerInfo : public TVirtualStreamerInfo { Int_t fOnFileClassVersion;///), sizeof(std::vector::iterator)) +TEmulatedCollectionProxy::TEmulatedCollectionProxy(const char *cl_name, Bool_t silent) + : TGenCollectionProxy(typeid(Cont_t), sizeof(Cont_t::iterator)) { // Build a Streamer for a collection whose type is described by 'collectionClass'. @@ -88,9 +88,12 @@ void TEmulatedCollectionProxy::Destructor(void* p, Bool_t dtorOnly) const const_cast(this)->Clear("force"); } if (dtorOnly) { - ((Cont_t*)p)->~Cont_t(); + WithCont(p, [](auto *c, std::size_t) { + using Vec_t = std::decay_t; + c->~Vec_t(); + }); } else { - delete (Cont_t*) p; + WithCont(p, [](auto *c, std::size_t) { delete c; }); } } @@ -102,7 +105,7 @@ void TEmulatedCollectionProxy::DeleteArray(void* p, Bool_t dtorOnly) const // how many elements are in the array. Warning("DeleteArray", "Cannot properly delete emulated array of %s at %p, I don't know how many elements it has!", fClass->GetName(), p); if (!dtorOnly) { - delete[] (Cont_t*) p; + ::operator delete(p); } } @@ -133,13 +136,11 @@ TGenCollectionProxy *TEmulatedCollectionProxy::InitializeEx(Bool_t silent) // Note: an emulated collection proxy is never really associative // since under-neath is actually an array. - // std::cout << "Initialized " << typeid(*this).name() << ":" << fName << std::endl; - auto alignedSize = [](size_t in) { - constexpr size_t kSizeOfPtr = sizeof(void*); - return in + (kSizeOfPtr - in%kSizeOfPtr)%kSizeOfPtr; + auto alignedSize = [](size_t in, TClass *align_cl) { + size_t align = align_cl ? align_cl->GetClassAlignment() : alignof(std::max_align_t); + return in + (align - in % align) % align; }; - struct GenerateTemporaryTEnum - { + struct GenerateTemporaryTEnum { TEnum *fTemporaryTEnum = nullptr; GenerateTemporaryTEnum(UInt_t typecase, const std::string &enumname) @@ -195,10 +196,10 @@ TGenCollectionProxy *TEmulatedCollectionProxy::InitializeEx(Bool_t silent) fProperties |= kNeedDelete; } if ( 0 == fValOffset ) { - fValOffset = alignedSize(fKey->fSize); + fValOffset = alignedSize(fKey->fSize, (*fValue).fType.GetClass()); } if ( 0 == fValDiff ) { - fValDiff = alignedSize(fValOffset + fVal->fSize); + fValDiff = alignedSize(fValOffset + fVal->fSize, (*fValue).fType.GetClass()); } if (num > 3 && !inside[3].empty()) { if (! TClassEdit::IsDefAlloc(inside[3].c_str(),inside[0].c_str())) { @@ -267,8 +268,7 @@ void TEmulatedCollectionProxy::Shrink(UInt_t nCurr, UInt_t left, Bool_t force ) { // Shrink the container - typedef std::string String_t; - PCont_t c = PCont_t(fEnv->fObject); + typedef std::string String_t; char* addr = ((char*)fEnv->fStart) + fValDiff*left; size_t i; @@ -363,8 +363,11 @@ void TEmulatedCollectionProxy::Shrink(UInt_t nCurr, UInt_t left, Bool_t force ) break; } } - c->resize(left*fValDiff,0); - fEnv->fStart = left > 0 ? c->data() : 0; + WithCont(fEnv->fObject, [&](auto *c, std::size_t alignmentElemSize) { + assert(fValDiff % alignmentElemSize == 0); + c->resize(left * fValDiff / alignmentElemSize); + fEnv->fStart = left > 0 ? c->data() : nullptr; + }); return; } @@ -372,10 +375,12 @@ void TEmulatedCollectionProxy::Expand(UInt_t nCurr, UInt_t left) { // Expand the container size_t i; - PCont_t c = PCont_t(fEnv->fObject); - c->resize(left*fValDiff,0); void *oldstart = fEnv->fStart; - fEnv->fStart = left > 0 ? c->data() : 0; + WithCont(fEnv->fObject, [&](auto *c, std::size_t alignmentElemSize) { + assert(fValDiff % alignmentElemSize == 0); + c->resize(left * fValDiff / alignmentElemSize); + fEnv->fStart = left > 0 ? c->data() : nullptr; + }); char* addr = ((char*)fEnv->fStart) + fValDiff*nCurr; switch ( fSTL_type ) { diff --git a/io/io/src/TGenCollectionProxy.cxx b/io/io/src/TGenCollectionProxy.cxx index 3defdece6e7fd..bae962d6e7c95 100644 --- a/io/io/src/TGenCollectionProxy.cxx +++ b/io/io/src/TGenCollectionProxy.cxx @@ -1519,8 +1519,7 @@ void TGenCollectionProxy__VectorCreateIterators(void *obj, void **begin_arena, v return; } *begin_arena = vec->data(); - *end_arena = vec->data() + vec->size(); - + *end_arena = vec->data() + vec->size(); } //////////////////////////////////////////////////////////////////////////////// @@ -1624,7 +1623,9 @@ TVirtualCollectionProxy::CreateIterators_t TGenCollectionProxy::GetFunctionCreat // fprintf(stderr,"a generic iterator\n"); // TODO could we do better than SlowCreateIterators for RVec? - if (fSTL_type==ROOT::kSTLvector || (fProperties & kIsEmulated)) + if (fProperties & kIsEmulated) + return fFunctionCreateIterators = TGenCollectionProxy__VectorCreateIterators; + else if (fSTL_type == ROOT::kSTLvector) return fFunctionCreateIterators = TGenCollectionProxy__VectorCreateIterators; else if ( (fProperties & kIsAssociative) && read) return TGenCollectionProxy__StagingCreateIterators; diff --git a/io/io/src/TStreamerInfo.cxx b/io/io/src/TStreamerInfo.cxx index 3900b54b37cf8..0b23b352d9d28 100644 --- a/io/io/src/TStreamerInfo.cxx +++ b/io/io/src/TStreamerInfo.cxx @@ -74,9 +74,12 @@ element type. #include "TVirtualMutex.h" #include "TStreamerInfoActions.h" +#include "ROOT/RAlignmentUtils.hxx" #include +#include #include +#include std::atomic TStreamerInfo::fgCount{0}; @@ -154,6 +157,7 @@ TStreamerInfo::TStreamerInfo() fNfulldata= 0; fNslots = 0; fSize = 0; + fAlignment= 0; fClassVersion = 0; fOnFileClassVersion = 0; fOldVersion = Class()->GetClassVersion(); @@ -188,6 +192,7 @@ TStreamerInfo::TStreamerInfo(TClass *cl) fNfulldata= 0; fNslots = 0; fSize = 0; + fAlignment= 0; fClassVersion = fClass->GetClassVersion(); fOnFileClassVersion = 0; fOldVersion = Class()->GetClassVersion(); @@ -240,6 +245,28 @@ TStreamerInfo::~TStreamerInfo() /// Makes sure kBuildRunning reset once Build finishes. namespace { + using ROOT::Internal::AlignUp; + + /// Layout of the cookie header that NewArray writes before the data and + /// DeleteArray reads back. Both functions must use exactly this struct to + /// stay in sync; + /// The header holds two Long_t cookie values (size and nElements). + /// Round the header size up to the next multiple of 'align' so that + /// dataBegin (= p + headerSize) is itself aligned to 'align'. + struct ArrayCookieLayout { + std::size_t align; ///< Alignment of the class (and of the whole block). + std::size_t cookieSize; ///< Raw size of the two Long_t cookie fields. + std::size_t headerSize; ///< Padded header size (cookieSize rounded up to align). + + explicit ArrayCookieLayout(const TClass *cl) + : align(std::max(cl->GetClassAlignment(), alignof(Long_t))), + cookieSize(sizeof(Long_t) * 2), + headerSize(AlignUp(cookieSize, align)) + { + assert(ROOT::Internal::IsValidAlignment(align)); + } + }; + struct TPreventRecursiveBuildGuard { TPreventRecursiveBuildGuard(TStreamerInfo* info): fInfo(info) { fInfo->SetBit(TStreamerInfo::kBuildRunning); @@ -503,6 +530,11 @@ void TStreamerInfo::Build(Bool_t isTransient) } if (element) { fElements->Add(element); + // We are building the TStreamerInfo for the in-memory representation + // of a loaded class, so we don't need to cross check the element's + // alignment (fAlignment will eventually be set to the one provided + // to the dictionary). + fAlignment = std::max(fAlignment, element->GetAlignment()); } } // end of base class loop } @@ -800,6 +832,11 @@ void TStreamerInfo::Build(Bool_t isTransient) } fElements->Add(element); + // We are building the TStreamerInfo for the in-memory representation + // of a loaded class, so we don't need to cross check the element's + // alignment (fAlignment will eventually be set to the one provided + // to the dictionary). + fAlignment = std::max(fAlignment, element->GetAlignment()); } // end of member loop // Now add artificial TStreamerElement (i.e. rules that creates new members or set transient members). @@ -1912,8 +1949,6 @@ void TStreamerInfo::BuildOld() Int_t offset = 0; TMemberStreamer* streamer = 0; - constexpr size_t kSizeOfPtr = sizeof(void*); - int nBaze = 0; if ((fElements->GetEntriesFast() == 1) && !strcmp(fElements->At(0)->GetName(), "This")) { @@ -2026,6 +2061,7 @@ void TStreamerInfo::BuildOld() // Calculate the offset using the 'real' base class name (as opposed to the // '@@emulated' in the case of the emulation of an abstract base class. + // baseOffset already take alignment in consideration. Int_t baseOffset = fClass->GetBaseClassOffset(baseclass); // Deal with potential schema evolution (renaming) of the base class. @@ -2090,14 +2126,24 @@ void TStreamerInfo::BuildOld() fNVirtualInfoLoc += infobase->fNVirtualInfoLoc; } - - { - if (baseOffset < 0) { - element->SetNewType(TVirtualStreamerInfo::kNoType); + if (baseOffset < 0) { + element->SetNewType(TVirtualStreamerInfo::kNoType); + } else { + auto align = baseclass->GetClassAlignment(); + if (!ROOT::Internal::IsValidAlignment(align)) { + // Print error only if this is meant to be the 'current' + // class layout description. + if (fClass->GetClassVersion() == fClassVersion) { + Error("TStreamerInfo::BuildOld", "Cannot determine alignment for base class %s for element %s", + baseclass->GetName(), GetName()); + } + align = alignof(std::max_align_t); } + fAlignment = std::max(fAlignment, align); } element->SetOffset(baseOffset); - offset += baseclass->Size(); + // We might be treating the base classes out of memory order. + offset = std::max(offset, baseOffset + baseclass->Size()); continue; } else { @@ -2182,8 +2228,22 @@ void TStreamerInfo::BuildOld() element->SetNewType(TVirtualStreamerInfo::kNoType); continue; } + + auto align = element->GetAlignment(); + if (!ROOT::Internal::IsValidAlignment(align)) { + // Print error only if this is meant to be the 'current' + // class layout description. + if (fClass->GetClassVersion() == fClassVersion) { + Error("TStreamerInfo::BuildOld", "Cannot determine alignment for base class %s for element %s", + element->GetClassPointer()->GetName(), GetName()); + } + align = alignof(std::max_align_t); + } + fAlignment = std::max(fAlignment, align); + element->SetOffset(baseOffset); - offset += asize; + // We might be treating the base classes out of memory order. + offset = std::max(offset, baseOffset + asize); element->Init(this); continue; } // if element is of type TStreamerBase or not. @@ -2195,6 +2255,7 @@ void TStreamerInfo::BuildOld() fVirtualInfoLoc = new ULong_t[1]; // To allow for a single delete statement. fVirtualInfoLoc[0] = offset; offset += sizeof(TStreamerInfo*); + fAlignment = std::max(fAlignment, alignof(TStreamerInfo *)); } TDataMember* dm = 0; @@ -2311,6 +2372,20 @@ void TStreamerInfo::BuildOld() } } // Class corresponding to StreamerInfo is emulated or not. + { + auto align = element->GetAlignment(); + if (!ROOT::Internal::IsValidAlignment(align)) { + // Print error only if this is meant to be the 'current' + // class layout description. + if (fClass->GetClassVersion() == fClassVersion) { + Error("TStreamerInfo::BuildOld", "Cannot determine alignment for type %s for element %s", + element->GetTypeName(), GetName()); + } + align = alignof(std::max_align_t); + } + fAlignment = std::max(fAlignment, align); + } + // Now let's deal with Schema evolution Int_t newType = -1; TClassRef newClass; @@ -2640,12 +2715,15 @@ void TStreamerInfo::BuildOld() // Regular case asize = element->GetSize(); } - // align the non-basic data types (required on alpha and IRIX!!) - if ((offset % kSizeOfPtr) != 0) { - offset = offset - (offset % kSizeOfPtr) + kSizeOfPtr; - } + // Use the precise alignment of the element type, falling back to + // max_align_t for types whose alignment is not known. + // (e.g. forward declared classes, or classes with incomplete types info, etc..) + const std::size_t align = element->GetAlignment(); + assert(ROOT::Internal::IsValidAlignment(align)); + offset = (Int_t)AlignUp((std::size_t)offset, align); element->SetOffset(offset); offset += asize; + fAlignment = std::max(fAlignment, align); } if (!wasCompiled && rules) { @@ -2744,6 +2822,7 @@ void TStreamerInfo::BuildOld() // If we get here, this means that there no data member after the last base class // (or no base class at all). if (shouldHaveInfoLoc && fNVirtualInfoLoc==0) { + offset = (Int_t)AlignUp((size_t)offset, alignof(TStreamerInfo *)); fNVirtualInfoLoc = 1; fVirtualInfoLoc = new ULong_t[1]; // To allow for a single delete statement. fVirtualInfoLoc[0] = offset; @@ -3314,10 +3393,18 @@ void TStreamerInfo::ComputeSize() if (this == fClass->GetCurrentStreamerInfo()) { if (fClass->GetState() >= TClass::kInterpreted || fClass->fIsSyntheticPair) { fSize = fClass->GetClassSize(); + fAlignment = fClass->GetClassAlignment(); return; } } + // Note for TStreamerInfo that do not represent the current in memory + // layout of the class (whether that layout is the compiled, interpreted + // or emulated), the size calculated below is fantasy since the last element + // of this StreamerInfo may or may not be the last one in the in-memory layout. + // However this is per se a non issue as this size will not be used for + // anything. + TStreamerElement *element = (TStreamerElement*)fElements->Last(); //faster and more precise to use last element offset +size //on 64 bit machines, offset may be forced to be a multiple of 8 bytes @@ -3326,12 +3413,23 @@ void TStreamerInfo::ComputeSize() fSize = fVirtualInfoLoc[0] + sizeof(TStreamerInfo*); } - // On some platform and in some case of layout non-basic data types needs - // to be aligned. So let's be on the safe side and align on the size of - // the pointers. (Question: is that the right thing on x32 ABI ?) - constexpr size_t kSizeOfPtr = sizeof(void*); - if ((fSize % kSizeOfPtr) != 0 && !fClass->IsSyntheticPair()) { - fSize = fSize - (fSize % kSizeOfPtr) + kSizeOfPtr; + if (fClass->GetCollectionProxy()) { + fAlignment = fClass->GetClassAlignment(); + } + + if (!fAlignment && fElements->IsEmpty()) { + // Empty class alignment is 1. + fAlignment = 1; + } + + // If we have no information use the default alignment. + if (!fAlignment) { + Error("ComputeSize", "No information on the alignment of class %s, using default alignment of %d bytes", + GetName(), (Int_t)alignof(std::max_align_t)); + fAlignment = alignof(std::max_align_t); + } + if ((fSize % fAlignment) != 0) { + fSize = (Int_t)AlignUp((size_t)fSize, fAlignment); } } @@ -4991,8 +5089,13 @@ void* TStreamerInfo::New(void *obj) TIter next(fElements); if (!p) { - // Allocate and initialize the memory block. - p = new char[fSize]; + // Allocate and initialize the memory block. Ensure the returned + // storage is aligned to the class alignment requirement. + auto align = fClass->GetClassAlignment(); + // Use aligned new (C++17). This will return memory aligned to + // 'align' and can be freed with the matching delete[]. + assert(ROOT::Internal::IsValidAlignment(align)); // align must be a power of 2 + p = static_cast(::operator new[](fSize, std::align_val_t(align))); memset(p, 0, fSize); } @@ -5145,23 +5248,30 @@ void* TStreamerInfo::NewArray(Long_t nElements, void *ary) char* p = (char*) ary; + // Determine the alignment requirement for the class. + const ArrayCookieLayout layout(fClass); + if (!p) { - Long_t len = nElements * size + sizeof(Long_t)*2; - p = new char[len]; + + Long_t len = nElements * size + layout.headerSize; + + // Allocate and initialize the memory block. Request alignment so + // that the raw block starts on an 'align'-boundary; combined with + // the rounded-up header this guarantees dataBegin is also aligned. + p = static_cast(::operator new[](len, std::align_val_t(layout.align))); memset(p, 0, len); } - // Store the array cookie - Long_t* r = (Long_t*) p; + Long_t* r = (Long_t*)(p + layout.headerSize - layout.cookieSize); r[0] = size; r[1] = nElements; - char* dataBegin = (char*) &r[2]; + char *dataBegin = p + layout.headerSize; // Do a placement new for each element. - p = dataBegin; + char *q = dataBegin; for (Long_t cnt = 0; cnt < nElements; ++cnt) { - New(p); - p += size; + New(q); + q += size; } // for nElements return dataBegin; @@ -5306,7 +5416,14 @@ void TStreamerInfo::DestructorImpl(void* obj, Bool_t dtorOnly) } // iter over elements if (!dtorOnly) { - delete[] p; + // Note: We rely on fClass->GetClassAlignment() being the same than we had + // on construction time. + // However it technically can change but there is no much we can sensibly do to detect it. eg. "allocate object, + // unload library, reload library, destruct object" (but in this case the 'unload' library is meant to/should also + // destruct the object (unload transaction) but this is not always possible) or "allocate emulated object, load + // library, destruct object". + assert(ROOT::Internal::IsValidAlignment(fClass->GetClassAlignment())); + ::operator delete[](p, std::align_val_t(fClass->GetClassAlignment())); } } @@ -5350,10 +5467,15 @@ void TStreamerInfo::DeleteArray(void* ary, Bool_t dtorOnly) //???FIX ME: What about varying length arrays? - Long_t* r = (Long_t*) ary; - Long_t arrayLen = r[-1]; - Long_t size = r[-2]; - char* memBegin = (char*) &r[-2]; + // Recover the cookie layout: the two Long_t values sit in the header + // block immediately before dataBegin, with the same alignment-based + // headerSize that NewArray used. + const ArrayCookieLayout layout(fClass); + + Long_t *r = (Long_t *)((char *)ary - layout.cookieSize); + Long_t arrayLen = r[1]; + Long_t size = r[0]; + char *memBegin = (char *)ary - layout.headerSize; char* p = ((char*) ary) + ((arrayLen - 1) * size); for (Long_t cnt = 0; cnt < arrayLen; ++cnt, p -= size) { @@ -5362,7 +5484,7 @@ void TStreamerInfo::DeleteArray(void* ary, Bool_t dtorOnly) } // for arrayItemSize if (!dtorOnly) { - delete[] memBegin; + ::operator delete[](memBegin, std::align_val_t(layout.align)); } } @@ -5910,7 +6032,8 @@ TStreamerInfo::GenExplicitClassStreamer( const ::ROOT::TCollectionProxyInfo &inf // // Utility functions // -static TStreamerElement* R__CreateEmulatedElement(const char *dmName, const std::string &dmFull, Int_t offset, bool silent) +static TStreamerElement * +R__CreateEmulatedElement(const char *dmName, const std::string &dmFull, Int_t offset, bool silent, bool needAlign) { // Create a TStreamerElement for the type 'dmFull' and whose data member name is 'dmName'. @@ -5918,12 +6041,22 @@ static TStreamerElement* R__CreateEmulatedElement(const char *dmName, const std: TString dmType( TClassEdit::ShortType(dmFull.c_str(),1) ); Bool_t dmIsPtr = (s1 != dmType); const char *dmTitle = "Emulation"; + // Over align the basic data types. + size_t align = alignof(std::max_align_t); + if (needAlign && offset % align != 0) + offset = (Int_t)AlignUp((size_t)offset, align); TDataType *dt = gROOT->GetType(dmType); if (dt && dt->GetType() > 0 ) { // found a basic type Int_t dsize,dtype; dtype = dt->GetType(); dsize = dt->Size(); + // Use the precise alignment for the basic type if available. + if (needAlign && dt->GetAlignOf()) { + align = dt->GetAlignOf(); + if (offset % align != 0) + offset = (Int_t)AlignUp((size_t)offset, align); + } if (dmIsPtr && dtype != kCharStar) { if (!silent) Error("Pair Emulation Building","%s is not yet supported in pair emulation", @@ -5969,6 +6102,10 @@ static TStreamerElement* R__CreateEmulatedElement(const char *dmName, const std: } } // a class + assert(ROOT::Internal::IsValidAlignment(clm->GetClassAlignment())); + align = std::max(align, clm->GetClassAlignment()); + if (needAlign && ((offset % align) != 0)) + offset = (Int_t)AlignUp((size_t)offset, align); if (clm->IsTObject()) { return new TStreamerObject(dmName,dmTitle,offset,dmFull.c_str()); } else if(clm == TString::Class() && !dmIsPtr) { @@ -6012,24 +6149,22 @@ TVirtualStreamerInfo *TStreamerInfo::GenerateInfoForPair(const std::string &firs i->SetName(pname.c_str()); i->SetClass(nullptr); i->GetElements()->Delete(); - TStreamerElement *fel = R__CreateEmulatedElement("first", firstname, 0, silent); + TStreamerElement *fel = R__CreateEmulatedElement("first", firstname, 0, silent, /*needAlign=*/false); Int_t size = 0; if (fel) { - i->GetElements()->Add( fel ); - - size = fel->GetSize(); - Int_t sp = sizeof(void *); - //align the non-basic data types (required on alpha and IRIX!!) - if (size%sp != 0) size = size - size%sp + sp; + i->GetElements()->Add(fel); + i->fAlignment = std::max(i->fAlignment, fel->GetAlignment()); } else { delete i; return 0; } if (hint_pair_offset) size = hint_pair_offset; - TStreamerElement *second = R__CreateEmulatedElement("second", secondname, size, silent); + TStreamerElement *second = + R__CreateEmulatedElement("second", secondname, size, silent, /*needAlign=*/!hint_pair_offset); if (second) { - i->GetElements()->Add( second ); + i->GetElements()->Add(second); + i->fAlignment = std::max(i->fAlignment, second->GetAlignment()); } else { delete i; return 0; diff --git a/io/io/src/TStreamerInfoActions.cxx b/io/io/src/TStreamerInfoActions.cxx index 35f315029fce5..bebd784eacd7d 100644 --- a/io/io/src/TStreamerInfoActions.cxx +++ b/io/io/src/TStreamerInfoActions.cxx @@ -4198,6 +4198,7 @@ void TStreamerInfo::Compile() fCompFull = new TCompInfo*[1]; fCompOpt = new TCompInfo*[1]; fCompOpt[0] = fCompFull[0] = &(fComp[0]); + fAlignment = 1; SetIsCompiled(); return; } diff --git a/roottest/root/meta/alignment/CMakeLists.txt b/roottest/root/meta/alignment/CMakeLists.txt new file mode 100644 index 0000000000000..ccc41c5a96097 --- /dev/null +++ b/roottest/root/meta/alignment/CMakeLists.txt @@ -0,0 +1,10 @@ +ROOTTEST_ADD_TEST(evolution + MACRO evolution.C+ + FIXTURES_SETUP root-meta-alignment-evolution-fixture) + +ROOTTEST_ADD_TEST(emulation + MACRO emulation.C+ + FIXTURES_REQUIRED root-meta-alignment-evolution-fixture) + +ROOTTEST_ADD_TEST(vecalloc + MACRO vecalloc.C+) diff --git a/roottest/root/meta/alignment/emulation.C b/roottest/root/meta/alignment/emulation.C new file mode 100644 index 0000000000000..a8b9e54d2c260 --- /dev/null +++ b/roottest/root/meta/alignment/emulation.C @@ -0,0 +1,110 @@ +// File: emulation.C + +#include +#include // uintptr_t +#include +#include "TError.h" +#include "TFile.h" +#include "TBuffer.h" +#include "TObjArray.h" + +// Containee type with a custom alignment (over-aligned) +struct alignas(256) Containee { + int id; + // some payload to make sizeof non-trivial + double payload[7]; + + Containee(int i = -1) : id(i) {} + ~Containee() = default; + + void Streamer(TBuffer &b) + { + if (b.IsReading()) { + std::cout << "Streaming Containee\n"; + // Guess the effective alignment of c's address: largest power-of-two + // that divides the address. + uintptr_t addr = reinterpret_cast(this); + uintptr_t guessed = 1; + if (addr != 0) { + guessed = addr & (~addr + 1); // isolate lowest set bit + } + std::cout << " address: " << static_cast(this) << " (guessed alignment: " << guessed + << " bytes)\n"; + if (reinterpret_cast(this) % alignof(Containee) != 0) { + Fatal("copyContainer", "Containee object at %p does not satisfy alignment requirement of %zu\n", this, + alignof(Containee)); + } + + b.ReadClassBuffer(TClass::GetClass("Containee"), this); + } else { + b.WriteClassBuffer(TClass::GetClass("Containee"), this); + } + } +}; + +static_assert(alignof(Containee) == 256, "Containee must be 256-byte aligned"); + +#ifdef __ROOTCLING__ +#pragma link C++ class Containee - ; +#endif + +/// Check that the emulated TClass for \a className reports the expected +/// \a expectedAlignment and \a expectedSize. Returns 0 on success or a +/// non-zero error code on failure. +int checkEmulatedClass(const char *className, std::size_t expectedAlignment, Int_t expectedSize) +{ + auto cl = TClass::GetClass(className); + if (!cl) { + Error("checkEmulatedClass", "Could not get TClass for %s", className); + return 1; + } + if (cl->GetClassAlignment() != expectedAlignment) { + Error("checkEmulatedClass", "TClass for %s has alignment %zu, expected %zu", className, cl->GetClassAlignment(), + expectedAlignment); + return 2; + } + if (cl->GetClassSize() != expectedSize) { + Error("checkEmulatedClass", "TClass for %s has size %d, expected %d", className, cl->GetClassSize(), + expectedSize); + return 3; + } + return 0; +} + +int readfile(const char *filename) +{ + TFile file(filename, "READ"); + if (file.IsZombie()) + return 1; + auto c = file.Get("origContainer"); + TClass::GetClass("Container")->GetStreamerInfos()->ls(); + if (!c) { + Error("readfile", "Could not read object from file"); + return 2; + } + + size_t align = alignof(Containee); + std::vector vec; + vec.resize(20); + + // Container: char(1) + padding(255) + Containee(256) + ... = 768 bytes, align=256 + if (int rc = checkEmulatedClass("Container", alignof(Containee), 768)) + return 10 + rc; + + // std::pair: int(4) + padding(252) + Containee(256) = 512 bytes, align=256 + if (int rc = checkEmulatedClass("pair", alignof(Containee), 512)) + return 20 + rc; + + // When emulating the Wrapper class, we start with some metadata and thus + // the Containee members starts at offset 256, which means the total size + // of the pair becomes 768 bytes. + if (int rc = checkEmulatedClass("pair", alignof(Containee), 768)) + return 30 + rc; + + return 0; +} + +int emulation() +{ + return readfile("alignment_evolution.root"); +} diff --git a/roottest/root/meta/alignment/evolution.C b/roottest/root/meta/alignment/evolution.C new file mode 100644 index 0000000000000..086143ea1de1e --- /dev/null +++ b/roottest/root/meta/alignment/evolution.C @@ -0,0 +1,167 @@ +// File: evolution.C +// Demonstrates a containee with custom alignment embedded directly inside a +// container (no heap allocation). The compiler is responsible for satisfying +// the alignment requirement of the embedded object, inserting padding before it +// if necessary. + +#include +#include // uintptr_t +#include +#include "TError.h" +#include "TFile.h" + +// Containee type with a custom alignment (over-aligned) +struct alignas(256) Containee { + int id; + // some payload to make sizeof non-trivial + double payload[7]; + + Containee(int i = -1) : id(i) {} + ~Containee() = default; +}; + +struct Wrapper { + Containee c; + Wrapper(int i = -1) : c(i) {} + ~Wrapper() = default; +}; + +static_assert(alignof(Containee) == 256, "Containee must be 256-byte aligned"); + +// Container that embeds a single Containee object directly as a data member. +// The compiler guarantees the member satisfies alignof(Containee) because the +// member type carries the alignas specifier; it inserts padding before m_data +// as needed. +class Container { +private: + using pair_t = std::pair; + using coll_t = std::map; + using nested_coll_t = std::map; + + char m_misalign; // dummy member to show the compiler inserts padding + Containee m_data; // embedded – no manual aligned allocation + // pair_t m_pair_data; // check on the run-time dictionary generated for pairs + coll_t m_collection; // check on the run-time dictionary generated for collections + nested_coll_t m_nested_collection; + +public: + Container() = default; + explicit Container(int id) : m_misalign(0), m_data(id) + { + m_collection.emplace(id, Containee(id + 1)); + m_nested_collection.emplace(id, Wrapper(id + 2)); + } + ~Container() = default; // embedded object is destroyed automatically + + // Demonstrate and verify alignment of the embedded element + void showAlignment() const + { + const void *addr = static_cast(&m_data); + uintptr_t v = reinterpret_cast(addr); + std::cout << "Containee alignment requirement: " << alignof(Containee) << '\n'; + std::cout << "m_data at " << addr << " (addr % align = " << (v % alignof(Containee)) << ")\n"; + // runtime check + assert((v % alignof(Containee)) == 0 && "m_data not correctly aligned"); + } + + Containee &get() { return m_data; } + const Containee &get() const { return m_data; } + + ClassDef(Container, 2) // Container with embedded Containee +}; + +// AlternateContainer: same layout and behaviour as Container. +class AlternateContainer { +private: + double padding; // dummy member to show the compiler inserts padding + char m_misalign; // dummy member to show the compiler inserts padding + Containee m_alt_data; // embedded – no manual aligned allocation + +public: + AlternateContainer() = default; + explicit AlternateContainer(int id) : m_misalign(0), m_alt_data(id) {} + ~AlternateContainer() = default; // embedded object is destroyed automatically + + // Demonstrate and verify alignment of the embedded element + void showAlignment() const + { + const void *addr = static_cast(&m_alt_data); + uintptr_t v = reinterpret_cast(addr); + std::cout << "Containee alignment requirement: " << alignof(Containee) << '\n'; + std::cout << "m_alt_data at " << addr << " (addr % align = " << (v % alignof(Containee)) << ")\n"; + // runtime check + assert((v % alignof(Containee)) == 0 && "m_data not correctly aligned"); + } + + void copyContainee(const Containee &c) + { + std::cout << "Copying Containee with id = " << c.id << " into AlternateContainer\n"; + // Guess the effective alignment of c's address: largest power-of-two + // that divides the address. + uintptr_t addr = reinterpret_cast(&c); + uintptr_t guessed = 1; + if (addr != 0) { + guessed = addr & (~addr + 1); // isolate lowest set bit + } + std::cout << " address of c: " << static_cast(&c) << " (guessed alignment: " << guessed + << " bytes)\n"; + if (reinterpret_cast(&c) % alignof(Containee) != 0) { + Error("copyContainer", "Containee object at %p does not satisfy alignment requirement of %zu\n", &c, + alignof(Containee)); + } + m_alt_data = c; + } + + Containee &get() { return m_alt_data; } + const Containee &get() const { return m_alt_data; } + + ClassDef(AlternateContainer, 2) // Alternate container with embedded Containee +}; + +#ifdef __ROOTCLING__ +#pragma link C++ class Container + ; +#pragma link C++ class Containee + ; +#pragma link C++ class Wrapper + ; +#pragma link C++ class AlternateContainer + ; +#pragma read sourceClass = "Container" source = "char m_misalign" targetClass = "AlternateContainer" target = \ + "m_misalign" code = "{ m_misalign = onfile.m_misalign; }"; +#pragma read sourceClass = "Container" source = "Containee m_data" targetClass = "AlternateContainer" target = \ + "m_alt_data" code = "{ newObj->copyContainee(onfile.m_data); }"; +#endif + +void writefile(const char *filename) +{ + Container c(42); + c.showAlignment(); + std::cout << "c.get().id = " << c.get().id << '\n'; + + TFile file(filename, "RECREATE"); + file.WriteObject(&c, "origContainer"); + file.Write(); +}; + +void readfile(const char *filename) +{ + TFile file(filename, "READ"); + AlternateContainer *alt = file.Get("origContainer"); + if (alt) { + std::cout << "Successfully read AlternateContainer from file:\n"; + alt->showAlignment(); + std::cout << "Containee id = " << alt->get().id << '\n'; + } else { + Fatal("readfile", "Failed to read AlternateContainer from file"); + } +}; + +int evolution() +{ + const char *filename = "alignment_evolution.root"; + writefile(filename); + readfile(filename); + return 0; +} + +int main() +{ + return evolution(); +} diff --git a/roottest/root/meta/alignment/vecalloc.C b/roottest/root/meta/alignment/vecalloc.C new file mode 100644 index 0000000000000..44886dd7b2337 --- /dev/null +++ b/roottest/root/meta/alignment/vecalloc.C @@ -0,0 +1,141 @@ +// vecalloc.C +// Tests round-trip TFile I/O for a Container whose data member is a +// std::vector that uses a custom over-aligned allocator. + +#include +#include +#include +#include "TError.h" +#include "TFile.h" + +template +struct MyAlignedAllocator { + using value_type = T; + + std::size_t alignment; + + explicit MyAlignedAllocator(std::size_t align = alignof(std::max_align_t)) : alignment(align) {} + + template + MyAlignedAllocator(const MyAlignedAllocator &other) noexcept : alignment(other.alignment) + { + } + + T *allocate(std::size_t n) + { + std::size_t bytes = n * sizeof(T); + std::size_t effectiveAlign = alignment < alignof(T) ? alignof(T) : alignment; + // Round up to a multiple of effectiveAlign (required by aligned operator new) + bytes = (bytes + effectiveAlign - 1) & ~(effectiveAlign - 1); + return static_cast(::operator new(bytes, std::align_val_t{effectiveAlign})); + } + + void deallocate(T *p, std::size_t n) noexcept + { + std::size_t bytes = n * sizeof(T); + std::size_t effectiveAlign = alignment < alignof(T) ? alignof(T) : alignment; + bytes = (bytes + effectiveAlign - 1) & ~(effectiveAlign - 1); + ::operator delete(p, bytes, std::align_val_t{effectiveAlign}); + } + + template + bool operator==(const MyAlignedAllocator &other) const noexcept + { + return alignment == other.alignment; + } + template + bool operator!=(const MyAlignedAllocator &other) const noexcept + { + return !(*this == other); + } +}; + +struct Container { + std::vector> data; + + Container() = default; + Container(std::initializer_list vals) : data(vals) {} + template + Container(Iter first, Iter last) : data(first, last) + { + } + + ClassDefNV(Container, 1) +}; + +#ifdef __ROOTCLING__ +#pragma link C++ class Container + ; +#endif + +static const char *kFileName = "vecalloc_test.root"; +static const char *kObjName = "cont"; + +// Known values written into the Container +static const std::vector kExpected = {10, 20, 30, 42, 99}; + +int writefile() +{ + Container c(kExpected.begin(), kExpected.end()); + std::cout << "Writing Container with data:"; + for (int v : c.data) + std::cout << ' ' << v; + std::cout << '\n'; + + TFile f(kFileName, "RECREATE"); + if (f.IsZombie()) { + Error("writefile", "Cannot open %s for writing", kFileName); + return 1; + } + f.WriteObject(&c, kObjName); + f.Write(); + return 0; +} + +int readfile() +{ + TFile f(kFileName, "READ"); + if (f.IsZombie()) { + Error("readfile", "Cannot open %s for reading", kFileName); + return 1; + } + + Container *c = f.Get(kObjName); + if (!c) { + Error("readfile", "Failed to read Container '%s' from file", kObjName); + return 1; + } + + std::cout << "Read back Container with data:"; + for (int v : c->data) + std::cout << ' ' << v; + std::cout << '\n'; + + // Verify size + if (c->data.size() != kExpected.size()) { + Error("readfile", "Size mismatch: expected %zu, got %zu", kExpected.size(), c->data.size()); + return 1; + } + + // Verify values + for (std::size_t i = 0; i < kExpected.size(); ++i) { + if (c->data[i] != kExpected[i]) { + Error("readfile", "Value mismatch at index %zu: expected %d, got %d", i, kExpected[i], c->data[i]); + return 1; + } + } + + std::cout << "All values verified successfully.\n"; + return 0; +} + +int vecalloc() +{ + if (int ret = writefile()) + return ret; + return readfile(); +} + +int main() +{ + return vecalloc(); +} diff --git a/roottest/root/tree/cloning/references/exectrimZLIB.ref b/roottest/root/tree/cloning/references/exectrimZLIB.ref index 60feedad48256..1b92482826ef0 100644 --- a/roottest/root/tree/cloning/references/exectrimZLIB.ref +++ b/roottest/root/tree/cloning/references/exectrimZLIB.ref @@ -46,7 +46,7 @@ Warning in : no dictionary for class Belle2::CalcMeanCov<2,double> *............................................................................* ****************************************************************************** *Tree :tree : tree * -*Entries : 10 : Total = 22683 bytes File Size = 5407 * +*Entries : 10 : Total = 22683 bytes File Size = 5389 * * : : Tree compression factor = 1.18 * ****************************************************************************** *Br 0 :StrSimHits : Int_t StrSimHits_ * diff --git a/roottest/root/tree/cloning/references/exectrimZLIB_ng.ref b/roottest/root/tree/cloning/references/exectrimZLIB_ng.ref index e38d6bccaf663..a369229e065ed 100644 --- a/roottest/root/tree/cloning/references/exectrimZLIB_ng.ref +++ b/roottest/root/tree/cloning/references/exectrimZLIB_ng.ref @@ -46,7 +46,7 @@ Warning in : no dictionary for class Belle2::CalcMeanCov<2,double> *............................................................................* ****************************************************************************** *Tree :tree : tree * -*Entries : 10 : Total = 22683 bytes File Size = 4912 * +*Entries : 10 : Total = 22683 bytes File Size = 4905 * * : : Tree compression factor = 1.17 * ****************************************************************************** *Br 0 :StrSimHits : Int_t StrSimHits_ *