diff --git a/README/ReleaseNotes/v636/index.md b/README/ReleaseNotes/v636/index.md index 73b42a976db27..3c45a522080a1 100644 --- a/README/ReleaseNotes/v636/index.md +++ b/README/ReleaseNotes/v636/index.md @@ -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 \ No newline at end of file +## 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)) diff --git a/core/clingutils/src/TClingUtils.cxx b/core/clingutils/src/TClingUtils.cxx index 699757af88fc8..2c24a6a9acbdd 100644 --- a/core/clingutils/src/TClingUtils.cxx +++ b/core/clingutils/src/TClingUtils.cxx @@ -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)) @@ -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; 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/LinkDef.h b/core/meta/inc/LinkDef.h index cf735da2b0c47..a262366cf0e3b 100644 --- a/core/meta/inc/LinkDef.h +++ b/core/meta/inc/LinkDef.h @@ -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+; diff --git a/core/meta/inc/TClass.h b/core/meta/inc/TClass.h index 345fb90c9a5c1..5284cd04a1d29 100644 --- a/core/meta/inc/TClass.h +++ b/core/meta/inc/TClass.h @@ -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 fCanSplit; //!Indicates whether this class can be split or not. Values are -1, 0, 1, 2 @@ -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); @@ -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; diff --git a/core/meta/inc/TDataType.h b/core/meta/inc/TDataType.h index d0be3694efecc..7c425b7b310ac 100644 --- a/core/meta/inc/TDataType.h +++ b/core/meta/inc/TDataType.h @@ -23,6 +23,7 @@ ////////////////////////////////////////////////////////////////////////// #include "TDictionary.h" +#include enum EDataType { @@ -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*" @@ -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; @@ -76,7 +80,7 @@ class TDataType : public TDictionary { static EDataType GetType(const std::type_info &typeinfo); static void AddBuiltins(TCollection* types); - ClassDefOverride(TDataType,2) //Basic data type descriptor + ClassDefOverride(TDataType,3) //Basic data type descriptor }; #endif diff --git a/core/meta/inc/TGenericClassInfo.h b/core/meta/inc/TGenericClassInfo.h index da75aab3dc652..a2a8e8e51fe81 100644 --- a/core/meta/inc/TGenericClassInfo.h +++ b/core/meta/inc/TGenericClassInfo.h @@ -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; @@ -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, diff --git a/core/meta/inc/TInterpreter.h b/core/meta/inc/TInterpreter.h index 59f2a09b91039..4c9f4575d0596 100644 --- a/core/meta/inc/TInterpreter.h +++ b/core/meta/inc/TInterpreter.h @@ -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;} diff --git a/core/meta/inc/TStreamerElement.h b/core/meta/inc/TStreamerElement.h index d762cb7530614..f53757427f3e1 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 1ddcc9812dd9b..472de07bf7304 100644 --- a/core/meta/inc/TVirtualStreamerInfo.h +++ b/core/meta/inc/TVirtualStreamerInfo.h @@ -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; diff --git a/core/meta/src/TClass.cxx b/core/meta/src/TClass.cxx index dc805c1b6df97..d55d958c83d93 100644 --- a/core/meta/src/TClass.cxx +++ b/core/meta/src/TClass.cxx @@ -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; @@ -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()) { @@ -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(); @@ -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); + } + 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 72c4beca23080..b8cc7171d7891 100644 --- a/core/meta/src/TDataType.cxx +++ b/core/meta/src/TDataType.cxx @@ -17,10 +17,12 @@ defined types (accessible via TROOT::GetListOfTypes()). */ #include "TDataType.h" +#include "TError.h" #include "TInterpreter.h" #include "TCollection.h" #include "TVirtualMutex.h" #include "ThreadLocalStorage.h" +#include #ifdef R__SOLARIS #include #endif @@ -42,6 +44,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); @@ -49,6 +52,7 @@ TDataType::TDataType(TypedefInfo_t *info) : TDictionary(), SetTitle("Builtin basic type"); fProperty = 0; fSize = 0; + fAlignOf = 0; fType = kNoType_t; } } @@ -73,6 +77,7 @@ 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), @@ -90,6 +95,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; @@ -302,69 +308,98 @@ void TDataType::SetType(const char *name) fTrueName = name; fType = kOther_t; fSize = 0; + fAlignOf = 0; + + // Assigns an alignof() result to fAlignOf after verifying it fits in unsigned int. + // All standard C++ alignments are small powers-of-two; the assert is a safety + // net against exotic future platforms. + auto setAlignOf = [this](std::size_t al) { + R__ASSERT(al <= static_cast(std::numeric_limits::max()) && + "alignof value exceeds unsigned int range"); + fAlignOf = static_cast(al); + }; if (name==nullptr) { return; } else if (!strcmp("unsigned int", name)) { fType = kUInt_t; fSize = sizeof(UInt_t); + setAlignOf(alignof(UInt_t)); } else if (!strcmp("unsigned", name)) { fType = kUInt_t; fSize = sizeof(UInt_t); + setAlignOf(alignof(UInt_t)); } else if (!strcmp("int", name)) { fType = kInt_t; fSize = sizeof(Int_t); + setAlignOf(alignof(Int_t)); } else if (!strcmp("unsigned long", name)) { fType = kULong_t; fSize = sizeof(ULong_t); + setAlignOf(alignof(ULong_t)); } else if (!strcmp("long", name)) { fType = kLong_t; fSize = sizeof(Long_t); + setAlignOf(alignof(Long_t)); } else if (!strcmp("unsigned long long", name) || !strcmp("ULong64_t",name)) { fType = kULong64_t; fSize = sizeof(ULong64_t); + setAlignOf(alignof(ULong64_t)); } else if (!strcmp("long long", name) || !strcmp("Long64_t",name)) { fType = kLong64_t; fSize = sizeof(Long64_t); + setAlignOf(alignof(Long64_t)); } else if (!strcmp("unsigned short", name)) { fType = kUShort_t; fSize = sizeof(UShort_t); + setAlignOf(alignof(UShort_t)); } else if (!strcmp("short", name)) { fType = kShort_t; fSize = sizeof(Short_t); + setAlignOf(alignof(Short_t)); } else if (!strcmp("unsigned char", name)) { fType = kUChar_t; fSize = sizeof(UChar_t); + setAlignOf(alignof(UChar_t)); } else if (!strcmp("char", name)) { fType = kChar_t; fSize = sizeof(Char_t); + setAlignOf(alignof(Char_t)); } else if (!strcmp("bool", name)) { fType = kBool_t; fSize = sizeof(Bool_t); + setAlignOf(alignof(Bool_t)); } else if (!strcmp("float", name)) { fType = kFloat_t; fSize = sizeof(Float_t); + setAlignOf(alignof(Float_t)); } else if (!strcmp("double", name)) { fType = kDouble_t; fSize = sizeof(Double_t); + setAlignOf(alignof(Double_t)); } else if (!strcmp("signed char", name)) { fType = kChar_t; // kDataTypeAliasSignedChar_t; fSize = sizeof(Char_t); + setAlignOf(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); + setAlignOf(alignof(Float16_t)); fType = kFloat16_t; } if (!strcmp("Double32_t", fName.Data())) { fSize = sizeof(Double32_t); + setAlignOf(alignof(Double32_t)); fType = kDouble32_t; } if (!strcmp("char*",fName.Data())) { fType = kCharStar; + setAlignOf(alignof(char *)); } // kCounter = 6, kBits = 15 } diff --git a/core/meta/src/TGenericClassInfo.cxx b/core/meta/src/TGenericClassInfo.cxx index 6a3ef2cde0f6d..2fee4ae6bd816 100644 --- a/core/meta/src/TGenericClassInfo.cxx +++ b/core/meta/src/TGenericClassInfo.cxx @@ -87,7 +87,7 @@ namespace Internal { 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_) : fAction(action), fClass(nullptr), fClassName(fullClassname), fDeclFileName(declFileName), fDeclFileLine(declFileLine), fDictionary(dictionary), fInfo(info), @@ -95,7 +95,7 @@ namespace Internal { 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), + fStreamerFunc(nullptr), fConvStreamerFunc(nullptr), fCollectionProxy(nullptr), fSizeof(sizof), fAlignment(alignof_), fPragmaBits(pragmabits), fCollectionProxyInfo(nullptr), fCollectionStreamerInfo(nullptr) { // Constructor. @@ -107,7 +107,7 @@ namespace Internal { 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_) : fAction(action), fClass(nullptr), fClassName(fullClassname), fDeclFileName(declFileName), fDeclFileLine(declFileLine), fDictionary(dictionary), fInfo(info), @@ -115,7 +115,7 @@ namespace Internal { 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), + fStreamerFunc(nullptr), fConvStreamerFunc(nullptr), fCollectionProxy(nullptr), fSizeof(sizof), fAlignment(alignof_), fPragmaBits(pragmabits), fCollectionProxyInfo(nullptr), fCollectionStreamerInfo(nullptr) { @@ -137,7 +137,7 @@ namespace Internal { 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), + fStreamerFunc(nullptr), fConvStreamerFunc(nullptr), fCollectionProxy(nullptr), fSizeof(0), fAlignment(0), fPragmaBits(pragmabits), fCollectionProxyInfo(nullptr), fCollectionStreamerInfo(nullptr) { @@ -291,6 +291,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 43bbad87fa099..549a338260b73 100644 --- a/core/meta/src/TStreamerElement.cxx +++ b/core/meta/src/TStreamerElement.cxx @@ -26,6 +26,7 @@ #include "TBaseClass.h" #include "TDataMember.h" #include "TDataType.h" +#include "ROOT/RAlignmentUtils.hxx" #include "TEnum.h" #include "TRealData.h" #include "ThreadLocalStorage.h" @@ -379,6 +380,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. @@ -684,6 +703,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. @@ -1048,6 +1082,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 @@ -1295,6 +1347,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. @@ -1392,6 +1459,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 0d9ea8f611e5e..1c94f92a03372 100644 --- a/core/metacling/src/TCling.cxx +++ b/core/metacling/src/TCling.cxx @@ -8171,6 +8171,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 4df24757ef8b2..6885a40bca5f8 100644 --- a/core/metacling/src/TCling.h +++ b/core/metacling/src/TCling.h @@ -441,6 +441,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..2da864bc627e0 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,47 @@ 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 3f1a1cd75dff4..b6127f1fed633 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,62 @@ 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 008db88af6020..920adaf6a126b 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)) + : TGenCollectionProxy(typeid(Cont_t), sizeof(Cont_t::iterator)) { // Build a Streamer for a collection whose type is described by 'collectionClass'. @@ -88,9 +88,9 @@ 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 +102,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 +133,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 +193,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())) { @@ -268,7 +266,6 @@ 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); char* addr = ((char*)fEnv->fStart) + fValDiff*left; size_t i; @@ -363,8 +360,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 +372,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 6d45f80222c20..4e3fceee18d6e 100644 --- a/io/io/src/TGenCollectionProxy.cxx +++ b/io/io/src/TGenCollectionProxy.cxx @@ -1520,7 +1520,6 @@ void TGenCollectionProxy__VectorCreateIterators(void *obj, void **begin_arena, v } *begin_arena = vec->data(); *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 abcddd9eaf5cc..646778cefbc14 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}; @@ -155,6 +158,7 @@ TStreamerInfo::TStreamerInfo() fNfulldata= 0; fNslots = 0; fSize = 0; + fAlignment= 0; fClassVersion = 0; fOnFileClassVersion = 0; fOldVersion = Class()->GetClassVersion(); @@ -189,6 +193,7 @@ TStreamerInfo::TStreamerInfo(TClass *cl) fNfulldata= 0; fNslots = 0; fSize = 0; + fAlignment= 0; fClassVersion = fClass->GetClassVersion(); fOnFileClassVersion = 0; fOldVersion = Class()->GetClassVersion(); @@ -241,6 +246,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); @@ -504,6 +531,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 } @@ -801,6 +833,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). @@ -1913,8 +1950,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")) { @@ -2027,6 +2062,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. @@ -2091,14 +2127,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 { @@ -2183,8 +2229,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. @@ -2196,6 +2256,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; @@ -2312,6 +2373,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; @@ -2641,12 +2716,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) { @@ -2745,6 +2823,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; @@ -3315,10 +3394,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 @@ -3327,12 +3414,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); } } @@ -4986,8 +5084,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); } @@ -5140,23 +5243,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; @@ -5301,7 +5411,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())); } } @@ -5345,10 +5462,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) { @@ -5357,7 +5479,7 @@ void TStreamerInfo::DeleteArray(void* ary, Bool_t dtorOnly) } // for arrayItemSize if (!dtorOnly) { - delete[] memBegin; + ::operator delete[](memBegin, std::align_val_t(layout.align)); } } @@ -5905,7 +6027,7 @@ 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'. @@ -5913,12 +6035,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", @@ -5964,6 +6096,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) { @@ -6007,24 +6143,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 61a046c898297..4c4134b53f026 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; }