diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index 11982af3fa609..44a239e50d5d8 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -932,6 +932,12 @@ def IsConstantEvaluated : LangBuiltin<"CXX_LANG"> { let Prototype = "bool()"; } +def ClearPadding : LangBuiltin<"CXX_LANG"> { + let Spellings = ["__builtin_clear_padding"]; + let Attributes = [NoThrow]; + let Prototype = "void(...)"; +} + // GCC exception builtins def EHReturn : Builtin { let Spellings = ["__builtin_eh_return"]; diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp index c16b69ba87567..485ee45d95a98 100644 --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -63,7 +63,10 @@ #include "llvm/Support/ScopedPrinter.h" #include "llvm/TargetParser/AArch64TargetParser.h" #include "llvm/TargetParser/X86TargetParser.h" +#include #include +#include +#include #include using namespace clang; @@ -2538,6 +2541,311 @@ static RValue EmitHipStdParUnsupportedBuiltin(CodeGenFunction *CGF, return RValue::get(CGF->Builder.CreateCall(UBF, Args)); } +namespace { + +struct PaddingClearer { + PaddingClearer(CodeGenFunction &F) + : CGF(F), CharWidth(CGF.getContext().getCharWidth()) {} + + void run(Value *Ptr, QualType Ty) { + OccuppiedIntervals.clear(); + Queue.clear(); + + Queue.push_back(Data{0, Ty, true}); + while (!Queue.empty()) { + auto Current = Queue.front(); + Queue.pop_front(); + Visit(Current); + } + + MergeOccuppiedIntervals(); + auto PaddingIntervals = + GetPaddingIntervals(CGF.getContext().getTypeSize(Ty)); + llvm::dbgs() << "Occuppied Bits:\n"; + for (auto [first, last] : OccuppiedIntervals) { + llvm::dbgs() << "[" << first << ", " << last << ")\n"; + } + llvm::dbgs() << "Padding Bits:\n"; + for (auto [first, last] : PaddingIntervals) { + llvm::dbgs() << "[" << first << ", " << last << ")\n"; + } + + for (const auto &Interval : PaddingIntervals) { + ClearPadding(Ptr, Interval); + } + } + +private: + struct BitInterval { + // [First, Last) + uint64_t First; + uint64_t Last; + }; + + struct Data { + uint64_t StartBitOffset; + QualType Ty; + bool VisitVirtualBase; + }; + + void Visit(Data const &D) { + if (auto *AT = dyn_cast(D.Ty)) { + VisitArray(AT, D.StartBitOffset); + return; + } + + if (auto *Record = D.Ty->getAsCXXRecordDecl()) { + VisitStruct(Record, D.StartBitOffset, D.VisitVirtualBase); + return; + } + + if (D.Ty->isAtomicType()) { + auto Unwrapped = D; + Unwrapped.Ty = D.Ty.getAtomicUnqualifiedType(); + Queue.push_back(Unwrapped); + return; + } + + if (const auto *Complex = D.Ty->getAs()) { + VisitComplex(Complex, D.StartBitOffset); + return; + } + + auto *Type = CGF.ConvertTypeForMem(D.Ty); + auto SizeBit = CGF.CGM.getModule() + .getDataLayout() + .getTypeSizeInBits(Type) + .getKnownMinValue(); + llvm::dbgs() << "clear_padding primitive type. adding Interval [" + << D.StartBitOffset << ", " << D.StartBitOffset + SizeBit + << ")\n"; + OccuppiedIntervals.push_back( + BitInterval{D.StartBitOffset, D.StartBitOffset + SizeBit}); + } + + void VisitArray(const ConstantArrayType *AT, uint64_t StartBitOffset) { + llvm::dbgs() << "clear_padding visiting constant array starting from " + << StartBitOffset << "\n"; + for (uint64_t ArrIndex = 0; ArrIndex < AT->getSize().getLimitedValue(); + ++ArrIndex) { + + QualType ElementQualType = AT->getElementType(); + auto ElementSize = CGF.getContext().getTypeSizeInChars(ElementQualType); + auto ElementAlign = CGF.getContext().getTypeAlignInChars(ElementQualType); + auto Offset = ElementSize.alignTo(ElementAlign); + + Queue.push_back( + Data{StartBitOffset + ArrIndex * Offset.getQuantity() * CharWidth, + ElementQualType, true}); + } + } + + void VisitStruct(const CXXRecordDecl *R, uint64_t StartBitOffset, + bool VisitVirtualBase) { + llvm::dbgs() << "clear_padding visiting struct: " + << R->getQualifiedNameAsString() << " starting from offset " + << StartBitOffset << '\n'; + const auto &DL = CGF.CGM.getModule().getDataLayout(); + + const ASTRecordLayout &ASTLayout = CGF.getContext().getASTRecordLayout(R); + if (ASTLayout.hasOwnVFPtr()) { + llvm::dbgs() + << "clear_padding found vtable ptr. Adding occuppied interval [" + << StartBitOffset << ", " + << (StartBitOffset + DL.getPointerSizeInBits()) << ")\n"; + OccuppiedIntervals.push_back(BitInterval{ + StartBitOffset, StartBitOffset + DL.getPointerSizeInBits()}); + } + + const auto VisitBase = [&ASTLayout, StartBitOffset, this]( + const CXXBaseSpecifier &Base, auto GetOffset) { + auto *BaseRecord = Base.getType()->getAsCXXRecordDecl(); + if (!BaseRecord) { + llvm::dbgs() << "Base is not a CXXRecord!\n"; + return; + } + auto BaseOffset = + std::invoke(GetOffset, ASTLayout, BaseRecord).getQuantity(); + + llvm::dbgs() << "visiting base at offset " << StartBitOffset << " + " + << BaseOffset * CharWidth << '\n'; + Queue.push_back( + Data{StartBitOffset + BaseOffset * CharWidth, Base.getType(), false}); + }; + + for (auto Base : R->bases()) { + if (!Base.isVirtual()) { + VisitBase(Base, &ASTRecordLayout::getBaseClassOffset); + } + } + + if (VisitVirtualBase) { + for (auto VBase : R->vbases()) { + VisitBase(VBase, &ASTRecordLayout::getVBaseClassOffset); + } + } + + for (auto *Field : R->fields()) { + auto FieldOffset = ASTLayout.getFieldOffset(Field->getFieldIndex()); + llvm::dbgs() << "visiting field at offset " << StartBitOffset << " + " + << FieldOffset << '\n'; + if (Field->isBitField()) { + llvm::dbgs() << "clear_padding found bit field. Adding Interval [" + << StartBitOffset + FieldOffset << " , " + << FieldOffset + Field->getBitWidthValue(CGF.getContext()) + << ")\n"; + OccuppiedIntervals.push_back( + BitInterval{StartBitOffset + FieldOffset, + StartBitOffset + FieldOffset + + Field->getBitWidthValue(CGF.getContext())}); + } else { + Queue.push_back( + Data{StartBitOffset + FieldOffset, Field->getType(), true}); + } + } + } + + void VisitComplex(const ComplexType *CT, uint64_t StartBitOffset) { + QualType ElementQualType = CT->getElementType(); + auto ElementSize = CGF.getContext().getTypeSizeInChars(ElementQualType); + auto ElementAlign = CGF.getContext().getTypeAlignInChars(ElementQualType); + auto ImgOffset = ElementSize.alignTo(ElementAlign); + + llvm::dbgs() << "clear_padding visiting Complex Type. Real from " + << StartBitOffset << "Img from " + << StartBitOffset + ImgOffset.getQuantity() * CharWidth + << "\n"; + Queue.push_back(Data{StartBitOffset, ElementQualType, true}); + Queue.push_back(Data{StartBitOffset + ImgOffset.getQuantity() * CharWidth, + ElementQualType, true}); + } + + void MergeOccuppiedIntervals() { + std::sort(OccuppiedIntervals.begin(), OccuppiedIntervals.end(), + [](const BitInterval &lhs, const BitInterval &rhs) { + return std::tie(lhs.First, lhs.Last) < + std::tie(rhs.First, rhs.Last); + }); + + std::vector Merged; + Merged.reserve(OccuppiedIntervals.size()); + + for (const BitInterval &NextInterval : OccuppiedIntervals) { + if (Merged.empty()) { + Merged.push_back(NextInterval); + continue; + } + auto &LastInterval = Merged.back(); + + if (NextInterval.First > LastInterval.Last) { + Merged.push_back(NextInterval); + } else { + LastInterval.Last = std::max(LastInterval.Last, NextInterval.Last); + } + } + + OccuppiedIntervals = Merged; + } + + std::vector GetPaddingIntervals(uint64_t SizeInBits) const { + std::vector Results; + if (OccuppiedIntervals.size() == 1 && + OccuppiedIntervals.front().First == 0 && + OccuppiedIntervals.end()->Last == SizeInBits) { + return Results; + } + Results.reserve(OccuppiedIntervals.size() + 1); + uint64_t CurrentPos = 0; + for (const BitInterval &OccupiedInterval : OccuppiedIntervals) { + if (OccupiedInterval.First > CurrentPos) { + Results.push_back(BitInterval{CurrentPos, OccupiedInterval.First}); + } + CurrentPos = OccupiedInterval.Last; + } + if (SizeInBits > CurrentPos) { + Results.push_back(BitInterval{CurrentPos, SizeInBits}); + } + return Results; + } + + + + void ClearPadding(Value *Ptr, const BitInterval &PaddingInterval) { + auto *I8Ptr = CGF.Builder.CreateBitCast(Ptr, CGF.Int8PtrTy); + auto *Zero = ConstantInt::get(CGF.Int8Ty, 0); + + // Calculate byte indices and bit positions + auto StartByte = PaddingInterval.First / CharWidth; + auto StartBit = PaddingInterval.First % CharWidth; + auto EndByte = PaddingInterval.Last / CharWidth; + auto EndBit = PaddingInterval.Last % CharWidth; + + if (StartByte == EndByte) { + // Interval is within a single byte + auto *Index = ConstantInt::get(CGF.IntTy, StartByte); + auto *Element = CGF.Builder.CreateGEP(CGF.Int8Ty, I8Ptr, Index); + Address ElementAddr(Element, CGF.Int8Ty, CharUnits::One()); + + auto *Value = CGF.Builder.CreateLoad(ElementAddr); + + // Create mask to clear bits within the byte + uint8_t mask = ((1 << EndBit) - 1) & ~((1 << StartBit) - 1); + auto *MaskValue = ConstantInt::get(CGF.Int8Ty, mask); + auto *NewValue = CGF.Builder.CreateAnd(Value, MaskValue); + + CGF.Builder.CreateStore(NewValue, ElementAddr); + } else { + // Handle the start byte + if (StartBit != 0) { + auto *Index = ConstantInt::get(CGF.IntTy, StartByte); + auto *Element = CGF.Builder.CreateGEP(CGF.Int8Ty, I8Ptr, Index); + Address ElementAddr(Element, CGF.Int8Ty, CharUnits::One()); + + auto *Value = CGF.Builder.CreateLoad(ElementAddr); + + uint8_t startMask = ((1 << (CharWidth - StartBit)) - 1) << StartBit; + auto *MaskValue = ConstantInt::get(CGF.Int8Ty, ~startMask); + auto *NewValue = CGF.Builder.CreateAnd(Value, MaskValue); + + CGF.Builder.CreateStore(NewValue, ElementAddr); + ++StartByte; + } + + // Handle full bytes in the middle + for (auto Offset = StartByte; Offset < EndByte; ++Offset) { + auto *Index = ConstantInt::get(CGF.IntTy, Offset); + auto *Element = CGF.Builder.CreateGEP(CGF.Int8Ty, I8Ptr, Index); + Address ElementAddr(Element, CGF.Int8Ty, CharUnits::One()); + + CGF.Builder.CreateStore(Zero, ElementAddr); + } + + // Handle the end byte + if (EndBit != 0) { + auto *Index = ConstantInt::get(CGF.IntTy, EndByte); + auto *Element = CGF.Builder.CreateGEP(CGF.Int8Ty, I8Ptr, Index); + Address ElementAddr(Element, CGF.Int8Ty, CharUnits::One()); + + auto *Value = CGF.Builder.CreateLoad(ElementAddr); + + uint8_t endMask = (1 << EndBit) - 1; + auto *MaskValue = ConstantInt::get(CGF.Int8Ty, endMask); + auto *NewValue = CGF.Builder.CreateAnd(Value, MaskValue); + + CGF.Builder.CreateStore(NewValue, ElementAddr); + } + } + } + + + CodeGenFunction &CGF; + const uint64_t CharWidth; + std::deque Queue; + std::vector OccuppiedIntervals; +}; + +} // namespace + RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID, const CallExpr *E, ReturnValueSlot ReturnValue) { @@ -4462,6 +4770,15 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID, return RValue::get(Ptr); } + case Builtin::BI__builtin_clear_padding: { + const Expr *Op = E->getArg(0); + Value *Address = EmitScalarExpr(Op); + auto PointeeTy = Op->getType()->getPointeeType(); + PaddingClearer clearer{*this}; + clearer.run(Address, PointeeTy); + //RecursivelyClearPadding(*this, Address, PointeeTy); + return RValue::get(nullptr); + } case Builtin::BI__sync_fetch_and_add: case Builtin::BI__sync_fetch_and_sub: case Builtin::BI__sync_fetch_and_or: diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 300af02239779..1915e64af0fad 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -2655,6 +2655,37 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID, } case Builtin::BI__builtin_launder: return BuiltinLaunder(*this, TheCall); + case Builtin::BI__builtin_clear_padding: { + const auto numArgs = TheCall->getNumArgs(); + if (numArgs < 1) { + Diag(TheCall->getEndLoc(), diag::err_typecheck_call_too_few_args_one) + << 0 /*function call*/ << "T*" << 0; + return ExprError(); + } + if (numArgs > 1) { + Diag(TheCall->getEndLoc(), diag::err_typecheck_call_too_many_args_one) + << 0 /*function call*/ << "T*" << numArgs << 0; + return ExprError(); + } + + const Expr *PtrArg = TheCall->getArg(0); + const QualType PtrArgType = PtrArg->getType(); + if (!PtrArgType->isPointerType()) { + Diag(PtrArg->getBeginLoc(), diag::err_typecheck_convert_incompatible) + << PtrArgType << "pointer" << 1 << 0 << 3 << 1 << PtrArgType + << "pointer"; + return ExprError(); + } + if (PtrArgType->getPointeeType().isConstQualified()) { + Diag(PtrArg->getBeginLoc(), diag::err_typecheck_assign_const) + << TheCall->getSourceRange() << 5 /*ConstUnknown*/; + return ExprError(); + } + if (RequireCompleteType(PtrArg->getBeginLoc(), PtrArgType->getPointeeType(), + diag::err_typecheck_decl_incomplete_type)) + return ExprError(); + break; + } case Builtin::BI__sync_fetch_and_add: case Builtin::BI__sync_fetch_and_add_1: case Builtin::BI__sync_fetch_and_add_2: diff --git a/clang/test/CodeGenCXX/builtin-clear-padding-codegen.cpp b/clang/test/CodeGenCXX/builtin-clear-padding-codegen.cpp new file mode 100644 index 0000000000000..54455e6699849 --- /dev/null +++ b/clang/test/CodeGenCXX/builtin-clear-padding-codegen.cpp @@ -0,0 +1,112 @@ +// RUN: %clang_cc1 -triple=x86_64-linux-gnu -emit-llvm -o - %s | FileCheck %s + +struct alignas(4) Foo { + char a; + alignas(2) char b; +}; + +struct alignas(4) Bar { + char c; + alignas(2) char d; +}; + +struct alignas(4) Baz : Foo { + char e; + Bar f; +}; + +// Baz structure: +// "a", PAD_1, "b", PAD_2, "c", PAD_3, PAD_4, PAD_5, "c", PAD_6, "d", PAD_7 +// %struct.Baz = type { %struct.Foo, i8, [3 x i8], %struct.Bar } +// %struct.Foo = type { i8, i8, i8, i8 } +// %struct.Bar = type { i8, i8, i8, i8 } + +// CHECK-LABEL: define void @_Z7testBazP3Baz(%struct.Baz* %baz) +// CHECK: [[ADDR:%.*]] = alloca %struct.Baz* +// CHECK: store %struct.Baz* %baz, %struct.Baz** [[ADDR]] +// CHECK: [[BAZ:%.*]] = load %struct.Baz*, %struct.Baz** [[ADDR]] +// CHECK: [[BAZ_RAW_PTR:%.*]] = bitcast %struct.Baz* [[BAZ]] to i8* + +// CHECK: [[FOO_BASE:%.*]] = getelementptr inbounds %struct.Baz, %struct.Baz* [[BAZ]], i32 0, i32 0 +// CHECK: [[FOO_RAW_PTR:%.*]] = bitcast %struct.Foo* [[FOO_BASE]] to i8* +// CHECK: [[PAD_1:%.*]] = getelementptr i8, i8* [[FOO_RAW_PTR]], i32 1 +// CHECK: store i8 0, i8* [[PAD_1]] +// CHECK: [[PAD_2:%.*]] = getelementptr i8, i8* [[FOO_RAW_PTR]], i32 3 +// CHECK: store i8 0, i8* [[PAD_2]] + +// CHECK: [[PAD_3:%.*]] = getelementptr i8, i8* [[BAZ_RAW_PTR]], i32 5 +// CHECK: store i8 0, i8* [[PAD_3]] +// CHECK: [[PAD_4:%.*]] = getelementptr i8, i8* [[BAZ_RAW_PTR]], i32 6 +// CHECK: store i8 0, i8* [[PAD_4]] +// CHECK: [[PAD_5:%.*]] = getelementptr i8, i8* [[BAZ_RAW_PTR]], i32 7 +// CHECK: store i8 0, i8* [[PAD_5]] + +// CHECK: [[BAR_MEMBER:%.*]] = getelementptr inbounds %struct.Baz, %struct.Baz* [[BAZ]], i32 0, i32 3 +// CHECK: [[BAR_RAW_PTR:%.*]] = bitcast %struct.Bar* [[BAR_MEMBER]] to i8* +// CHECK: [[PAD_6:%.*]] = getelementptr i8, i8* [[BAR_RAW_PTR]], i32 1 +// CHECK: store i8 0, i8* [[PAD_6]] +// CHECK: [[PAD_7:%.*]] = getelementptr i8, i8* [[BAR_RAW_PTR]], i32 3 +// CHECK: store i8 0, i8* [[PAD_7]] +// CHECK: ret void +void testBaz(Baz *baz) { + __builtin_clear_padding(baz); +} + +struct UnsizedTail { + int size; + alignas(8) char buf[]; + + UnsizedTail(int size) : size(size) {} +}; + +// UnsizedTail structure: +// "size", PAD_1, PAD_2, PAD_3, PAD_4 +// %struct.UnsizedTail = type { i32, [4 x i8], [0 x i8] } + +// CHECK-LABEL: define void @_Z15testUnsizedTailP11UnsizedTail(%struct.UnsizedTail* %u) +// CHECK: [[U_ADDR:%.*]] = alloca %struct.UnsizedTail* +// CHECK: store %struct.UnsizedTail* %u, %struct.UnsizedTail** [[U_ADDR]] +// CHECK: [[U:%.*]] = load %struct.UnsizedTail*, %struct.UnsizedTail** [[U_ADDR]] +// CHECK: [[U_RAW_PTR:%.*]] = bitcast %struct.UnsizedTail* [[U]] to i8* +// CHECK: [[PAD_1:%.*]] = getelementptr i8, i8* [[U_RAW_PTR]], i32 4 +// CHECK: store i8 0, i8* [[PAD_1]] +// CHECK: [[PAD_2:%.*]] = getelementptr i8, i8* [[U_RAW_PTR]], i32 5 +// CHECK: store i8 0, i8* [[PAD_2]] +// CHECK: [[PAD_3:%.*]] = getelementptr i8, i8* [[U_RAW_PTR]], i32 6 +// CHECK: store i8 0, i8* [[PAD_3]] +// CHECK: [[PAD_4:%.*]] = getelementptr i8, i8* [[U_RAW_PTR]], i32 7 +// CHECK: store i8 0, i8* [[PAD_4]] +// CHECK: ret void +void testUnsizedTail(UnsizedTail *u) { + __builtin_clear_padding(u); +} + +struct ArrOfStructsWithPadding { + Bar bars[2]; +}; + +// ArrOfStructsWithPadding structure: +// "c" (1), PAD_1, "d" (1), PAD_2, "c" (2), PAD_3, "d" (2), PAD_4 +// %struct.ArrOfStructsWithPadding = type { [2 x %struct.Bar] } + +// CHECK-LABEL: define void @_Z27testArrOfStructsWithPaddingP23ArrOfStructsWithPadding(%struct.ArrOfStructsWithPadding* %arr) +// CHECK: [[ARR_ADDR:%.*]] = alloca %struct.ArrOfStructsWithPadding* +// CHECK: store %struct.ArrOfStructsWithPadding* %arr, %struct.ArrOfStructsWithPadding** [[ARR_ADDR]] +// CHECK: [[ARR:%.*]] = load %struct.ArrOfStructsWithPadding*, %struct.ArrOfStructsWithPadding** [[ARR_ADDR]] +// CHECK: [[BARS:%.*]] = getelementptr inbounds %struct.ArrOfStructsWithPadding, %struct.ArrOfStructsWithPadding* [[ARR]], i32 0, i32 0 +// CHECK: [[FIRST:%.*]] = getelementptr inbounds [2 x %struct.Bar], [2 x %struct.Bar]* [[BARS]], i64 0, i64 0 +// CHECK: [[FIRST_RAW_PTR:%.*]] = bitcast %struct.Bar* [[FIRST]] to i8* +// CHECK: [[PAD_1:%.*]] = getelementptr i8, i8* [[FIRST_RAW_PTR]], i32 1 +// CHECK: store i8 0, i8* [[PAD_1]] +// CHECK: [[PAD_2:%.*]] = getelementptr i8, i8* %4, i32 3 +// CHECK: store i8 0, i8* [[PAD_2]] +// CHECK: [[SECOND:%.*]] = getelementptr inbounds [2 x %struct.Bar], [2 x %struct.Bar]* [[BARS]], i64 0, i64 1 +// CHECK: [[SECOND_RAW_PTR:%.*]] = bitcast %struct.Bar* [[SECOND]] to i8* +// CHECK: [[PAD_3:%.*]] = getelementptr i8, i8* [[SECOND_RAW_PTR]], i32 1 +// CHECK: store i8 0, i8* [[PAD_3]] +// CHECK: [[PAD_4:%.*]] = getelementptr i8, i8* [[SECOND_RAW_PTR]], i32 3 +// CHECK: store i8 0, i8* [[PAD_4]] +// CHECK: ret void +void testArrOfStructsWithPadding(ArrOfStructsWithPadding *arr) { + __builtin_clear_padding(arr); +} diff --git a/clang/test/SemaCXX/builtin-clear-padding.cpp b/clang/test/SemaCXX/builtin-clear-padding.cpp new file mode 100644 index 0000000000000..ea87249c87b0a --- /dev/null +++ b/clang/test/SemaCXX/builtin-clear-padding.cpp @@ -0,0 +1,15 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s + +struct Foo {}; + +struct Incomplete; // expected-note {{forward declaration of 'Incomplete'}} + +void test(int a, Foo b, void *c, int *d, Foo *e, const Foo *f, Incomplete *g) { + __builtin_clear_padding(a); // expected-error {{passing 'int' to parameter of incompatible type pointer: type mismatch at 1st parameter ('int' vs pointer)}} + __builtin_clear_padding(b); // expected-error {{passing 'Foo' to parameter of incompatible type pointer: type mismatch at 1st parameter ('Foo' vs pointer)}} + __builtin_clear_padding(c); // expected-error {{variable has incomplete type 'void'}} + __builtin_clear_padding(d); // This should not error. + __builtin_clear_padding(e); // This should not error. + __builtin_clear_padding(f); // expected-error {{read-only variable is not assignable}} + __builtin_clear_padding(g); // expected-error {{variable has incomplete type 'Incomplete'}} +} diff --git a/libcxx/test/libcxx/atomics/builtin_clear_padding.pass.cpp b/libcxx/test/libcxx/atomics/builtin_clear_padding.pass.cpp new file mode 100644 index 0000000000000..ec220088ac1be --- /dev/null +++ b/libcxx/test/libcxx/atomics/builtin_clear_padding.pass.cpp @@ -0,0 +1,824 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// UNSUPPORTED: c++03 + +// ADDITIONAL_COMPILE_FLAGS: -Wno-deprecated-volatile -Wno-dynamic-class-memaccess + +#include +#include +#include +#include + +template +void print_bytes(const T* object) { + auto size = sizeof(T); + const unsigned char* const bytes = reinterpret_cast(object); + size_t i; + + fprintf(stderr, "[ "); + for (i = 0; i < size; i++) { + fprintf(stderr, "%02x ", bytes[i]); + } + fprintf(stderr, "]\n"); +} + +template +void __builtin_clear_padding2(T t) { + //__builtin_clear_padding(t); + (void)t; +} + +void assert2(bool b){ + //assert(b); + (void)b; +} + +template +struct alignas(A1) BasicWithPadding { + T x; + alignas(A2) T y; +}; + +template +struct alignas(A1) SpacedArrayMembers { + T x[N]; + alignas(A2) char c; + T y[N]; +}; + +template +struct alignas(A1) PaddedPointerMembers { + T* x; + alignas(A2) T* y; +}; + +template +struct alignas(A1) ThreeMembers { + T x; + alignas(A2) T y; + alignas(A3) T z; +}; + +template +struct Normal { + T a; + T b; +}; + +template +struct X { + T x; +}; + +template +struct Z { + T z; +}; + +template +struct YZ : public Z { + alignas(A) T y; +}; + +template +struct alignas(A1) HasBase : public X, public YZ { + T a; + alignas(A2) T b; +}; + +template +void testAllStructsForType(T a, T b, T c, T d) { + // basic padding + { + using B = BasicWithPadding; + B basic1; + memset(&basic1, 0, sizeof(B)); + basic1.x = a; + basic1.y = b; + B basic2; + memset(&basic2, 42, sizeof(B)); + basic2.x = a; + basic2.y = b; + assert2(memcmp(&basic1, &basic2, sizeof(B)) != 0); + __builtin_clear_padding2(&basic2); + assert2(memcmp(&basic1, &basic2, sizeof(B)) == 0); + } + + // spaced array + { + using A = SpacedArrayMembers; + A arr1; + memset(&arr1, 0, sizeof(A)); + arr1.x[0] = a; + arr1.x[1] = b; + arr1.y[0] = c; + arr1.y[1] = d; + A arr2; + memset(&arr2, 42, sizeof(A)); + arr2.x[0] = a; + arr2.x[1] = b; + arr2.y[0] = c; + arr2.y[1] = d; + arr2.c = 0; + assert2(memcmp(&arr1, &arr2, sizeof(A)) != 0); + __builtin_clear_padding2(&arr2); + assert2(memcmp(&arr1, &arr2, sizeof(A)) == 0); + } + + // pointer members + { + using P = PaddedPointerMembers; + P ptr1; + memset(&ptr1, 0, sizeof(P)); + ptr1.x = &a; + ptr1.y = &b; + P ptr2; + memset(&ptr2, 42, sizeof(P)); + ptr2.x = &a; + ptr2.y = &b; + assert2(memcmp(&ptr1, &ptr2, sizeof(P)) != 0); + __builtin_clear_padding2(&ptr2); + assert2(memcmp(&ptr1, &ptr2, sizeof(P)) == 0); + } + + // three members + { + using Three = ThreeMembers; + Three three1; + memset(&three1, 0, sizeof(Three)); + three1.x = a; + three1.y = b; + three1.z = c; + Three three2; + memset(&three2, 42, sizeof(Three)); + three2.x = a; + three2.y = b; + three2.z = c; + __builtin_clear_padding2(&three2); + assert2(memcmp(&three1, &three2, sizeof(Three)) == 0); + } + + // Normal struct no padding + { + using N = Normal; + N normal1; + memset(&normal1, 0, sizeof(N)); + normal1.a = a; + normal1.b = b; + N normal2; + memset(&normal2, 42, sizeof(N)); + normal2.a = a; + normal2.b = b; + __builtin_clear_padding2(&normal2); + assert2(memcmp(&normal1, &normal2, sizeof(N)) == 0); + } + + // base class + { + using H = HasBase; + H base1; + memset(&base1, 0, sizeof(H)); + base1.a = a; + base1.b = b; + base1.x = c; + base1.y = d; + base1.z = a; + H base2; + memset(&base2, 42, sizeof(H)); + base2.a = a; + base2.b = b; + base2.x = c; + base2.y = d; + base2.z = a; + assert2(memcmp(&base1, &base2, sizeof(H)) != 0); + __builtin_clear_padding2(&base2); + assert2(memcmp(&base1, &base2, sizeof(H)) == 0); + } +} + +struct UnsizedTail { + int size; + alignas(8) char buf[]; + + UnsizedTail(int size) : size(size) {} +}; + +void otherStructTests() { + // Unsized Tail + { + const size_t size1 = sizeof(UnsizedTail) + 4; + char buff1[size1]; + char buff2[size1]; + memset(buff1, 0, size1); + memset(buff2, 42, size1); + auto* u1 = new (buff1) UnsizedTail(4); + u1->buf[0] = 1; + u1->buf[1] = 2; + u1->buf[2] = 3; + u1->buf[3] = 4; + auto* u2 = new (buff2) UnsizedTail(4); + u2->buf[0] = 1; + u2->buf[1] = 2; + u2->buf[2] = 3; + u2->buf[3] = 4; + assert2(memcmp(u1, u2, sizeof(UnsizedTail)) != 0); + __builtin_clear_padding2(u2); + + assert2(memcmp(u1, u2, sizeof(UnsizedTail)) == 0); + } + + // basic padding on the heap + { + using B = BasicWithPadding<8, 4, char>; + auto* basic1 = new B; + memset(basic1, 0, sizeof(B)); + basic1->x = 1; + basic1->y = 2; + auto* basic2 = new B; + memset(basic2, 42, sizeof(B)); + basic2->x = 1; + basic2->y = 2; + assert2(memcmp(basic1, basic2, sizeof(B)) != 0); + __builtin_clear_padding2(basic2); + assert2(memcmp(basic1, basic2, sizeof(B)) == 0); + delete basic2; + delete basic1; + } + + // basic padding volatile on the heap + { + using B = BasicWithPadding<8, 4, char>; + B* basic3 = new B; + memset(basic3, 0, sizeof(B)); + basic3->x = 1; + basic3->y = 2; + B* basic4 = new B; + memset(basic4, 42, sizeof(B)); + basic4->x = 1; + basic4->y = 2; + assert2(memcmp(basic3, basic4, sizeof(B)) != 0); + __builtin_clear_padding2(const_cast(basic4)); + __builtin_clear_padding2(basic4); + assert2(memcmp(basic3, basic4, sizeof(B)) == 0); + delete basic4; + delete basic3; + } +} + +struct Foo { + int x; + int y; +}; + +typedef float Float4Vec __attribute__((ext_vector_type(4))); +typedef float Float3Vec __attribute__((ext_vector_type(3))); + +void primitiveTests() { + // no padding + { + int i1 = 42, i2 = 42; + __builtin_clear_padding2(&i1); // does nothing + assert2(i1 == 42); + assert2(memcmp(&i1, &i2, sizeof(int)) == 0); + } + + // long double + { + long double d1, d2; + memset(&d1, 42, sizeof(long double)); + memset(&d2, 0, sizeof(long double)); + + d1 = 3.0L; + d2 = 3.0L; + + __builtin_clear_padding2(&d1); + assert2(d1 == 3.0L); + assert2(memcmp(&d1, &d2, sizeof(long double)) == 0); + } + + // _Complex + { + _Complex long double c1, c2; + + memset(&c1, 42, sizeof(_Complex long double)); + memset(&c2, 0, sizeof(_Complex long double)); + c1 = 3.0L ; + c1 = 3.0L ; + __builtin_clear_padding2(&c1); + //TODO + } +} + +void structTests() { + // no_unique_address + { + struct S1 { + int x; + char c; + }; + + struct S2 { + [[no_unique_address]] S1 s; + bool b; + }; + + S2 s1, s2; + memset(&s1, 42, sizeof(S2)); + memset(&s2, 0, sizeof(S2)); + + s1.s.x = 4; + s1.s.c = 'a'; + s1.b = true; + s2.s.x = 4; + s2.s.c = 'a'; + s2.b = true; + + assert2(memcmp(&s1, &s2, sizeof(S2)) != 0); + __builtin_clear_padding2(&s1); + assert2(s1.s.x == 4); + assert2(s1.s.c == 'a'); + assert2(s1.b == true); + + assert2(memcmp(&s1, &s2, sizeof(S2)) == 0); + } + + // struct with long double + { + struct S { + long double l; + bool b; + }; + + S s1, s2; + memset(&s1, 42, sizeof(S)); + memset(&s2, 0, sizeof(S)); + + s1.l = 3.0L; + s1.b = true; + s2.l = 3.0L; + s2.b = true; + + assert2(memcmp(&s1, &s2, sizeof(S)) != 0); + __builtin_clear_padding2(&s1); + assert2(s1.l == 3.0L); + assert2(s1.b == true); + assert2(memcmp(&s1, &s2, sizeof(S)) == 0); + } + + // EBO + { + struct Empty {}; + struct B { + int i; + }; + struct S : Empty, B { + bool b; + }; + + S s1, s2; + memset(&s1, 42, sizeof(S)); + memset(&s2, 0, sizeof(S)); + + s1.i = 4; + s1.b = true; + s2.i = 4; + s2.b = true; + + assert2(memcmp(&s1, &s2, sizeof(S)) != 0); + __builtin_clear_padding2(&s1); + assert2(s1.i == 4); + assert2(s1.b == true); + assert2(memcmp(&s1, &s2, sizeof(S)) == 0); + } + + // padding between bases + { + struct B1 { + char c1; + }; + struct B2 { + alignas(4) char c2; + }; + + struct S : B1, B2 {}; + + S s1, s2; + memset(&s1, 42, sizeof(S)); + memset(&s2, 0, sizeof(S)); + + s1.c1 = 'a'; + s1.c2 = 'b'; + s2.c1 = 'a'; + s2.c2 = 'b'; + + assert2(memcmp(&s1, &s2, sizeof(S)) != 0); + __builtin_clear_padding2(&s1); + assert2(s1.c1 == 'a'); + assert2(s1.c2 == 'b'); + assert2(memcmp(&s1, &s2, sizeof(S)) == 0); + } + + // padding after last base + { + struct B1 { + char c1; + }; + struct B2 { + char c2; + }; + + struct S : B1, B2 { + alignas(4) char c3; + }; + + S s1, s2; + memset(&s1, 42, sizeof(S)); + memset(&s2, 0, sizeof(S)); + + s1.c1 = 'a'; + s1.c2 = 'b'; + s1.c3 = 'c'; + s2.c1 = 'a'; + s2.c2 = 'b'; + s2.c3 = 'c'; + + assert2(memcmp(&s1, &s2, sizeof(S)) != 0); + __builtin_clear_padding2(&s1); + assert2(s1.c1 == 'a'); + assert2(s1.c2 == 'b'); + assert2(s1.c3 == 'c'); + assert2(memcmp(&s1, &s2, sizeof(S)) == 0); + } + + // vtable + { + struct VirtualBase { + unsigned int x; + virtual int call() { return x; }; + virtual ~VirtualBase() = default; + }; + + struct NonVirtualBase { + char y; + }; + + struct S : VirtualBase, NonVirtualBase { + virtual int call() override { return 5; } + bool z; + }; + + char buff1[sizeof(S)]; + char buff2[sizeof(S)]; + memset(buff1, 0, sizeof(S)); + memset(buff2, 42, sizeof(S)); + + S* s1 = new (&buff1) S; + S* s2 = new (&buff2) S; + + s1->x = 0xFFFFFFFF; + s2->x = 0xFFFFFFFF; + s1->y = 'a'; + s2->y = 'a'; + s1->z = true; + s2->z = true; + __builtin_clear_padding2(s2); + assert2(s2->x == 0xFFFFFFFF); + assert2(s2->y == 'a'); + assert2(s2->z == true); + assert2(s2->call() == 5); + assert2(memcmp(s1, s2, sizeof(S)) == 0); + } + + // multiple bases with vtable + { + struct VirtualBase1 { + unsigned int x1; + virtual int call1() { return x1; }; + virtual ~VirtualBase1() = default; + }; + + struct VirtualBase2 { + unsigned int x2; + virtual int call2() { return x2; }; + virtual ~VirtualBase2() = default; + }; + + struct VirtualBase3 { + unsigned int x3; + virtual int call3() { return x3; }; + virtual ~VirtualBase3() = default; + }; + + struct NonVirtualBase { + char y; + }; + + struct S : VirtualBase1, VirtualBase2, NonVirtualBase, VirtualBase3 { + virtual int call1() override { return 5; } + bool z; + }; + + char buff1[sizeof(S)]; + char buff2[sizeof(S)]; + memset(buff1, 0, sizeof(S)); + memset(buff2, 42, sizeof(S)); + + S* s1 = new (&buff1) S; + S* s2 = new (&buff2) S; + + s1->x1 = 0xFFFFFFFF; + s2->x1 = 0xFFFFFFFF; + s1->x2 = 0xFAFAFAFA; + s2->x2 = 0xFAFAFAFA; + s1->x3 = 0xAAAAAAAA; + s2->x3 = 0xAAAAAAAA; + s1->y = 'a'; + s2->y = 'a'; + s1->z = true; + s2->z = true; + __builtin_clear_padding2(s2); + assert2(s2->x1 == 0xFFFFFFFF); + assert2(s2->x2 == 0xFAFAFAFA); + assert2(s2->x3 == 0xAAAAAAAA); + assert2(s2->y == 'a'); + assert2(s2->z == true); + assert2(s2->call1() == 5); + assert2(memcmp(s1, s2, sizeof(S)) == 0); + } + + // chain of bases with virtual functions + { + struct VirtualBase1 { + unsigned int x1; + virtual int call1() { return x1; }; + virtual ~VirtualBase1() = default; + }; + + struct VirtualBase2 : VirtualBase1 { + unsigned int x2; + virtual int call2() { return x2; }; + virtual ~VirtualBase2() = default; + }; + + struct VirtualBase3 : VirtualBase2 { + unsigned int x3; + virtual int call3() { return x3; }; + virtual ~VirtualBase3() = default; + }; + + struct NonVirtualBase { + char y; + }; + + struct S : NonVirtualBase, VirtualBase3 { + //virtual int call() override { return 5; } + bool z; + }; + + char buff1[sizeof(S)]; + char buff2[sizeof(S)]; + memset(buff1, 0, sizeof(S)); + memset(buff2, 42, sizeof(S)); + S* s1 = new (&buff1) S; + S* s2 = new (&buff2) S; + + s1->x1 = 0xFFFFFFFF; + s2->x1 = 0xFFFFFFFF; + s1->x2 = 0xFAFAFAFA; + s2->x2 = 0xFAFAFAFA; + s1->x3 = 0xAAAAAAAA; + s2->x3 = 0xAAAAAAAA; + s1->y = 'a'; + s2->y = 'a'; + s1->z = true; + s2->z = true; + __builtin_clear_padding2(s2); + assert2(memcmp(s1, s2, sizeof(S)) == 0); + } + + // virtual inheritance + { + struct Base { + int x; + }; + struct D1 : virtual Base { + int d1; + bool b1; + }; + struct D2 : virtual Base { + int d2; + bool b2; + }; + + struct S : D1, D2 { + bool s; + }; + + char buff1[sizeof(S)]; + char buff2[sizeof(S)]; + memset(buff1, 0, sizeof(S)); + memset(buff2, 42, sizeof(S)); + S* s1 = new (&buff1) S; + S* s2 = new (&buff2) S; + + s1->x = 0xFFFFFFFF; + s2->x = 0xFFFFFFFF; + s1->d1 = 0xFAFAFAFA; + s2->d1 = 0xFAFAFAFA; + s1->d2 = 0xAAAAAAAA; + s2->d2 = 0xAAAAAAAA; + s1->b1 = true; + s2->b1 = true; + s1->b2 = true; + s2->b2 = true; + s1->s = true; + s2->s = true; + __builtin_clear_padding2(s2); + assert2(memcmp(s1, s2, sizeof(S)) == 0); + } + + // bit fields + { + struct S { + // will usually occupy 2 bytes: + unsigned char b1 : 3; // 1st 3 bits (in 1st byte) are b1 + unsigned char b2 : 2; // next 2 bits (in 1st byte) are blocked out as unused + unsigned char b3 : 6; // 6 bits for b2 - doesn't fit into the 1st byte => starts a 2nd + unsigned char b4 : 2; // 2 bits for b3 - next (and final) bits in the 2nd byte + }; + + S s1, s2; + memset(&s1, 0, sizeof(S)); + memset(&s2, 42, sizeof(S)); + + s1.b1 = 5; + s2.b1 = 5; + s1.b2 = 3; + s2.b2 = 3; + s1.b3 = 27; + s2.b3 = 27; + s1.b4 = 3; + s2.b4 = 3; + __builtin_clear_padding2(&s2); + assert2(memcmp(&s1, &s2, sizeof(S)) == 0); + } + + testAllStructsForType<32, 16, char>(11, 22, 33, 44); + testAllStructsForType<64, 32, char>(4, 5, 6, 7); + testAllStructsForType<32, 16, volatile char>(11, 22, 33, 44); + testAllStructsForType<64, 32, volatile char>(4, 5, 6, 7); + testAllStructsForType<32, 16, int>(0, 1, 2, 3); + testAllStructsForType<64, 32, int>(4, 5, 6, 7); + testAllStructsForType<32, 16, volatile int>(0, 1, 2, 3); + testAllStructsForType<64, 32, volatile int>(4, 5, 6, 7); + testAllStructsForType<32, 16, double>(0, 1, 2, 3); + testAllStructsForType<64, 32, double>(4, 5, 6, 7); + testAllStructsForType<32, 16, _BitInt(28)>(0, 1, 2, 3); + testAllStructsForType<64, 32, _BitInt(28)>(4, 5, 6, 7); + testAllStructsForType<32, 16, _BitInt(60)>(0, 1, 2, 3); + testAllStructsForType<64, 32, _BitInt(60)>(4, 5, 6, 7); + testAllStructsForType<32, 16, _BitInt(64)>(0, 1, 2, 3); + testAllStructsForType<64, 32, _BitInt(64)>(4, 5, 6, 7); + testAllStructsForType<32, 16, Foo>(Foo{1, 2}, Foo{3, 4}, Foo{1, 2}, Foo{3, 4}); + testAllStructsForType<64, 32, Foo>(Foo{1, 2}, Foo{3, 4}, Foo{1, 2}, Foo{3, 4}); + testAllStructsForType<256, 128, Float3Vec>(0, 1, 2, 3); + testAllStructsForType<128, 128, Float3Vec>(4, 5, 6, 7); + testAllStructsForType<256, 128, Float4Vec>(0, 1, 2, 3); + testAllStructsForType<128, 128, Float4Vec>(4, 5, 6, 7); + + otherStructTests(); +} + +void unionTests() { + // different length, do not clear object repr bits of non-active member + { + union u { + int i; + char c; + }; + + u u1, u2; + memset(&u1, 42, sizeof(u)); + memset(&u2, 42, sizeof(u)); + u1.c = '4'; + u2.c = '4'; + + __builtin_clear_padding2(&u1); // should have no effect + assert2(u1.c == '4'); + + assert2(memcmp(&u1, &u2, sizeof(u)) == 0); + } + + // tail padding of longest member + { + struct s { + alignas(8) char c1; + }; + + union u { + s s1; + char c2; + }; + + u u1, u2; + memset(&u1, 42, sizeof(u)); + memset(&u2, 0, sizeof(u)); + + u1.s1.c1 = '4'; + u2.s1.c1 = '4'; + + assert2(memcmp(&u1, &u2, sizeof(u)) != 0); + __builtin_clear_padding2(&u1); + assert2(u1.s1.c1 == '4'); + assert2(memcmp(&u1, &u2, sizeof(u)) == 0); + } +} + +void arrayTests() { + // no padding + { + int i1[2] = {1, 2}; + int i2[2] = {1, 2}; + + __builtin_clear_padding2(&i1); + assert2(i1[0] == 1); + assert2(i1[1] == 2); + assert2(memcmp(&i1, &i2, 2 * sizeof(int)) == 0); + } + + // long double + { + long double d1[2], d2[2]; + memset(&d1, 42, 2 * sizeof(long double)); + memset(&d2, 0, 2 * sizeof(long double)); + + d1[0] = 3.0L; + d1[1] = 4.0L; + d2[0] = 3.0L; + d2[1] = 4.0L; + + __builtin_clear_padding2(&d1); + assert2(d1[0] == 3.0L); + assert2(d2[1] == 4.0L); + assert2(memcmp(&d1, &d2, 2 * sizeof(long double)) == 0); + } + + // struct + { + struct S { + int i1; + char c1; + int i2; + char c2; + }; + + S s1[2], s2[2]; + memset(&s1, 42, 2 * sizeof(S)); + memset(&s2, 0, 2 * sizeof(S)); + + s1[0].i1 = 1; + s1[0].c1 = 'a'; + s1[0].i2 = 2; + s1[0].c2 = 'b'; + s1[1].i1 = 3; + s1[1].c1 = 'c'; + s1[1].i2 = 4; + s1[1].c2 = 'd'; + + s2[0].i1 = 1; + s2[0].c1 = 'a'; + s2[0].i2 = 2; + s2[0].c2 = 'b'; + s2[1].i1 = 3; + s2[1].c1 = 'c'; + s2[1].i2 = 4; + s2[1].c2 = 'd'; + + assert2(memcmp(&s1, &s2, 2 * sizeof(S)) != 0); + __builtin_clear_padding2(&s1); + + assert2(s1[0].i1 == 1); + assert2(s1[0].c1 == 'a'); + assert2(s1[0].i2 == 2); + assert2(s1[0].c2 == 'b'); + assert2(s1[1].i1 == 3); + assert2(s1[1].c1 == 'c'); + assert2(s1[1].i2 == 4); + assert2(s1[1].c2 == 'd'); + assert2(memcmp(&s1, &s2, 2 * sizeof(S)) == 0); + } +} + +int main(int, const char**) { + primitiveTests(); + unionTests(); + structTests(); + arrayTests(); + + return 0; +}