From 73fbe6d2ed7e1e32c9bbc08279d80138ab77060f Mon Sep 17 00:00:00 2001 From: Alex Richardson Date: Thu, 21 Oct 2021 17:20:48 +0100 Subject: [PATCH] Set no_preserve_tags for copies of structs without capabilities This allows inlining of structure assignments for structs that are at least capability size but do not contain any capabilities (e.g. `struct { long a; long b; }`). We can also set the attribute for all trivial auto var-init cases since those patterns never contain valid capabilities. Due to C's effective type rules, we have to be careful when setting the attribute and only perform the type-base tag-preservation analysis if we know the effective type. For example, marking a memcpy() to/from `long*` as not tag-preserving could result in tag stripping for code that uses type casts. Such code is correct even under strict aliasing rules since the first store to a memory location determines the type. Example from https://github.com/CTSRD-CHERI/llvm-project/pull/506: ``` void *malloc(__SIZE_TYPE__); void *memcpy(void *, const void *, __SIZE_TYPE__); void foo(long **p, long **q) { *p = malloc(32); *q = malloc(32); (*p)[0] = 1; (*p)[1] = 2; *(void (**)(long **, long **))(*p + 2) = &foo; memcpy(*q, *p, 32); } ``` Despite the memcpy() argument being a long* (and therefore intuitively not tag preserving), we can't add the attribute since we don't actually know the type of the underlying object (malloc creates an allocated with no declared type). From C99: ``` The effective type of an object for an access to its stored value is the declared type of the object, if any (footnote 75: Allocated objects have no declared type). If a value is stored into an object having no declared type through an lvalue having a type that is not a character type, then the type of the lvalue becomes the effective type of the object for that access and for subsequent accesses that do not modify the stored value. If a value is copied into an object having no declared type using memcpy or memmove, or is copied as an array of character type, then the effective type of the modified object for that access and for subsequent accesses that do not modify the value is the effective type of the object from which the value is copied, if it has one. For all other accesses to an object having no declared type, the effective type of the object is simply the type of the lvalue used for the access. ``` There is another important caveat: we have to conservatively assume that the copy affects adjacent data (e.g. C++ subclass fields) that could hold capabilities if we don't know the copy size. If the copy size is <= sizeof(T), we can mark copies as non-tag-preserving since it cannot affect trailing fields (even if we are actually copying a subclass). We are also conservative if the structure contains an array of type ((un)signed) char or std::byte since those are often used to store arbitrary data (including capabilities). We could make this check more strict and require the array to be capability aligned, but that could be done as a follow-up change. --- clang/include/clang/AST/ASTContext.h | 11 ++ clang/lib/AST/ASTContext.cpp | 49 +++++ clang/lib/CodeGen/CGBuiltin.cpp | 106 +++++------ clang/lib/CodeGen/CGClass.cpp | 5 +- clang/lib/CodeGen/CGDecl.cpp | 30 +-- clang/lib/CodeGen/CGExprAgg.cpp | 15 +- clang/lib/CodeGen/CodeGenTypes.cpp | 134 +++++++++++++ clang/lib/CodeGen/CodeGenTypes.h | 37 ++++ .../CodeGen/cheri/cheri-inregs-param-info.c | 15 +- .../CodeGen/cheri/cheri-struct-return-value.c | 25 ++- clang/test/CodeGen/cheri/memcpy-unaligned.c | 180 +++++++++--------- .../no-tag-copy-attribute-nocap-struct.c | 37 ++-- .../cheri/no-tag-copy-attribute-with-caps.c | 89 ++++++--- .../cheri/no-tag-copy-strict-aliasing.c | 64 +++++++ .../cheri/subobject-bounds-structure-array.c | 22 ++- .../cheri/no-tag-copy-copy-ctor.cpp | 90 +++++++++ .../test/CodeGenCXX/trivial-auto-var-init.cpp | 29 ++- .../Mips/cheri/memcpy-unaligned-addrinfo.c | 92 ++++++--- 18 files changed, 783 insertions(+), 247 deletions(-) create mode 100644 clang/test/CodeGen/cheri/no-tag-copy-strict-aliasing.c create mode 100644 clang/test/CodeGenCXX/cheri/no-tag-copy-copy-ctor.cpp diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index 97a6391bc8a0..bfd106991f08 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -2210,6 +2210,7 @@ class ASTContext : public RefCountedBase { private: /// Map storing whether a type contains capabilities. mutable llvm::DenseMap ContainsCapabilities; + mutable llvm::DenseMap CannotContainCapabilities; CanQualType getFromTargetType(unsigned Type) const; TypeInfo getTypeInfoImpl(const Type *T) const; @@ -2478,6 +2479,16 @@ class ASTContext : public RefCountedBase { /// capability or an aggregate type that contains one or more capabilities. bool containsCapabilities(QualType Ty) const; + /// Returns true if the record type cannot contain capabilities. + /// NB: this is a conservative analysis that treats overaligned char arrays as + /// potentially containing capabilities. + bool cannotContainCapabilities(const RecordDecl *RD) const; + /// Returns true if the type is a scalar type that has a representationa + /// that cannot be used to (legally) store capabilities. + /// NB: this is a conservative analysis that treats overaligned char arrays as + /// potentially containing capabilities. + bool cannotContainCapabilities(QualType Ty) const; + /// Return true if the specified type has unique object representations /// according to (C++17 [meta.unary.prop]p9) bool hasUniqueObjectRepresentations(QualType Ty) const; diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 2cb8fd537f06..00f07b3484a6 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -11730,6 +11730,55 @@ bool ASTContext::containsCapabilities(QualType Ty) const { return Ret; } +bool ASTContext::cannotContainCapabilities(const RecordDecl *RD) const { + for (auto i = RD->field_begin(), e = RD->field_end(); i != e; ++i) { + const QualType Ty = i->getType(); + if (Ty->isCHERICapabilityType(*this)) + return false; + else if (const RecordType *RT = Ty->getAs()) { + if (!cannotContainCapabilities(RT->getDecl())) + return false; + } else if (!cannotContainCapabilities(Ty)) + return false; + } + // In the case of C++ classes, also check base classes + if (const CXXRecordDecl *CRD = dyn_cast(RD)) { + for (auto i = CRD->bases_begin(), e = CRD->bases_end(); i != e; ++i) { + const QualType Ty = i->getType(); + if (const RecordType *RT = Ty->getAs()) + if (!cannotContainCapabilities(RT->getDecl())) + return false; + } + } + return true; // Check all types that could contain capabilities +} + +bool ASTContext::cannotContainCapabilities(QualType Ty) const { + // If we've already looked up this type, then return the cached value. + auto Cached = CannotContainCapabilities.find(Ty.getAsOpaquePtr()); + if (Cached != CannotContainCapabilities.end()) + return Cached->second; + // Don't bother caching the trivial cases. + if (containsCapabilities(Ty)) + return false; + if (Ty->isArrayType()) { + QualType ElTy(Ty->getBaseElementTypeUnsafe(), 0); + // We have to be conservative here and assume that (unsigned) char[] as + // well as std::byte can be used for buffers that store capabilities. + // TODO: we could restrict this to buffers that are large enough and + // sufficiently aligned to store a capability. + if (ElTy->isCharType() || ElTy->isStdByteType()) + return false; + return cannotContainCapabilities(ElTy); + } + const RecordType *RT = Ty->getAs(); + if (!RT) + return false; + bool Ret = cannotContainCapabilities(RT->getDecl()); + CannotContainCapabilities[Ty.getAsOpaquePtr()] = Ret; + return Ret; +} + QualType ASTContext::getCorrespondingSaturatedType(QualType Ty) const { assert(Ty->isFixedPointType()); diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp index e38260116099..a12e4a080676 100644 --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -2164,18 +2164,17 @@ RValue CodeGenFunction::emitRotate(const CallExpr *E, bool IsRotateRight) { } // Diagnose misaligned copies (memmove/memcpy) of source types that contain -// capabilities to a dst buffer that is less than capability aligned. -// This can result in tags being lost at runtime if the buffer is not actually -// capability aligned. Furthermore, if the user adds a __builtin_assume_aligned() -// or a cast to a capability we can assume it is capability aligned an use -// csc/clc if the memcpy()/memmove() is expanded inline. +// capabilities to a dst buffer that is less than capability aligned. This can +// result in tags being lost at runtime if the buffer is not actually capability +// aligned. Another benefit of this diagnostic is that it can cause the the user +// to add __builtin_assume_aligned() or a cast to a capability. This allows us +// to potentially expand the memcpy()/memmove() inline. // TODO: maybe there needs to be an attribute __memmove_like__ or similar to // indicate that a function behaves like memmove/memcpy and we can use that // to diagnose unaligned copies. -static void -diagnoseMisalignedCapabiliyCopyDest(CodeGenFunction &CGF, StringRef Function, - const Expr *Src, const CharUnits DstAlignCU, - AnyMemTransferInst *MemInst = nullptr) { +static void checkCapabilityCopy(CodeGenFunction &CGF, StringRef Function, + const Expr *Src, const CharUnits DstAlignCU, + AnyMemTransferInst *MemInst = nullptr) { // we want the real type not the implicit conversion to void* // TODO: ignore the first explicit cast to void*? auto UnderlyingSrcTy = Src->IgnoreParenImpCasts()->getType(); @@ -2187,18 +2186,15 @@ diagnoseMisalignedCapabiliyCopyDest(CodeGenFunction &CGF, StringRef Function, if (!Ctx.containsCapabilities(UnderlyingSrcTy)) return; - // Add a must_preserve_cheri_tags attribute to the memcpy/memmove - // intrinsic to ensure that the backend will not lower it to an inlined - // sequence of 1/2/4/8 byte loads and stores which would strip the tag bits. - // TODO: a clc/csc that works on unaligned data but traps for a csc - // with a tagged value and unaligned address could also prevent tags - // from being lost. + // If we have a memory intrinsic, we let the backend diagnose this issue + // since the clang frontend rarely has enough information to correctly infer + // the alignment. if (MemInst) { - // If we have a memory intrinsic let the backend diagnose this issue: - // First, tell the backend that this copy must preserve tags - MemInst->addAttribute(llvm::AttributeList::FunctionIndex, - llvm::Attribute::MustPreserveCheriTags); - // And also tell it what the underlying type was for improved diagnostics. + // No need to diagnose anything if we aren't preserving tags. + if (MemInst->shouldPreserveCheriTags() == PreserveCheriTags::Unnecessary) + return; + // Add a "frontend-memtransfer-type" attribute to the intrinsic + // to ensure that the backend can diagnose misaligned capability copies. std::string TypeName = UnderlyingSrcTy.getAsString(); std::string CanonicalStr = UnderlyingSrcTy.getCanonicalType().getAsString(); if (CanonicalStr != TypeName) @@ -2255,23 +2251,20 @@ diagnoseMisalignedCapabiliyCopyDest(CodeGenFunction &CGF, StringRef Function, } } -static void diagnoseMisalignedCapabiliyCopyDest(CodeGenFunction &CGF, - StringRef Function, - const Expr *Src, CallInst *CI) { +static void checkCapabilityCopy(CodeGenFunction &CGF, StringRef Function, + const Expr *Src, CallInst *CI) { AnyMemTransferInst *MemInst = cast(CI); - diagnoseMisalignedCapabiliyCopyDest( - CGF, Function, Src, CharUnits::fromQuantity(MemInst->getDestAlignment()), - MemInst); + checkCapabilityCopy(CGF, Function, Src, + CharUnits::fromQuantity(MemInst->getDestAlignment()), + MemInst); } -static void diagnoseMisalignedCapabiliyCopyDest(CodeGenFunction &CGF, - StringRef Function, - const Expr *Src, - const Expr *Dst) { +static void checkCapabilityCopy(CodeGenFunction &CGF, StringRef Function, + const Expr *Src, const Expr *Dst) { auto UnderlyingDstTy = QualType( Dst->IgnoreImpCasts()->getType()->getPointeeOrArrayElementType(), 0); - diagnoseMisalignedCapabiliyCopyDest( - CGF, Function, Src, CGF.CGM.getNaturalTypeAlignment(UnderlyingDstTy)); + checkCapabilityCopy(CGF, Function, Src, + CGF.CGM.getNaturalTypeAlignment(UnderlyingDstTy)); } // Map math builtins for long-double to f128 version. @@ -3490,9 +3483,11 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID, E->getArg(0)->getExprLoc(), FD, 0); EmitNonNullArgCheck(RValue::get(Src.getPointer()), E->getArg(1)->getType(), E->getArg(1)->getExprLoc(), FD, 1); - auto CI = Builder.CreateMemCpy(Dest, Src, SizeVal, - llvm::PreserveCheriTags::TODO, false); - diagnoseMisalignedCapabiliyCopyDest(*this, "memcpy", E->getArg(1), CI); + auto CI = Builder.CreateMemCpy( + Dest, Src, SizeVal, + getTypes().copyShouldPreserveTags(E->getArg(0), E->getArg(1), SizeVal), + false); + checkCapabilityCopy(*this, "memcpy", E->getArg(1), CI); if (BuiltinID == Builtin::BImempcpy || BuiltinID == Builtin::BI__builtin_mempcpy) return RValue::get(Builder.CreateInBoundsGEP(Dest.getElementType(), @@ -3511,7 +3506,10 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID, E->getArg(0)->getExprLoc(), FD, 0); EmitNonNullArgCheck(RValue::get(Src.getPointer()), E->getArg(1)->getType(), E->getArg(1)->getExprLoc(), FD, 1); - Builder.CreateMemCpyInline(Dest, Src, Size); + Builder.CreateMemCpyInline( + Dest, Src, Size, + getTypes().copyShouldPreserveTags(E->getArg(0), E->getArg(1), + CharUnits::fromQuantity(Size))); return RValue::get(nullptr); } @@ -3524,23 +3522,23 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID, Expr::EvalResult SizeResult, DstSizeResult; if (!E->getArg(2)->EvaluateAsInt(SizeResult, CGM.getContext()) || !E->getArg(3)->EvaluateAsInt(DstSizeResult, CGM.getContext())) { - diagnoseMisalignedCapabiliyCopyDest(*this, "__memcpy_chk", E->getArg(1), - E->getArg(0)); + checkCapabilityCopy(*this, "__memcpy_chk", E->getArg(1), E->getArg(0)); break; } llvm::APSInt Size = SizeResult.Val.getInt(); llvm::APSInt DstSize = DstSizeResult.Val.getInt(); if (Size.ugt(DstSize)) { - diagnoseMisalignedCapabiliyCopyDest(*this, "__memcpy_chk", E->getArg(1), - E->getArg(0)); + checkCapabilityCopy(*this, "__memcpy_chk", E->getArg(1), E->getArg(0)); break; } Address Dest = EmitPointerWithAlignment(E->getArg(0)); Address Src = EmitPointerWithAlignment(E->getArg(1)); Value *SizeVal = llvm::ConstantInt::get(Builder.getContext(), Size); - auto CI = Builder.CreateMemCpy(Dest, Src, SizeVal, - llvm::PreserveCheriTags::TODO, false); - diagnoseMisalignedCapabiliyCopyDest(*this, "memcpy", E->getArg(1), CI); + auto CI = Builder.CreateMemCpy( + Dest, Src, SizeVal, + getTypes().copyShouldPreserveTags(E->getArg(0), E->getArg(1), SizeVal), + false); + checkCapabilityCopy(*this, "memcpy", E->getArg(1), CI); return RValue::get(Dest.getPointer(), Dest.getAlignment().getQuantity()); } @@ -3559,23 +3557,23 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID, Expr::EvalResult SizeResult, DstSizeResult; if (!E->getArg(2)->EvaluateAsInt(SizeResult, CGM.getContext()) || !E->getArg(3)->EvaluateAsInt(DstSizeResult, CGM.getContext())) { - diagnoseMisalignedCapabiliyCopyDest(*this, "__memmove_chk", E->getArg(1), - E->getArg(0)); + checkCapabilityCopy(*this, "__memmove_chk", E->getArg(1), E->getArg(0)); break; } llvm::APSInt Size = SizeResult.Val.getInt(); llvm::APSInt DstSize = DstSizeResult.Val.getInt(); if (Size.ugt(DstSize)) { - diagnoseMisalignedCapabiliyCopyDest(*this, "__memmove_chk", E->getArg(1), - E->getArg(0)); + checkCapabilityCopy(*this, "__memmove_chk", E->getArg(1), E->getArg(0)); break; } Address Dest = EmitPointerWithAlignment(E->getArg(0)); Address Src = EmitPointerWithAlignment(E->getArg(1)); Value *SizeVal = llvm::ConstantInt::get(Builder.getContext(), Size); - auto CI = Builder.CreateMemMove(Dest, Src, SizeVal, - llvm::PreserveCheriTags::TODO, false); - diagnoseMisalignedCapabiliyCopyDest(*this, "memmove", E->getArg(1), CI); + auto CI = Builder.CreateMemMove( + Dest, Src, SizeVal, + getTypes().copyShouldPreserveTags(E->getArg(0), E->getArg(1), SizeVal), + false); + checkCapabilityCopy(*this, "memmove", E->getArg(1), CI); return RValue::get(Dest.getPointer(), Dest.getAlignment().getQuantity()); } @@ -3588,9 +3586,11 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID, E->getArg(0)->getExprLoc(), FD, 0); EmitNonNullArgCheck(RValue::get(Src.getPointer()), E->getArg(1)->getType(), E->getArg(1)->getExprLoc(), FD, 1); - auto CI = Builder.CreateMemMove(Dest, Src, SizeVal, - llvm::PreserveCheriTags::TODO, false); - diagnoseMisalignedCapabiliyCopyDest(*this, "memmove", E->getArg(1), CI); + auto CI = Builder.CreateMemMove( + Dest, Src, SizeVal, + getTypes().copyShouldPreserveTags(E->getArg(0), E->getArg(1), SizeVal), + false); + checkCapabilityCopy(*this, "memmove", E->getArg(1), CI); return RValue::get(Dest.getPointer(), Dest.getAlignment().getQuantity()); } case Builtin::BImemset: diff --git a/clang/lib/CodeGen/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp index c665c8569d98..954521fde3f0 100644 --- a/clang/lib/CodeGen/CGClass.cpp +++ b/clang/lib/CodeGen/CGClass.cpp @@ -980,10 +980,13 @@ namespace { LValue SrcLV = CGF.MakeNaturalAlignAddrLValue(SrcPtr, RecordTy); LValue Src = CGF.EmitLValueForFieldInitialization(SrcLV, FirstField); + // We can pass EffectiveTypeKnown=true since this a C++ field copy. emitMemcpyIR( Dest.isBitField() ? Dest.getBitFieldAddress() : Dest.getAddress(CGF), Src.isBitField() ? Src.getBitFieldAddress() : Src.getAddress(CGF), - MemcpySize, llvm::PreserveCheriTags::TODO); + MemcpySize, + CGF.getTypes().copyShouldPreserveTagsForPointee( + RecordTy, /*EffectiveTypeKnown=*/true, MemcpySize)); reset(); } diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp index 22d9c0058f07..211a145809d7 100644 --- a/clang/lib/CodeGen/CGDecl.cpp +++ b/clang/lib/CodeGen/CGDecl.cpp @@ -1265,12 +1265,18 @@ static void emitStoresForConstant(CodeGenModule &CGM, const VarDecl &D, } } - // Copy from a global. - auto *I = - Builder.CreateMemCpy(Loc, - createUnnamedGlobalForMemcpyFrom( - CGM, D, Builder, constant, Loc.getAlignment()), - SizeVal, llvm::PreserveCheriTags::TODO, isVolatile); + // Copy from a global (and therefore the effective type of the variable is + // known). + auto *I = Builder.CreateMemCpy( + Loc, + createUnnamedGlobalForMemcpyFrom(CGM, D, Builder, constant, + Loc.getAlignment()), + SizeVal, + IsAutoInit || !ContainsCaps + ? llvm::PreserveCheriTags::Unnecessary + : CGM.getTypes().copyShouldPreserveTagsForPointee( + D.getType(), /*EffectiveTypeKnown=*/true, SizeVal), + isVolatile); if (IsAutoInit) I->addAnnotationMetadata("auto-init"); } @@ -1799,11 +1805,13 @@ void CodeGenFunction::emitZeroOrPatternForAutoVarInit(QualType type, llvm::PHINode *Cur = Builder.CreatePHI(Begin.getType(), 2, "vla.cur"); Cur->addIncoming(Begin.getPointer(), OriginBB); CharUnits CurAlign = Loc.getAlignment().alignmentOfArrayElement(EltSize); - auto *I = Builder.CreateMemCpy( - Address(Cur, CurAlign), - createUnnamedGlobalForMemcpyFrom(CGM, D, Builder, Constant, - ConstantAlign), - BaseSizeInChars, llvm::PreserveCheriTags::TODO, isVolatile); + auto *I = + Builder.CreateMemCpy(Address(Cur, CurAlign), + createUnnamedGlobalForMemcpyFrom( + CGM, D, Builder, Constant, ConstantAlign), + BaseSizeInChars, + /* pattern init never has tags */ + llvm::PreserveCheriTags::Unnecessary, isVolatile); I->addAnnotationMetadata("auto-init"); llvm::Value *Next = Builder.CreateInBoundsGEP(Int8Ty, Cur, BaseSizeInChars, "vla.next"); diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp index 5a1bbbef2012..fb0bed0c547c 100644 --- a/clang/lib/CodeGen/CGExprAgg.cpp +++ b/clang/lib/CodeGen/CGExprAgg.cpp @@ -747,7 +747,9 @@ void AggExprEmitter::VisitCastExpr(CastExpr *E) { llvm::Value *SizeVal = llvm::ConstantInt::get( CGF.SizeTy, CGF.getContext().getTypeSizeInChars(E->getType()).getQuantity()); - Builder.CreateMemCpy(DestAddress, SourceAddress, SizeVal); + Builder.CreateMemCpy( + DestAddress, SourceAddress, SizeVal, + CGF.getTypes().copyShouldPreserveTags(E, E->getSubExpr(), SizeVal)); break; } @@ -2168,9 +2170,14 @@ void CodeGenFunction::EmitAggregateCopy(LValue Dest, LValue Src, QualType Ty, } } } - - auto Inst = Builder.CreateMemCpy(DestPtr, SrcPtr, SizeVal, - llvm::PreserveCheriTags::TODO, isVolatile); + // Note: this is used for expressions such as x = y, and not memcpy() calls, + // so according to C2x 6.5 "the effective type of the object is simply + // the type of the lvalue used for the access." + auto Inst = + Builder.CreateMemCpy(DestPtr, SrcPtr, SizeVal, + getTypes().copyShouldPreserveTagsForPointee( + Ty, /*EffectiveTypeKnown=*/true, SizeVal), + isVolatile); // Determine the metadata to describe the position of any padding in this // memcpy, as well as the TBAA tags for the members of the struct, in case diff --git a/clang/lib/CodeGen/CodeGenTypes.cpp b/clang/lib/CodeGen/CodeGenTypes.cpp index 08c723a02357..b2063501d8c9 100644 --- a/clang/lib/CodeGen/CodeGenTypes.cpp +++ b/clang/lib/CodeGen/CodeGenTypes.cpp @@ -952,6 +952,140 @@ bool CodeGenTypes::isZeroInitializable(QualType T) { return true; } +static bool copiesAtMostTypeSize(const QualType Ty, const ASTContext &Context, + Optional Size) { + if (!Size) + return false; + auto TypeSize = Context.getTypeSizeInCharsIfKnown(Ty); + return TypeSize && Size <= TypeSize; +} + +llvm::PreserveCheriTags +CodeGenTypes::copyShouldPreserveTags(const Expr *DestPtr, const Expr *SrcPtr, + Optional Size) { + // Don't add the no_preserve_tags/must_preserve_tags attribute for non-CHERI + // targets to avoid changing tests and to avoid compile-time impact. + if (!Context.getTargetInfo().SupportsCapabilities()) + return llvm::PreserveCheriTags::Unknown; + auto DstPreserve = copyShouldPreserveTags(DestPtr, Size); + if (DstPreserve == llvm::PreserveCheriTags::Unnecessary) { + // If the destination does not need to preserve tags, we know that we don't + // need to retain tags even if the source is a capability type. + return llvm::PreserveCheriTags::Unnecessary; + } + assert(DstPreserve == llvm::PreserveCheriTags::Required || + DstPreserve == llvm::PreserveCheriTags::Unknown); + auto SrcPreserve = copyShouldPreserveTags(SrcPtr, Size); + if (SrcPreserve == llvm::PreserveCheriTags::Unnecessary) { + // If the copy source never contains capabilities, we don't need to retain + // tags even if the destination is contains capabilities. + return llvm::PreserveCheriTags::Unnecessary; + } + if (SrcPreserve == llvm::PreserveCheriTags::Required) { + // If the source is capability-bearing but the destination is Unknown, we + // assume that we should be preserving tags. + // TODO: if this is too conservative, we could probably use the destination + // value (Unknown or Required) instead, but that will result in fewer + // (potentially) underaligned copies being diagnosed. + return llvm::PreserveCheriTags::Required; + } + assert(SrcPreserve == llvm::PreserveCheriTags::Unknown); + // Source preservation kind is unknown -> use the destination value. + return DstPreserve; +} + +llvm::PreserveCheriTags +CodeGenTypes::copyShouldPreserveTags(const Expr *E, Optional Size) { + assert(E->getType()->isAnyPointerType()); + // Ignore the implicit cast to void* for the memcpy call. + // Note: IgnoreParenImpCasts() might strip function/array-to-pointer decay + // so we can't always call getPointeeType(). + QualType Ty = E->IgnoreParenImpCasts()->getType(); + if (Ty->isAnyPointerType()) + Ty = Ty->getPointeeType(); + // TODO: Find the underlying VarDecl to improve diagnostics + const VarDecl *UnderlyingVar = nullptr; + // TODO: this assertion may be overly aggressive. + assert((!UnderlyingVar || UnderlyingVar->getType() == Ty) && + "Passed wrong VarDecl?"); + // If we have an underlying VarDecl, we can assume that the dynamic type of + // the object is known and can perform more detailed analysis. + return copyShouldPreserveTagsForPointee(Ty, UnderlyingVar != nullptr, Size); +} + +llvm::PreserveCheriTags CodeGenTypes::copyShouldPreserveTagsForPointee( + QualType Pointee, bool EffectiveTypeKnown, Optional Size) { + // Don't add the no_preserve_tags/must_preserve_tags attribute for non-CHERI + // targets to avoid changing tests and to avoid compile-time impact. + if (!Context.getTargetInfo().SupportsCapabilities()) + return llvm::PreserveCheriTags::Unknown; + assert(!Pointee.isNull() && "Should only be called for valid types"); + if (Context.containsCapabilities(Pointee)) { + // If this is a capability type or a structure/union containing + // capabilities, we clearly need to retain tag bits when copying. + // TODO: we should consider removing the require attribute since the + // backends have to be conservative on a missing no_preserve_cheri_tags + // anyway, so not having the attribute should be equivalent to "Required". + return llvm::PreserveCheriTags::Required; + } else if (!EffectiveTypeKnown) { + // If we don't know the underlying type of the copy (i.e. we just have a + // pointer without any additional context), we cannot assume that the actual + // object at that location matches the type of the pointer, so we have to + // conservatively return Unknown if containsCapabilities() was false. + // This is needed because C's strict aliasing rules are not based on the + // type of the pointer but rather based on the type of what was last stored. + // See C2x 6.5: + // "If a value is copied into an object having no declared type using memcpy + // or memmove, or is copied as an array of character type, then the + // effective type of the modified object for that access and for subsequent + // accesses that do not modify the value is the effective type of the object + // from which the value is copied, if it has one. For all other accesses to + // an object having no declared type, the effective type of the object is + // simply the type of the lvalue used for the access." + // And importantly: "Allocated objects have no declared type.", so unless + // we know what the underlying VarDecl is, we cannot use the type of the + // expression to determine whether it could hold tags. + return llvm::PreserveCheriTags::Unknown; + } else if (Pointee->isIncompleteType()) { + // We don't know if incomplete types contain capabilities, so be + // conservative and assume that they might. + return llvm::PreserveCheriTags::Unknown; + } else if (auto *RD = Pointee->getAsRecordDecl()) { + // For C++ classes, there could be a subclass that contains capabilities, + // so we have to be conservative unless the class is final. Similarly, we + // have to be conservative in C as the struct could be embedded inside + // another structure and the copy could affect adjacent capability data.s + // If the copy size is <= sizeof(T), we can still mark copies as + // non-tag-preserving since it cannot affect subclass/adjacent data. + if (!copiesAtMostTypeSize(Pointee, Context, Size)) + return llvm::PreserveCheriTags::Unknown; + // structures without fields could be used as an opaque type -> assume it + // might contain capabilities + if (RD->field_empty()) + return llvm::PreserveCheriTags::Unknown; + // Otherwise we have a non-empty structure/union without variable-size + // arrays that does not have any capability fields -> the copy does not + // need to retain tags. + // This is useful to optimize cases such as assignment of something like + // `struct { long a; long b; }`: + // Since the type is as large as a capability but the known alignment is + // only 4/8 bytes, the backend needs to be conservative and assume that + // the memcpy might contain capabilities. By annotating the memcpy/memmove + // intrinsic, the backend can emit non-capability loads inline instead of + // having to call the library function. + return llvm::PreserveCheriTags::Unnecessary; + } else if (Context.cannotContainCapabilities(Pointee) && + copiesAtMostTypeSize(Pointee, Context, Size)) { + // If the type cannot contain capabilities and we are copying at most + // sizeof(type), then we can use a non-tag-preserving copy. + return llvm::PreserveCheriTags::Unnecessary; + } + // This is some other type that might contain capabilities (e.g. char[]) or + // a copy that covers a larger region than the size of the underlying type. + // In that case, fall back to the default (retaining tags if possible). + return llvm::PreserveCheriTags::Unknown; +} + bool CodeGenTypes::isZeroInitializable(const RecordDecl *RD) { return getCGRecordLayout(RD).isZeroInitializable(); } diff --git a/clang/lib/CodeGen/CodeGenTypes.h b/clang/lib/CodeGen/CodeGenTypes.h index 99bf091606a2..23cc0e8d7299 100644 --- a/clang/lib/CodeGen/CodeGenTypes.h +++ b/clang/lib/CodeGen/CodeGenTypes.h @@ -25,6 +25,7 @@ class DataLayout; class Type; class LLVMContext; class StructType; +enum class PreserveCheriTags; } namespace clang { @@ -303,6 +304,34 @@ class CodeGenTypes { /// zero-initialized (in the C++ sense) with an LLVM zeroinitializer. bool isZeroInitializable(const RecordDecl *RD); + /// Return whether a copy (e.g. memcpy/memmove) where the destination is a + /// pointer to DestType may need to preserve CHERI tags (i.e. needs to call + /// the copy function at run time if the alignment is not greater than the + /// alignment of the destination buffer. + /// This function attempts to determine the effective type of the source and + /// destination values (C2x 6.5p6) by checking for the underlying storage + /// (e.g. a referenced VarDecl) and performing a more conservative analysis + /// if this is not the case. + llvm::PreserveCheriTags copyShouldPreserveTags(const Expr *DestPtr, + const Expr *SrcPtr, + Optional Size); + llvm::PreserveCheriTags copyShouldPreserveTags(const Expr *DestPtr, + const Expr *SrcPtr, + const llvm::Value *Size) { + return copyShouldPreserveTags(DestPtr, SrcPtr, copySizeInCharUnits(Size)); + } + /// Same as the copyShouldPreserveTags(), but expects CopyTy to be the + /// pointee type rather than the type of the buffer pointer. + llvm::PreserveCheriTags + copyShouldPreserveTagsForPointee(QualType CopyTy, bool EffectiveTypeKnown, + Optional Size); + llvm::PreserveCheriTags + copyShouldPreserveTagsForPointee(QualType CopyTy, bool EffectiveTypeKnown, + const llvm::Value *Size) { + return copyShouldPreserveTagsForPointee(CopyTy, EffectiveTypeKnown, + copySizeInCharUnits(Size)); + } + bool isRecordLayoutComplete(const Type *Ty) const; bool noRecordsBeingLaidOut() const { return RecordsBeingLaidOut.empty(); @@ -311,6 +340,14 @@ class CodeGenTypes { return RecordsBeingLaidOut.count(Ty); } +private: + llvm::PreserveCheriTags copyShouldPreserveTags(const Expr *E, + Optional Size); + Optional copySizeInCharUnits(const llvm::Value *Size) { + if (auto *ConstSize = llvm::dyn_cast_or_null(Size)) + return CharUnits::fromQuantity(ConstSize->getSExtValue()); + return None; + } }; } // end namespace CodeGen diff --git a/clang/test/CodeGen/cheri/cheri-inregs-param-info.c b/clang/test/CodeGen/cheri/cheri-inregs-param-info.c index 91cb39587c81..1f9006ccc3b8 100644 --- a/clang/test/CodeGen/cheri/cheri-inregs-param-info.c +++ b/clang/test/CodeGen/cheri/cheri-inregs-param-info.c @@ -1,6 +1,7 @@ // NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --function-signature -// RUN: %cheri_purecap_cc1 -emit-llvm -O2 %s -o - | FileCheck %s -// RUN: %cheri_purecap_cc1 -S -O2 %s -o - | FileCheck %s --check-prefix ASM +// REQUIRES: mips-registered-target +// RUN: %cheri_purecap_cc1 -emit-llvm -O2 %s -o - -verify | FileCheck %s +// RUN: %cheri_purecap_cc1 -S -O2 %s -o - -verify | FileCheck %s --check-prefix ASM // This used to crash with an assertion: // void llvm::CCState::getInRegsParamInfo(unsigned int, unsigned int &, unsigned int &) const: Assertion `InRegsParamRecordIndex < ByValRegs.size() && "Wrong ByVal parameter index"' failed. @@ -8,18 +9,22 @@ // ASM: clcbi $c12, %capcall20(memcpy)( typedef struct { int err_msg[1024]; } Dwarf_Error; extern Dwarf_Error a; -void fn2(); +void fn2(); // expected-note{{candidate function declaration needs parameter types}} // CHECK-LABEL: define {{[^@]+}}@fn1 // CHECK-SAME: () local_unnamed_addr addrspace(200) #[[ATTR0:[0-9]+]] { // CHECK-NEXT: entry: // CHECK-NEXT: [[BYVAL_TEMP:%.*]] = alloca [[STRUCT_DWARF_ERROR:%.*]], align 8, addrspace(200) // CHECK-NEXT: [[TMP0:%.*]] = bitcast [[STRUCT_DWARF_ERROR]] addrspace(200)* [[BYVAL_TEMP]] to i8 addrspace(200)* // CHECK-NEXT: call void @llvm.lifetime.start.p200i8(i64 4096, i8 addrspace(200)* nonnull [[TMP0]]) #[[ATTR3:[0-9]+]] -// CHECK-NEXT: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* noundef nonnull align 8 dereferenceable(4096) [[TMP0]], i8 addrspace(200)* noundef nonnull align 4 dereferenceable(4096) bitcast ([[STRUCT_DWARF_ERROR]] addrspace(200)* @a to i8 addrspace(200)*), i64 4096, i1 false), !tbaa.struct !2 +// CHECK-NEXT: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* noundef nonnull align 8 dereferenceable(4096) [[TMP0]], i8 addrspace(200)* noundef nonnull align 4 dereferenceable(4096) bitcast ([[STRUCT_DWARF_ERROR]] addrspace(200)* @a to i8 addrspace(200)*), i64 4096, i1 false) #[[ATTR4:[0-9]+]], !tbaa.struct !2 // CHECK-NEXT: tail call void bitcast (void (...) addrspace(200)* @fn2 to void ([[STRUCT_DWARF_ERROR]] addrspace(200)*) addrspace(200)*)([[STRUCT_DWARF_ERROR]] addrspace(200)* nonnull byval([[STRUCT_DWARF_ERROR]]) align 8 [[BYVAL_TEMP]]) #[[ATTR3]] // CHECK-NEXT: call void @llvm.lifetime.end.p200i8(i64 4096, i8 addrspace(200)* nonnull [[TMP0]]) #[[ATTR3]] // CHECK-NEXT: ret void // void fn1() { - fn2(a); + fn2(a); // expected-warning{{call to function 'fn2' with no prototype may lead to run-time stack corruption}} + // expected-note@-1{{Calling functions without prototypes is dangerous}} } + +// UTC_ARGS: --disable +// CHECK: attributes #[[ATTR4]] = { no_preserve_cheri_tags } diff --git a/clang/test/CodeGen/cheri/cheri-struct-return-value.c b/clang/test/CodeGen/cheri/cheri-struct-return-value.c index 6ee6b7bed475..45cf0d98a644 100644 --- a/clang/test/CodeGen/cheri/cheri-struct-return-value.c +++ b/clang/test/CodeGen/cheri/cheri-struct-return-value.c @@ -1,5 +1,6 @@ // NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --function-signature // REQUIRES: mips-registered-target +// FIXME: this should not be a clang test since it's checking ASM output // RUN: %cheri_purecap_cc1 -std=c11 -O2 -emit-llvm -o - %s | %cheri_FileCheck %s // RUN: %cheri_purecap_cc1 -mllvm -cheri-cap-table-abi=pcrel -std=c11 -O2 -S -o - %s | %cheri_FileCheck -check-prefixes=ASM,CHERI128-ASM %s @@ -141,18 +142,23 @@ typedef struct { // CHECK-SAME: ([[STRUCT_THREELONGS:%.*]] addrspace(200)* noalias nocapture sret([[STRUCT_THREELONGS]]) align 8 [[AGG_RESULT:%.*]]) local_unnamed_addr addrspace(200) #[[ATTR3:[0-9]+]] { // CHECK-NEXT: entry: // CHECK-NEXT: [[TMP0:%.*]] = bitcast [[STRUCT_THREELONGS]] addrspace(200)* [[AGG_RESULT]] to i8 addrspace(200)* -// CHECK-NEXT: tail call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* noundef nonnull align 8 dereferenceable(24) [[TMP0]], i8 addrspace(200)* noundef nonnull align 8 dereferenceable(24) bitcast ([[STRUCT_THREELONGS]] addrspace(200)* @__const.three_longs.t to i8 addrspace(200)*), i64 24, i1 false) +// CHECK-NEXT: tail call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* noundef nonnull align 8 dereferenceable(24) [[TMP0]], i8 addrspace(200)* noundef nonnull align 8 dereferenceable(24) bitcast ([[STRUCT_THREELONGS]] addrspace(200)* @__const.three_longs.t to i8 addrspace(200)*), i64 24, i1 false) #[[ATTR6:[0-9]+]] // CHECK-NEXT: ret void // ThreeLongs three_longs() { ThreeLongs t = { 1, 2, 3 }; return t; // ASM-LABEL: three_longs - // Clang uses a memcpy from a global for cheri128. - // For cheri256 it inlined the memcpy from a global (since it is smaller than 1 cap) - // CHERI128-ASM: clcbi $c4, %captab20(.L__const.three_longs.t)($c{{.+}}) - // CHERI128-ASM: clcbi $c12, %capcall20(memcpy)($c{{.+}}) - + // Clang uses a memcpy from a global for cheri128 (and it can be inlined despite being less than cap aligned + // since longs don't contain tags): + // CHERI128-ASM: clcbi $c1, %captab20(.L__const.three_longs.t)($c1) + // CHERI128-ASM-NEXT: cld $1, $zero, 16($c1) + // CHERI128-ASM-NEXT: cld $2, $zero, 8($c1) + // CHERI128-ASM-NEXT: cld $3, $zero, 0($c1) + // CHERI128-ASM-NEXT: csd $1, $zero, 16($c3) + // CHERI128-ASM-NEXT: csd $2, $zero, 8($c3) + // CHERI128-ASM-NEXT: cjr $c17 + // CHERI128-ASM-NEXT: csd $3, $zero, 0($c3) } typedef struct { @@ -193,7 +199,7 @@ extern IntAndLong extern_int_and_long(); // CHECK-LABEL: define {{[^@]+}}@read_int_and_long_1 // CHECK-SAME: () local_unnamed_addr addrspace(200) #[[ATTR4:[0-9]+]] { // CHECK-NEXT: entry: -// CHECK-NEXT: [[CALL:%.*]] = tail call inreg { i64, i64 } bitcast ({ i64, i64 } (...) addrspace(200)* @extern_int_and_long to { i64, i64 } () addrspace(200)*)() #[[ATTR6:[0-9]+]] +// CHECK-NEXT: [[CALL:%.*]] = tail call inreg { i64, i64 } bitcast ({ i64, i64 } (...) addrspace(200)* @extern_int_and_long to { i64, i64 } () addrspace(200)*)() #[[ATTR7:[0-9]+]] // CHECK-NEXT: [[TMP0:%.*]] = extractvalue { i64, i64 } [[CALL]], 0 // CHECK-NEXT: [[COERCE_SROA_0_0_EXTRACT_SHIFT:%.*]] = lshr i64 [[TMP0]], 32 // CHECK-NEXT: [[COERCE_SROA_0_0_EXTRACT_TRUNC:%.*]] = trunc i64 [[COERCE_SROA_0_0_EXTRACT_SHIFT]] to i32 @@ -217,7 +223,7 @@ int read_int_and_long_1() { // CHECK-LABEL: define {{[^@]+}}@read_int_and_long_2 // CHECK-SAME: () local_unnamed_addr addrspace(200) #[[ATTR4]] { // CHECK-NEXT: entry: -// CHECK-NEXT: [[CALL:%.*]] = tail call inreg { i64, i64 } bitcast ({ i64, i64 } (...) addrspace(200)* @extern_int_and_long to { i64, i64 } () addrspace(200)*)() #[[ATTR6]] +// CHECK-NEXT: [[CALL:%.*]] = tail call inreg { i64, i64 } bitcast ({ i64, i64 } (...) addrspace(200)* @extern_int_and_long to { i64, i64 } () addrspace(200)*)() #[[ATTR7]] // CHECK-NEXT: [[TMP0:%.*]] = extractvalue { i64, i64 } [[CALL]], 1 // CHECK-NEXT: ret i64 [[TMP0]] // @@ -607,3 +613,6 @@ Int128AndCap int128_and_cap(Int128AndCap in) { // ASM-NEXT: cjr $c17 // ASM-NEXT: csc $c1, $zero, 16($c3) } + +// UTC_ARGS: --disabble +// CHECK: attributes #[[ATTR6]] = { no_preserve_cheri_tags } diff --git a/clang/test/CodeGen/cheri/memcpy-unaligned.c b/clang/test/CodeGen/cheri/memcpy-unaligned.c index 6924b93d7b8d..f5bfb176eaaf 100644 --- a/clang/test/CodeGen/cheri/memcpy-unaligned.c +++ b/clang/test/CodeGen/cheri/memcpy-unaligned.c @@ -1,31 +1,36 @@ // REQUIRES: mips-registered-target +// Note: some alignment attributes are lower than expected since +// __attribute__((align_value(2))) is not propagated to the +// llvm.memcpy intrinsic until later optimization passes. // RUN: %cheri128_purecap_cc1 -O0 -o - -emit-llvm %s -w | FileCheck %s -// RUN: %cheri128_purecap_cc1 -DBUILTIN -O0 -o - -emit-llvm %s -w | FileCheck %s // This diagnostic is disabled at -O0 -> must check at -O2 // RUN: %cheri128_purecap_cc1 -debug-info-kind=standalone -O2 -S -o /dev/null %s -verify -// RUN: %cheri128_purecap_cc1 -debug-info-kind=standalone -DBUILTIN -O2 -S -o /dev/null %s -verify // If we are using -Werror this warning should not fail the build, only if it is explicitly added: // RUN: %cheri128_purecap_cc1 -O2 -o /dev/null -S %s -Werror 2> /dev/null // RUN: not %cheri128_purecap_cc1 -O2 -o /dev/null -S %s -Werror=cheri-misaligned 2>/dev/null -#ifdef BUILTIN -#define memcpy __builtin_memcpy -#define memmove __builtin_memmove -#else -void * memcpy(void *, const void *, unsigned long); -void * memmove(void *, const void *, unsigned long); -#endif +void *memcpy(void *, const void *, unsigned long); +void *memmove(void *, const void *, unsigned long); typedef unsigned __intcap a; void *b; - +typedef void *__attribute__((align_value(2))) align2_ptr; +typedef void *__attribute__((align_value(4))) align4_ptr; +typedef void *__attribute__((align_value(8))) align8_ptr; +typedef void *__attribute__((align_value(sizeof(void *__capability)))) align_cap_ptr; unsigned __intcap get_cap(void); -void test_dst_unliagned_src_cap_memcpy(void* align1, short* align2, int* align4, long* align8, void** align_cap, a* src) { - // CHECK-LABEL @test_dst_unliagned_src_cap_memcpy( +void test_dst_unliagned_src_cap_memcpy(void *align1, align2_ptr align2, align4_ptr align4, align8_ptr align8, align_cap_ptr align_cap, a *src) { + // CHECK-LABEL: @test_dst_unliagned_src_cap_memcpy( + // CHECK-SAME: i8 addrspace(200)* %align1, + // CHECK-SAME: i8 addrspace(200)* align 2 %align2, + // CHECK-SAME: i8 addrspace(200)* align 4 %align4, + // CHECK-SAME: i8 addrspace(200)* align 8 %align8, + // CHECK-SAME: i8 addrspace(200)* align 16 %align_cap, + // CHECK-SAME: i8 addrspace(200)* addrspace(200)* %src) memcpy(align1, src, sizeof(*src)); // expected-warning@-1{{memcpy operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} @@ -37,23 +42,24 @@ void test_dst_unliagned_src_cap_memcpy(void* align1, short* align2, int* align4, // expected-warning@-1{{memcpy operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 2 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 2 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] + // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} memcpy(align4, src, sizeof(*src)); // expected-warning@-1{{memcpy operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 4 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 4 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] + // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} memcpy(align8, src, sizeof(*src)); // expected-warning@-1{{memcpy operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 8 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 8 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] - memcpy(align_cap, src, sizeof(*src)); // this is fine! + memcpy(align_cap, src, sizeof(*src)); // this is fine (and must preserve tags) // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 16 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} } struct without_cap { @@ -63,85 +69,87 @@ struct without_cap { struct with_cap { struct without_cap a; - void* cap; + void *cap; }; -void test_no_warn_for_non_caps(short* align2, int not_a_cap, unsigned __intcap* capptr, - struct with_cap* struct_with_cap, struct without_cap* struct_without_cap) { - // CHECK-LABEL @test_no_warn_for_non_caps( +void test_no_warn_for_non_caps(short *align2, align2_ptr align2_not_short, int not_a_cap, unsigned __intcap *capptr, + struct with_cap *struct_with_cap, struct without_cap *struct_without_cap) { + // CHECK-LABEL: @test_no_warn_for_non_caps( - memcpy(align2, ¬_a_cap, sizeof(not_a_cap)); // no warning + memcpy(align2, ¬_a_cap, sizeof(not_a_cap)); // no warning // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 2 %{{.+}}, i8 addrspace(200)* align 4 %{{.+}}, i64 4, i1 false) + // CHECK-SAME: align 2 %{{.+}}, i8 addrspace(200)* align 4 %{{.+}}, i64 4, i1 false){{$}} + // FIXME-SAME: [[NO_PRESERVE_TAGS_ATTRIB:#[0-9]+]]{{$}} memcpy(align2, struct_without_cap, sizeof(*struct_without_cap)); // no warning // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 2 %{{.+}}, i8 addrspace(200)* align 4 %{{.+}}, i64 8, i1 false) + // CHECK-SAME: align 2 %{{.+}}, i8 addrspace(200)* align 4 %{{.+}}, i64 8, i1 false){{$}} + // FIXME-SAME: [[NO_PRESERVE_TAGS_ATTRIB:#[0-9]+]]{{$}} memcpy(align2, capptr, sizeof(*capptr)); // expected-warning@-1{{memcpy operation with capability argument 'unsigned __intcap' and underaligned destination (aligned to 2 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 2 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP:#[0-9]+]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP:#[0-9]+]]{{$}} - memcpy(align2, struct_with_cap, sizeof(*struct_with_cap)); + memcpy(align2_not_short, struct_with_cap, sizeof(*struct_with_cap)); // expected-warning@-1{{memcpy operation with capability argument 'struct with_cap' and underaligned destination (aligned to 2 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 2 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 32, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_STRUCT_WITH_CAP:#[0-9]+]] + // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 32, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_STRUCT_WITH_CAP:#[0-9]+]]{{$}} } - -void test_dst_unliagned_src_cap_memmove(void* align1, short* align2, int* align4, long* align8, void** align_cap, a* src) { - // CHECK-LABEL @test_dst_unliagned_src_cap_memcpy( +void test_dst_unliagned_src_cap_memmove(void *align1, align2_ptr align2, align4_ptr align4, align8_ptr align8, align_cap_ptr align_cap, a *src) { + // CHECK-LABEL: @test_dst_unliagned_src_cap_memmove( memmove(align1, src, sizeof(*src)); // expected-warning@-1{{memmove operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} memmove(align2, src, sizeof(*src)); // expected-warning@-1{{memmove operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 2 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 2 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] + // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} memmove(align4, src, sizeof(*src)); // expected-warning@-1{{memmove operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 4 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 4 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] + // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} memmove(align8, src, sizeof(*src)); // expected-warning@-1{{memmove operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 8 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 8 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] + // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} memmove(align_cap, src, sizeof(*src)); // this is fine! // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 16 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} } -#define memcpy_chk(x,y,z) __builtin___memcpy_chk(x,y,z, __builtin_object_size(x,0)) -#define memcpy_chk_inbounds(x,y,z) __builtin___memcpy_chk(x,y,z, z) -#define memmove_chk(x,y,z) __builtin___memmove_chk(x,y,z, __builtin_object_size(x,0)) -#define memmove_chk_inbounds(x,y,z) __builtin___memmove_chk(x,y,z, z) +#define memcpy_chk(x, y, z) __builtin___memcpy_chk(x, y, z, __builtin_object_size(x, 0)) +#define memcpy_chk_inbounds(x, y, z) __builtin___memcpy_chk(x, y, z, z) +#define memmove_chk(x, y, z) __builtin___memmove_chk(x, y, z, __builtin_object_size(x, 0)) +#define memmove_chk_inbounds(x, y, z) __builtin___memmove_chk(x, y, z, z) -void test_memcpy_chk(void* align1, long* align8, void** align_cap, a* src) { - // CHECK-LABEL @test_memcpy_chk( +void test_memcpy_chk(void *align1, align8_ptr align8, void **align_cap, a *src) { + // CHECK-LABEL: @test_memcpy_chk( memcpy_chk(align1, src, sizeof(*src)); // expected-warning@-1{{__memcpy_chk operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call i8 addrspace(200)* @__memcpy_chk( memcpy_chk(align8, src, sizeof(*src)); - // expected-warning@-1{{__memcpy_chk operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 8 bytes) may be inefficient or result in CHERI tags bits being stripped}} + // expected-warning@-1{{__memcpy_chk operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call i8 addrspace(200)* @__memcpy_chk( @@ -154,103 +162,105 @@ void test_memcpy_chk(void* align1, long* align8, void** align_cap, a* src) { // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} memcpy_chk_inbounds(align8, src, sizeof(*src)); // expected-warning@-1{{memcpy operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 8 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 8 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] + // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} memcpy_chk_inbounds(align_cap, src, sizeof(*src)); // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 16 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} } -void test_memmove_chk(void* align1, long* align8, void** align_cap, a* src) { - // CHECK-LABEL @test_memmove_chk( +void test_memmove_chk(void *align1, align8_ptr align8, void **align_cap, a *src) { + // CHECK-LABEL: @test_memmove_chk( memmove_chk(align1, src, sizeof(*src)); // expected-warning@-1{{__memmove_chk operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call i8 addrspace(200)* @__memmove_chk( memmove_chk(align8, src, sizeof(*src)); - // expected-warning@-1{{__memmove_chk operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 8 bytes) may be inefficient or result in CHERI tags bits being stripped}} + // expected-warning@-1{{__memmove_chk operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} + // FIXME: diagnostic should say 8 bytes, but we don't handle the align_value // CHECK: call i8 addrspace(200)* @__memmove_chk( memmove_chk(align_cap, src, sizeof(*src)); // no warning // CHECK: call i8 addrspace(200)* @__memmove_chk( - // these are always turned into a memmove: memmove_chk_inbounds(align1, src, sizeof(*src)); // expected-warning@-1{{memmove operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} memmove_chk_inbounds(align8, src, sizeof(*src)); // expected-warning@-1{{memmove operation with capability argument 'a' (aka 'unsigned __intcap') and underaligned destination (aligned to 8 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 8 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] + // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} memmove_chk_inbounds(align_cap, src, sizeof(*src)); // no warning // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 16 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} } -void test_builtin_assume_aligned_fix_1(long *align8, long *align8_again, char *align1, char *align1_again, unsigned __intcap* src) { +void test_builtin_assume_aligned_fix_1(align8_ptr align8, align8_ptr align8_again, char *align1, char *align1_again, unsigned __intcap *src) { // CHECK-LABEL: @test_builtin_assume_aligned_fix_1( memcpy(align8, src, sizeof(*src)); // expected-warning@-1{{memcpy operation with capability argument 'unsigned __intcap' and underaligned destination (aligned to 8 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 8 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]] + // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]]{{$}} memmove(align8_again, src, sizeof(*src)); // expected-warning@-1{{memmove operation with capability argument 'unsigned __intcap' and underaligned destination (aligned to 8 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* - // CHECK-SAME: align 8 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]] - + // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]]{{$}} memcpy(align1, src, sizeof(*src)); // expected-warning@-1{{memcpy operation with capability argument 'unsigned __intcap' and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]]{{$}} memmove(align1_again, src, sizeof(*src)); // expected-warning@-1{{memmove operation with capability argument 'unsigned __intcap' and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]]{{$}} } -void test_builtin_assume_aligned_fix_2(long *align8, long *align8_again, char *align1, char *align1_again, unsigned __intcap* src) { +void test_builtin_assume_aligned_fix_2(long *align8, long *align8_again, char *align1, char *align1_again, unsigned __intcap *src) { // CHECK-LABEL: @test_builtin_assume_aligned_fix_2( - // should not warn if we add __builtin_assume_aligned or cast to (u)intcap_t + // should not warn if we add __builtin_assume_aligned or cast to (u)intcap // But this only works at -O1 or higher: memcpy(__builtin_assume_aligned(align8, sizeof(void *)), src, sizeof(*src)); // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]]{{$}} memmove(__builtin_assume_aligned(align1, sizeof(void *)), src, sizeof(*src)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]]{{$}} memmove((__intcap *)align1_again, src, sizeof(*src)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 16 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]]{{$}} } -void test_builtin_assume_aligned_fix_3(long *align8, unsigned __intcap* src) { +void test_builtin_assume_aligned_fix_3(long *align8, unsigned __intcap *src) { // CHECK-LABEL: @test_builtin_assume_aligned_fix_3( // Check that we inferred align 8 in the warning here: memcpy(__builtin_assume_aligned(align8, sizeof(long)), src, sizeof(*src)); @@ -258,19 +268,18 @@ void test_builtin_assume_aligned_fix_3(long *align8, unsigned __intcap* src) { // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]]{{$}} memmove(__builtin_assume_aligned(align8, sizeof(long)), src, sizeof(*src)); // expected-warning@-1{{memmove operation with capability argument 'unsigned __intcap' and underaligned destination (aligned to 8 bytes) may be inefficient or result in CHERI tags bits being stripped}} // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]]{{$}} } - extern unsigned __intcap foo_array[10][2]; -void do_stuff(char* buf); +void do_stuff(char *buf); void test_no_crash_with_array(void) { // CHECK-LABEL: @test_no_crash_with_array( char buffer[1234]; @@ -279,11 +288,11 @@ void test_no_crash_with_array(void) { // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 {{.+}}, i64 320, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]]{{$}} do_stuff(buffer); // So that buffer is not optimized away } -void test_builtin_assume_aligned_intermediate_var(char *align1, char *align1_again, a* src) { +void test_builtin_assume_aligned_intermediate_var(char *align1, char *align1_again, a *src) { // CHECK-LABEL: @test_builtin_assume_aligned_intermediate_var( // this should still warn: void *align4 = __builtin_assume_aligned(align1, 4); @@ -292,16 +301,16 @@ void test_builtin_assume_aligned_intermediate_var(char *align1, char *align1_aga // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} void *align32 = __builtin_assume_aligned(align1_again, 32); memcpy(align32, src, sizeof(*src)); // this is fine // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} } -void test_builtin_assume_aligned_memmove_intermediate_var(char *align1, char *align1_again, a* src) { +void test_builtin_assume_aligned_memmove_intermediate_var(char *align1, char *align1_again, a *src) { // CHECK-LABEL: @test_builtin_assume_aligned_memmove_intermediate_var( // this should still warn: void *align4 = __builtin_assume_aligned(align1, 4); @@ -310,15 +319,16 @@ void test_builtin_assume_aligned_memmove_intermediate_var(char *align1, char *al // expected-note@-2{{use __builtin_assume_aligned() or cast to (u)intptr_t*}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} void *align32 = __builtin_assume_aligned(align1_again, 32); memmove(align32, src, sizeof(*src)); // this is fine // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* // CHECK-SAME: align 1 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) - // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A]]{{$}} } -// CHECK: attributes [[PRESERVE_TAGS_ATTRIB_TYPE_A]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'a' (aka 'unsigned __intcap')" } -// CHECK: attributes [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'unsigned __intcap'" } -// CHECK: attributes [[PRESERVE_TAGS_ATTRIB_TYPE_STRUCT_WITH_CAP]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'struct with_cap'" } +// FIXME-DAG: attributes [[NO_PRESERVE_TAGS_ATTRIB]] = { no_preserve_cheri_tags } +// CHECK-DAG: attributes [[PRESERVE_TAGS_ATTRIB_TYPE_A]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'a' (aka 'unsigned __intcap')" } +// CHECK-DAG: attributes [[PRESERVE_TAGS_ATTRIB_TYPE_STRUCT_WITH_CAP]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'struct with_cap'" } +// CHECK-DAG: attributes [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'unsigned __intcap'" } diff --git a/clang/test/CodeGen/cheri/no-tag-copy-attribute-nocap-struct.c b/clang/test/CodeGen/cheri/no-tag-copy-attribute-nocap-struct.c index 498698696330..63613f9928ab 100644 --- a/clang/test/CodeGen/cheri/no-tag-copy-attribute-nocap-struct.c +++ b/clang/test/CodeGen/cheri/no-tag-copy-attribute-nocap-struct.c @@ -3,9 +3,8 @@ /// not to contain capabilities. Previously we assumed that all copies >= sizeof(capability) /// can contain capabilities and we therefore fell back to calling memcpy if the /// alignment was less than >= alignof(capability). -/// TODO: include some end-to-end testing to ensure that the frontend and backend changes allow -/// eliding the memcpy() call that we were previously force to make? -// RUN: %riscv64_cheri_purecap_cc1 %s -emit-llvm -o - -O0 | FileCheck %s +// RUN: %riscv64_cheri_purecap_cc1 %s -emit-llvm -o - -O0 | FileCheck %s --check-prefixes=CHECK +// RUN: %riscv64_cheri_purecap_cc1 %s -relaxed-aliasing -emit-llvm -o - -O0 | FileCheck %s --check-prefixes=CHECK #if __has_feature(capabilities) #define CAP_SIZE sizeof(void *__capability) @@ -28,10 +27,12 @@ struct vdso_timehands { void test_binuptime_assign(struct bintime *bt, struct vdso_timehands *th) { *bt = th->th_offset; + // We should always be able add the no_preserve_tags attribute for this assignment + // since this falls under C2x 6.5 "For all other accesses to an object having no declared type, + // the effective type of the object is simply the type of the lvalue used for the access." // CHECK-LABEL: void @test_binuptime_assign( // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 8 {{%[0-9a-zA-Z.]+}}, i8 addrspace(200)* align 8 {{%[0-9a-zA-Z.]+}}, - // CHECK-SAME: i64 16, i1 false){{$}} - // FIXME-SAME: i64 16, i1 false) [[NO_PRESERVE_TAGS_ATTR:#[0-9]+]]{{$}} + // CHECK-SAME: i64 16, i1 false) [[NO_PRESERVE_TAGS_ATTR:#[0-9]+]]{{$}} } struct cap_size_buffer { @@ -42,8 +43,7 @@ void test_cap_size_buffer_copy(struct cap_size_buffer *a, struct cap_size_buffer *a = *b; // CHECK-LABEL: void @test_cap_size_buffer_copy( // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 4 {{%[0-9a-zA-Z.]+}}, i8 addrspace(200)* align 4 {{%[0-9a-zA-Z.]+}}, - // CHECK-SAME: i64 16, i1 false){{$}} - // FIXME-SAME: i64 16, i1 false) [[NO_PRESERVE_TAGS_ATTR]]{{$}} + // CHECK-SAME: i64 16, i1 false) [[NO_PRESERVE_TAGS_ATTR]]{{$}} } union large_union_align_half_cap_size { @@ -55,16 +55,17 @@ union large_union_align_half_cap_size { }; void test_bigger_union_copy(union large_union_align_half_cap_size *a, union large_union_align_half_cap_size *b) { - // No tags in this union -> can inline the memcpy() + // No tags in this union -> can inline the memcpy(). + // Note: we can do this even with -fno-strict-aliasing since this is a direct assignment. + // XXX: Do we need an option to treats underaligned char[] members as potentially tag-bearing? *a = *b; // CHECK-LABEL: void @test_bigger_union_copy( // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 8 {{%[0-9a-zA-Z.]+}}, i8 addrspace(200)* align 8 {{%[0-9a-zA-Z.]+}}, - // CHECK-SAME: i64 64, i1 false){{$}} - // FIXME-SAME: i64 64, i1 false) [[NO_PRESERVE_TAGS_ATTR]]{{$}} + // CHECK-SAME: i64 64, i1 false) [[NO_PRESERVE_TAGS_ATTR]]{{$}} } void test_align_copy_voidptr(void *a, void *b) { - // void* could contain caps but we don't add the attribute and rely on the backend to decide + // void* could contain caps so we don't add the attribute and rely on the backend to decide memcpy(a, b, CAP_SIZE); // CHECK-LABEL: void @test_align_copy_voidptr( // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[0-9a-zA-Z.]+}}, i8 addrspace(200)* align 1 {{%[0-9a-zA-Z.]+}}, @@ -73,7 +74,7 @@ void test_align_copy_voidptr(void *a, void *b) { void test_align_copy_charptr(char *a, char *b) { // char* could contain caps since it's (unfortunately) basically the same as void*, - // but again we don't add the attribute and rely on the backend to decide + // so again we don't add the attribute and rely on the backend to decide memcpy(a, b, CAP_SIZE); // CHECK-LABEL: void @test_align_copy_charptr( // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[0-9a-zA-Z.]+}}, i8 addrspace(200)* align 1 {{%[0-9a-zA-Z.]+}}, @@ -81,12 +82,12 @@ void test_align_copy_charptr(char *a, char *b) { } void test_align_copy_longptr(long *a, long *b) { - // Note: here we don't infer the no-preserve tags (yet) + // We don't know the effective type of the underlying objects, so we can't add + // no_preserve_cheri_tags despite both pointeee types being non-tag-carrying. memcpy(a, b, CAP_SIZE); // CHECK-LABEL: void @test_align_copy_longptr( // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 8 {{%[0-9a-zA-Z.]+}}, i8 addrspace(200)* align 8 {{%[0-9a-zA-Z.]+}}, // CHECK-SAME: i64 16, i1 false){{$}} - // FIXME-SAME: [[NO_PRESERVE_TAGS_ATTR]]{{$}} } #if __has_feature(capabilities) @@ -119,13 +120,17 @@ void test_align_copy_fwd_declared_2(void *a, struct fwddecl *b) { } void test_align_copy_fwd_declared_dst_notag(long *a, struct fwddecl *b) { - // We don't know if src contains capabilities, but the destination can't contain tags + // We don't know if src contains capabilities, and we also can't assume this + // for the destination since we don't know the effective type of the underlying object. // CHECK-LABEL: void @test_align_copy_fwd_declared_dst_notag( // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 8 {{%[0-9a-zA-Z.]+}}, i8 addrspace(200)* align 1 {{%[0-9a-zA-Z.]+}}, // CHECK-SAME: i64 16, i1 false){{$}} memcpy(a, b, CAP_SIZE); + // Note: if you look at the assembly output for this call, it + // still uses memcpy despite the attribute. b is only aligned to one byte and + // expanding it would be too costly on an architecture without fast unaligned loads/stores. } // CHECK: attributes #0 = { -// FIXME-DAG: attributes [[NO_PRESERVE_TAGS_ATTR]] = { no_preserve_cheri_tags } +// CHECK-DAG: attributes [[NO_PRESERVE_TAGS_ATTR]] = { no_preserve_cheri_tags } // CHECK-DAG: attributes [[MUST_PRESERVE_TAGS_ATTR]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'unsigned __intcap'" } diff --git a/clang/test/CodeGen/cheri/no-tag-copy-attribute-with-caps.c b/clang/test/CodeGen/cheri/no-tag-copy-attribute-with-caps.c index 4ae8ba719a9c..5be072b5d3ea 100644 --- a/clang/test/CodeGen/cheri/no-tag-copy-attribute-with-caps.c +++ b/clang/test/CodeGen/cheri/no-tag-copy-attribute-with-caps.c @@ -1,5 +1,6 @@ // REQUIRES: riscv-registered-target -// RUN: %riscv64_cheri_purecap_cc1 %s -o - -emit-llvm | FileCheck %s +// RUN: %riscv64_cheri_purecap_cc1 %s -o - -emit-llvm | FileCheck %s --check-prefixes=CHECK +// RUN: %riscv64_cheri_purecap_cc1 %s -o - -emit-llvm -relaxed-aliasing | FileCheck %s --check-prefixes=CHECK // Diagnostics are only emitted when generating assembly (with optimizations) // RUN: %riscv64_cheri_purecap_cc1 -debug-info-kind=standalone %s -o /dev/null -O1 -S -verify @@ -7,29 +8,30 @@ struct OneCap { void *__capability b; }; -// define void @test_addrof_char(%struct.OneCap addrspace(200)* {{%[a-z0-9]+}}, i8 signext {{%[a-z0-9]+}}, i128 {{%[a-z0-9]+}}) addrspace(200) #0 { - void test_addrof_char(struct OneCap *cap, char c, __uint128_t u) { // CHECK-LABEL: void @test_addrof_char( // Since this is an address-of expression we should be able to detect that // the source does not contain tags __builtin_memmove(cap, &c, sizeof(c)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 1 {{%[a-z0-9.]+}} - // CHECK-SAME: , i64 1, i1 false){{$}} + // CHECK-SAME: , i64 1, i1 false) [[MUST_PRESERVE_ATTR:#[0-9]+]]{{$}} // FIXME-SAME: , i64 1, i1 false) [[NO_PRESERVE_ATTR:#[0-9]+]]{{$}} __builtin_memmove(&c, cap, sizeof(c)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[a-z0-9]+}}.addr, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} - // CHECK-SAME: , i64 1, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR:#[0-9]+]]{{$}} + // CHECK-SAME: , i64 1, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR:#[0-9]+]]{{$}} // FIXME-SAME: , i64 1, i1 false) [[NO_PRESERVE_ATTR:#[0-9]+]]{{$}} + + // uint128_t cannot not hold tags -> no need to preserve them since we can see the underlying allocation. __builtin_memmove(cap, &u, sizeof(u)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} - // CHECK-SAME: , i64 16, i1 false){{$}} - // FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR:#[0-9]+]]{{$}} - + // FIXME: We can see the underlying decl, this should not need to preserve tags + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR]]{{$}} + // FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}} __builtin_memmove(&u, cap, sizeof(u)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} - // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}} + // FIXME: We can see the underlying decl, this should not need to preserve tags // FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR:#[0-9]+]]{{$}} } void test_small_copy(struct OneCap *cap1, struct OneCap *cap2) { @@ -38,7 +40,6 @@ void test_small_copy(struct OneCap *cap1, struct OneCap *cap2) { // This copy preserves tags // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}} - // FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}} __builtin_memmove(cap1, cap2, 2); // TODO :This copy is too small -> should not preserve tags // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} @@ -59,13 +60,14 @@ void test_addrof_char_buf(struct OneCap *cap, struct strbuf s) { // FIXME: can we add no_preserve_tags if the programmer didn't add an _Alignas()? __builtin_memmove(cap, &s, sizeof(s)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 1 {{%[a-z0-9]+}} - // CHECK-SAME: , i64 16, i1 false){{$}} - // TODO-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{$}} - + // FIXME: We can see the underlying decl, this should not need to preserve tags + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR]]{{$}} + // FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{$}} __builtin_memmove(&s, cap, sizeof(s)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} + // FIXME: We can see the underlying decl, this should not need to preserve tags + // FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}} // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}} - // TODO-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{$}} } void test_array_decay(struct OneCap *cap) { @@ -74,10 +76,13 @@ void test_array_decay(struct OneCap *cap) { int buf[16]; __builtin_memmove(cap, buf, sizeof(*cap)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 4 {{%[a-z0-9]+}} - // CHECK-SAME: , i64 16, i1 false){{$}} - // FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{$}} + // FIXME: We can see the underlying decl, this should not need to preserve tags + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR:#[0-9]+]]{{$}} + // FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}} + __builtin_memmove(buf, cap, sizeof(*cap)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 4 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} + // FIXME: We can see the underlying decl, this should not need to preserve tags // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}} // FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}} @@ -86,7 +91,7 @@ void test_array_decay(struct OneCap *cap) { char buf2[16]; __builtin_memmove(cap, buf2, sizeof(*cap)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 1 {{%[a-z0-9]+}} - // CHECK-SAME: , i64 16, i1 false){{$}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR]]{{$}} // TODO-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}} __builtin_memmove(buf2, cap, sizeof(*cap)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} @@ -98,7 +103,7 @@ void test_array_decay(struct OneCap *cap) { _Alignas(void *__capability) char aligned_char_buf[16]; __builtin_memmove(cap, aligned_char_buf, sizeof(*cap)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} - // CHECK-SAME: , i64 16, i1 false){{$}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR]]{{$}} __builtin_memmove(aligned_char_buf, cap, sizeof(*cap)); // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}} @@ -108,10 +113,11 @@ void test_string_constant(struct OneCap *cap) { // CHECK-LABEL: void @test_string_constant( // Same for string -> char* __builtin_memmove(cap, "abcdefghijklmnopqrstuvwxyz", sizeof(*cap)); + // expected-warning@-1{{memcpy operation with capability argument and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} expected-note@-1{{For more information}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 1 getelementptr inbounds ([27 x i8], [27 x i8] addrspace(200)* @.str // CHECK-SAME: , i64 0 // CHECK-SAME: , i64 0) - // CHECK-SAME: , i64 16, i1 false){{$}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR]]{{$}} // FIXME-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}} } @@ -119,8 +125,9 @@ void test_void_buffer(struct OneCap *cap, void *buf) { // CHECK-LABEL: void @test_void_buffer( // A void* means unknown contents and therefore we preserve tags. __builtin_memmove(cap, buf, sizeof(*cap)); + // expected-warning@-1{{memmove operation with capability argument and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} expected-note@-1{{For more information}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 1 {{%[a-z0-9]+}} - // CHECK-SAME: , i64 16, i1 false){{$}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR]]{{$}} __builtin_memmove(buf, cap, sizeof(*cap)); // expected-warning@-1{{memmove operation with capability argument 'struct OneCap' and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} expected-note@-1{{For more information}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} @@ -131,8 +138,35 @@ void test_char_buffer(struct OneCap *cap, char *buf) { // CHECK-LABEL: void @test_char_buffer( // We have to also assume that char* means unknown contents. __builtin_memmove(cap, buf, sizeof(*cap)); + // expected-warning@-1{{memmove operation with capability argument and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} expected-note@-1{{For more information}} + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 1 {{%[a-z0-9]+}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR]]{{$}} + __builtin_memmove(buf, cap, sizeof(*cap)); + // expected-warning@-1{{memmove operation with capability argument 'struct OneCap' and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} expected-note@-1{{For more information}} + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}} +} + +void test_uchar_buffer(struct OneCap *cap, unsigned char *buf) { + // CHECK-LABEL: void @test_uchar_buffer( + // We also assume that unsigned char* means unknown contents. + __builtin_memmove(cap, buf, sizeof(*cap)); + // expected-warning@-1{{memmove operation with capability argument and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} expected-note@-1{{For more information}} + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 1 {{%[a-z0-9]+}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR]]{{$}} + __builtin_memmove(buf, cap, sizeof(*cap)); + // expected-warning@-1{{memmove operation with capability argument 'struct OneCap' and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} expected-note@-1{{For more information}} + // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}} +} + +void test_u8_buffer(struct OneCap *cap, __UINT8_TYPE__ *buf) { + // CHECK-LABEL: void @test_u8_buffer( + // Same for uint8_t (it's almost certainly defined as unsigned char). + __builtin_memmove(cap, buf, sizeof(*cap)); + // expected-warning@-1{{memmove operation with capability argument and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} expected-note@-1{{For more information}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 1 {{%[a-z0-9]+}} - // CHECK-SAME: , i64 16, i1 false){{$}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR]]{{$}} __builtin_memmove(buf, cap, sizeof(*cap)); // expected-warning@-1{{memmove operation with capability argument 'struct OneCap' and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} expected-note@-1{{For more information}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 1 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} @@ -141,16 +175,23 @@ void test_char_buffer(struct OneCap *cap, char *buf) { void test_int_buffer(struct OneCap *cap, int *buf) { // CHECK-LABEL: void @test_int_buffer( - // However, for int* we can assume it does not contain tags. + // Note: we cannot assume the int buffer is free of tags since C's rules + // depend on the type stored to that memory location last and not the type of + // the pointer. + // FIXME: shouldn't print __builtin_memmove(cap, buf, sizeof(*cap)); + // expected-warning@-1{{memmove operation with capability argument and underaligned destination (aligned to 4 bytes) may be inefficient or result in CHERI tags bits being stripped}} + // expected-note@-2{{use __builtin_assume_aligned()}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[a-z0-9]+}}, i8 addrspace(200)* align 4 {{%[a-z0-9]+}} - // CHECK-SAME: , i64 16, i1 false){{$}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_ATTR]]{{$}} __builtin_memmove(buf, cap, sizeof(*cap)); - // expected-warning@-1{{memmove operation with capability argument 'struct OneCap' and underaligned destination (aligned to 4 bytes) may be inefficient or result in CHERI tags bits being stripped}} expected-note@-1{{For more information}} + // expected-warning@-1{{memmove operation with capability argument 'struct OneCap' and underaligned destination (aligned to 4 bytes) may be inefficient or result in CHERI tags bits being stripped}} + // expected-note@-2{{use __builtin_assume_aligned()}} // CHECK: call void @llvm.memmove.p200i8.p200i8.i64(i8 addrspace(200)* align 4 {{%[a-z0-9]+}}, i8 addrspace(200)* align 16 {{%[a-z0-9]+}} // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}} } // CHECK: attributes #0 = { +// CHECK-DAG: attributes [[MUST_PRESERVE_ATTR]] = { must_preserve_cheri_tags } // CHECK-DAG: attributes [[MUST_PRESERVE_WITH_TYPE_ATTR]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'struct OneCap'" } // FIXME-DAG: attributes [[NO_PRESERVE_ATTR]] = { no_preserve_cheri_tags } diff --git a/clang/test/CodeGen/cheri/no-tag-copy-strict-aliasing.c b/clang/test/CodeGen/cheri/no-tag-copy-strict-aliasing.c new file mode 100644 index 000000000000..66d1fb2a2a1b --- /dev/null +++ b/clang/test/CodeGen/cheri/no-tag-copy-strict-aliasing.c @@ -0,0 +1,64 @@ +// RUN: %riscv64_cheri_purecap_cc1 %s -emit-llvm -o - -O0 | FileCheck %s --check-prefixes=CHECK +// RUN: %riscv64_cheri_purecap_cc1 %s -relaxed-aliasing -emit-llvm -o - -O0 | FileCheck %s --check-prefixes=CHECK +/// Check that we don't add the no_preserve_cheri_tags attribute based on the +/// pointee type of the memcpy since that could break certain valid (albeit +/// dubious) code that relies on tag-preservation for types such as long* +/// See C2x 6.5p6 ("effective type") for more detail. + +void *malloc(__SIZE_TYPE__); +void *memcpy(void *, const void *, __SIZE_TYPE__); +void foo(long **p, long **q); + +void must_retain(long **p, long **q) { + *p = malloc(32); + *q = malloc(32); + (*p)[0] = 1; + (*p)[1] = 2; + // Note: Despite the pointer being a long*, the C standard states that the + // first store to a malloc'd memory location defines the type of the memory + // for strict-aliasing purposes. + // Therefore, this cast and store is fine and we need to retain tags + // in the memcpy below (i.e. we can't add no_preserve_cheri_tags). + *(void (**)(long **, long **))(*p + 2) = &foo; + memcpy(*q, *p, 32); + // CHECK: @must_retain(i64 addrspace(200)* addrspace(200)* [[P:%.*]], i64 addrspace(200)* addrspace(200)* [[Q:%.*]]) addrspace(200) + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 8 {{%[0-9]+}}, i8 addrspace(200)* align 8 {{%[0-9]+}} + // CHECK-SAME: , i64 32, i1 false){{$}} + // CHECK-NEXT: ret void +} + +void no_retain(long **q) { + long p[4]; + *q = malloc(32); + p[0] = 1; + p[1] = 2; + *(void (**)(long **, long **))(p + 2) = &foo; + memcpy(*q, p, 32); + // Since we can see that p is a long[4] stack-allocated variable, the object + // type is long[4] and therefore the memory should not be used to store tags. + // We can therefore omit the copy of tags (although in practise it will + // probably be aligned so malloc will retain tags at run time). + // CHECK: @no_retain(i64 addrspace(200)* addrspace(200)* [[Q:%.*]]) addrspace(200) + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 8 {{%[0-9]+}}, i8 addrspace(200)* align 8 {{%[0-9]+}} + // CHECK-SAME: , i64 32, i1 false){{$}} + // TODO-CHECK-SAME: , i64 32, i1 false) [[NO_TAGS_ATTR:#.*]]{{$}} + // CHECK-NEXT: ret void +} + +void retain_char_array(long **q) { + // The C standard treats character arrays specially and therefore we must + // assume that those can hold tags (even if they aren't sufficiently aligned + // for holding capabilities). + _Alignas(8) char p[32]; + *q = malloc(32); + ((long *)p)[0] = 1; + ((long *)p)[1] = 2; + *(void (**)(long **, long **))(p + 16) = &foo; + memcpy(*q, p, 32); + // CHECK: @retain_char_array(i64 addrspace(200)* addrspace(200)* [[Q:%.*]]) addrspace(200) + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 8 {{%[a-z0-9]+}}, i8 addrspace(200)* align 8 {{%[a-z0-9]+}} + // CHECK-SAME: , i64 32, i1 false){{$}} + // CHECK-NEXT: ret void +} + +// TODO-CHECK: [[NO_TAGS_ATTR]] = { no_preserve_cheri_tags } diff --git a/clang/test/CodeGen/cheri/subobject-bounds-structure-array.c b/clang/test/CodeGen/cheri/subobject-bounds-structure-array.c index b6ce89b04028..921029f543f4 100644 --- a/clang/test/CodeGen/cheri/subobject-bounds-structure-array.c +++ b/clang/test/CodeGen/cheri/subobject-bounds-structure-array.c @@ -45,7 +45,7 @@ int test_struct_with_array1(struct_with_array *s, long index) { // CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [[STRUCT_STRUCT_WITH_ARRAY]], [[STRUCT_STRUCT_WITH_ARRAY]] addrspace(200)* [[S]], i64 [[INDEX]] // CHECK-NEXT: [[TMP0:%.*]] = bitcast [[STRUCT_STRUCT_WITH_ARRAY]] addrspace(200)* [[AGG_RESULT]] to i8 addrspace(200)* // CHECK-NEXT: [[TMP1:%.*]] = bitcast [[STRUCT_STRUCT_WITH_ARRAY]] addrspace(200)* [[ARRAYIDX]] to i8 addrspace(200)* -// CHECK-NEXT: tail call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* noundef nonnull align 8 dereferenceable(56) [[TMP0]], i8 addrspace(200)* noundef nonnull align 8 dereferenceable(56) [[TMP1]], i64 56, i1 false), !tbaa.struct !6 +// CHECK-NEXT: tail call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* noundef nonnull align 8 dereferenceable(56) [[TMP0]], i8 addrspace(200)* noundef nonnull align 8 dereferenceable(56) [[TMP1]], i64 56, i1 false) #[[ATTR10:[0-9]+]], !tbaa.struct !6 // CHECK-NEXT: ret void // struct_with_array test_struct_with_array2(struct_with_array *s, long index) { @@ -78,7 +78,7 @@ int test_struct_with_ptr1(struct_with_ptr *s, long index) { // CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [[STRUCT_STRUCT_WITH_PTR]], [[STRUCT_STRUCT_WITH_PTR]] addrspace(200)* [[S]], i64 [[INDEX]] // CHECK-NEXT: [[TMP0:%.*]] = bitcast [[STRUCT_STRUCT_WITH_PTR]] addrspace(200)* [[AGG_RESULT]] to i8 addrspace(200)* // CHECK-NEXT: [[TMP1:%.*]] = bitcast [[STRUCT_STRUCT_WITH_PTR]] addrspace(200)* [[ARRAYIDX]] to i8 addrspace(200)* -// CHECK-NEXT: tail call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* noundef nonnull align 16 dereferenceable(48) [[TMP0]], i8 addrspace(200)* noundef nonnull align 16 dereferenceable(48) [[TMP1]], i64 48, i1 false), !tbaa.struct !13 +// CHECK-NEXT: tail call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* noundef nonnull align 16 dereferenceable(48) [[TMP0]], i8 addrspace(200)* noundef nonnull align 16 dereferenceable(48) [[TMP1]], i64 48, i1 false) #[[ATTR11:[0-9]+]], !tbaa.struct !13 // CHECK-NEXT: ret void // struct_with_ptr test_struct_with_ptr2(struct_with_ptr *s, long index) { @@ -182,7 +182,7 @@ int test_fake_vla2(struct_fake_vla2 *s, long index) { // CHECK-NEXT: [[CUR_LEN:%.*]] = call i64 @llvm.cheri.cap.length.get.i64(i8 addrspace(200)* nonnull [[TMP1]]) // CHECK-NEXT: [[REMAINING_BYTES:%.*]] = sub i64 [[CUR_LEN]], [[CUR_OFFSET]] // CHECK-NEXT: [[TMP2:%.*]] = call i8 addrspace(200)* @llvm.cheri.cap.bounds.set.i64(i8 addrspace(200)* nonnull [[TMP1]], i64 [[REMAINING_BYTES]]) -// CHECK-NEXT: call void @use_buf(i8 addrspace(200)* [[TMP2]]) #[[ATTR10:[0-9]+]] +// CHECK-NEXT: call void @use_buf(i8 addrspace(200)* [[TMP2]]) #[[ATTR12:[0-9]+]] // CHECK-NEXT: [[CUR_OFFSET4:%.*]] = call i64 @llvm.cheri.cap.offset.get.i64(i8 addrspace(200)* [[TMP2]]) // CHECK-NEXT: [[CUR_LEN5:%.*]] = call i64 @llvm.cheri.cap.length.get.i64(i8 addrspace(200)* [[TMP2]]) // CHECK-NEXT: [[REMAINING_BYTES6:%.*]] = sub i64 [[CUR_LEN5]], [[CUR_OFFSET4]] @@ -524,14 +524,14 @@ typedef struct { // CHECK-NEXT: entry: // CHECK-NEXT: [[ARRAY23:%.*]] = alloca [100 x i8], align 1, addrspace(200) // CHECK-NEXT: [[ARRAY23_SUB:%.*]] = getelementptr inbounds [100 x i8], [100 x i8] addrspace(200)* [[ARRAY23]], i64 0, i64 0 -// CHECK-NEXT: call void @llvm.lifetime.start.p200i8(i64 100, i8 addrspace(200)* nonnull [[ARRAY23_SUB]]) #[[ATTR10]] +// CHECK-NEXT: call void @llvm.lifetime.start.p200i8(i64 100, i8 addrspace(200)* nonnull [[ARRAY23_SUB]]) #[[ATTR12]] // CHECK-NEXT: [[TMP0:%.*]] = call i8 addrspace(200)* @llvm.cheri.cap.bounds.set.i64(i8 addrspace(200)* nonnull [[ARRAY23_SUB]], i64 100) // CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [100 x i8], [100 x i8] addrspace(200)* [[ARRAY23]], i64 0, i64 80 // CHECK-NEXT: [[TMP1:%.*]] = call i8 addrspace(200)* @llvm.cheri.cap.bounds.set.i64(i8 addrspace(200)* nonnull [[ARRAYIDX]], i64 10) // CHECK-NEXT: [[ARRAYIDX2:%.*]] = getelementptr inbounds i8, i8 addrspace(200)* [[TMP1]], i64 [[INDEX]] // CHECK-NEXT: store i8 65, i8 addrspace(200)* [[ARRAYIDX2]], align 1, !tbaa [[TBAA9]] -// CHECK-NEXT: call void @use_buf(i8 addrspace(200)* [[TMP0]]) #[[ATTR10]] -// CHECK-NEXT: call void @llvm.lifetime.end.p200i8(i64 100, i8 addrspace(200)* nonnull [[ARRAY23_SUB]]) #[[ATTR10]] +// CHECK-NEXT: call void @use_buf(i8 addrspace(200)* [[TMP0]]) #[[ATTR12]] +// CHECK-NEXT: call void @llvm.lifetime.end.p200i8(i64 100, i8 addrspace(200)* nonnull [[ARRAY23_SUB]]) #[[ATTR12]] // CHECK-NEXT: ret i32 0 // int test28a(long index) { @@ -565,15 +565,15 @@ int test28b(my_struct28 **array1, long index) { // CHECK-NEXT: entry: // CHECK-NEXT: [[ARRAY23:%.*]] = alloca [100 x i8], align 1, addrspace(200) // CHECK-NEXT: [[ARRAY23_SUB:%.*]] = getelementptr inbounds [100 x i8], [100 x i8] addrspace(200)* [[ARRAY23]], i64 0, i64 0 -// CHECK-NEXT: call void @llvm.lifetime.start.p200i8(i64 100, i8 addrspace(200)* nonnull [[ARRAY23_SUB]]) #[[ATTR10]] +// CHECK-NEXT: call void @llvm.lifetime.start.p200i8(i64 100, i8 addrspace(200)* nonnull [[ARRAY23_SUB]]) #[[ATTR12]] // CHECK-NEXT: [[TMP0:%.*]] = call i8 addrspace(200)* @llvm.cheri.cap.bounds.set.i64(i8 addrspace(200)* nonnull [[ARRAY23_SUB]], i64 100) // CHECK-NEXT: [[SUBSCRIPT_WITH_BOUNDS:%.*]] = bitcast i8 addrspace(200)* [[TMP0]] to [5 x %struct.my_struct28] addrspace(200)* // CHECK-NEXT: [[TMP1:%.*]] = getelementptr inbounds [5 x %struct.my_struct28], [5 x %struct.my_struct28] addrspace(200)* [[SUBSCRIPT_WITH_BOUNDS]], i64 0, i64 [[INDEX1]], i32 0, i64 0 // CHECK-NEXT: [[TMP2:%.*]] = call i8 addrspace(200)* @llvm.cheri.cap.bounds.set.i64(i8 addrspace(200)* [[TMP1]], i64 10) // CHECK-NEXT: [[ARRAYIDX2:%.*]] = getelementptr inbounds i8, i8 addrspace(200)* [[TMP2]], i64 [[INDEX2]] // CHECK-NEXT: store i8 65, i8 addrspace(200)* [[ARRAYIDX2]], align 1, !tbaa [[TBAA9]] -// CHECK-NEXT: call void @use_buf(i8 addrspace(200)* [[TMP0]]) #[[ATTR10]] -// CHECK-NEXT: call void @llvm.lifetime.end.p200i8(i64 100, i8 addrspace(200)* nonnull [[ARRAY23_SUB]]) #[[ATTR10]] +// CHECK-NEXT: call void @use_buf(i8 addrspace(200)* [[TMP0]]) #[[ATTR12]] +// CHECK-NEXT: call void @llvm.lifetime.end.p200i8(i64 100, i8 addrspace(200)* nonnull [[ARRAY23_SUB]]) #[[ATTR12]] // CHECK-NEXT: ret i32 0 // int test28c(long index1, long index2) { @@ -601,3 +601,7 @@ int test28d(my_struct28 **array1, long index1, long index2) { // expected-remark@-1{{not setting bounds for array subscript on 'my_struct28 **' (array subscript on non-array type)}} return 0; } + +// UTC_ARGS: --disable +// CHECK: attributes #[[ATTR10]] = { no_preserve_cheri_tags } +// CHECK: attributes #[[ATTR11]] = { must_preserve_cheri_tags } diff --git a/clang/test/CodeGenCXX/cheri/no-tag-copy-copy-ctor.cpp b/clang/test/CodeGenCXX/cheri/no-tag-copy-copy-ctor.cpp new file mode 100644 index 000000000000..ede245af5dc6 --- /dev/null +++ b/clang/test/CodeGenCXX/cheri/no-tag-copy-copy-ctor.cpp @@ -0,0 +1,90 @@ +// RUN: %riscv64_cheri_purecap_cc1 %s -emit-llvm -o - | FileCheck %s +// RUN: %riscv64_cheri_purecap_cc1 %s -relaxed-aliasing -emit-llvm -o -| FileCheck %s +/// Check that we add the no_preserve_tags/must_preserve_tags attribute to +/// C++ copy constructors. +/// TODO: we may need to special-case char[] fields + +struct TestThreeLongsFinal final { + long a; + long b; + long c; + TestThreeLongsFinal(const TestThreeLongsFinal &) = default; +}; + +TestThreeLongsFinal test_copy_ctor_longs_final(const TestThreeLongsFinal &t) { + // Since this type only contains a long[] (and is final) we don't need to preserve tags when copying + return t; + // CHECK-LABEL: @_Z26test_copy_ctor_longs_finalRK19TestThreeLongsFinal( + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64( + // CHECK-SAME: i8 addrspace(200)* align 8 {{%[0-9]+}}, i8 addrspace(200)* align 8 {{%[0-9]+}} + // CHECK-SAME: , i64 24, i1 false) [[NO_TAGS_ATTR:#[0-9]+]]{{$}} + // CHECK-NEXT: ret void +} + +struct TestLongArray { + long array[5]; + TestLongArray(const TestLongArray &) = default; +}; + +TestLongArray test_copy_ctor_long_array(const TestLongArray &t) { + // Since this type only contains a long[] we don't need to preserve tags when copying + return t; + // CHECK-LABEL: @_Z25test_copy_ctor_long_arrayRK13TestLongArray( + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64( + // CHECK-SAME: i8 addrspace(200)* align 8 {{%[0-9]+}}, i8 addrspace(200)* align 8 {{%[0-9]+}} + // CHECK-SAME: , i64 40, i1 false) [[NO_TAGS_ATTR:#[0-9]+]] + // CHECK-NEXT: ret void +} + +struct TestCharPtr { + char *cap; + long array[5]; + TestCharPtr(const TestCharPtr &) = default; +}; + +TestCharPtr test_copy_ctor_with_ptr(const TestCharPtr &t) { + // Since this type only contains a char[] we must preserve tags when copying + return t; + // CHECK-LABEL: @_Z23test_copy_ctor_with_ptrRK11TestCharPtr( + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64( + // CHECK-SAME: i8 addrspace(200)* align 16 {{%[0-9]+}}, i8 addrspace(200)* align 16 {{%[0-9]+}} + // CHECK-SAME: , i64 64, i1 false) [[MUST_PRESERVE_TAGS_ATTR:#[0-9]+]] + // CHECK-NEXT: ret void +} + +// TODO: we may need to conservatively assume that char[] could be used to hold tags? +struct TestCharArray { + // XXX: should we assume this can be used to store tags even though it is underaligned? + char array[32]; + TestCharArray(const TestCharArray &) = default; +}; + +TestCharArray test_copy_ctor_char_array(const TestCharArray &t) { + return t; + // CHECK-LABEL: @_Z25test_copy_ctor_char_arrayRK13TestCharArray( + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64( + // CHECK-SAME: i8 addrspace(200)* align 1 {{%[0-9]+}}, i8 addrspace(200)* align 1 {{%[0-9]+}} + // CHECK-SAME: , i64 32, i1 false) [[NO_TAGS_ATTR]] + // CHECK-NEXT: ret void +} + +struct TestOveralignedCharArray { + // NB: we have to assume this array can be used to store tags. + alignas(32) char array[32]; + TestOveralignedCharArray(const TestOveralignedCharArray &) = default; +}; + +TestOveralignedCharArray test_copy_ctor_overaligned_char_array(const TestOveralignedCharArray &t) { + return t; + // CHECK-LABEL: @_Z37test_copy_ctor_overaligned_char_arrayRK24TestOveralignedCharArray( + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64( + // CHECK-SAME: i8 addrspace(200)* align 32 {{%[0-9]+}}, i8 addrspace(200)* align 32 {{%[0-9]+}} + // FIXME: we should be conservative here and not set the attribute since there is + // an overaligned char[] that could potentially hold capabilities. + // TODO-CHECK-SAME: , i64 32, i1 false){{$}} + // CHECK-SAME: , i64 32, i1 false) [[NO_TAGS_ATTR]] + // CHECK-NEXT: ret void +} + +// CHECK: [[NO_TAGS_ATTR]] = { no_preserve_cheri_tags } +// CHECK: [[MUST_PRESERVE_TAGS_ATTR]] = { must_preserve_cheri_tags } diff --git a/clang/test/CodeGenCXX/trivial-auto-var-init.cpp b/clang/test/CodeGenCXX/trivial-auto-var-init.cpp index 513222cb3f1d..04e920f244de 100644 --- a/clang/test/CodeGenCXX/trivial-auto-var-init.cpp +++ b/clang/test/CodeGenCXX/trivial-auto-var-init.cpp @@ -1,6 +1,8 @@ // RUN: %clang_cc1 -triple x86_64-unknown-unknown -fblocks %s -emit-llvm -o - | FileCheck %s -check-prefix=UNINIT -// RUN: %clang_cc1 -triple x86_64-unknown-unknown -fblocks -ftrivial-auto-var-init=pattern %s -emit-llvm -o - | FileCheck %s -check-prefix=PATTERN -// RUN: %clang_cc1 -triple x86_64-unknown-unknown -fblocks -ftrivial-auto-var-init=zero %s -emit-llvm -o - | FileCheck %s -check-prefix=ZERO +// RUN: %clang_cc1 -triple x86_64-unknown-unknown -fblocks -ftrivial-auto-var-init=pattern %s -emit-llvm -o - | FileCheck %s --check-prefixes=PATTERN,PATTERN-NOCHERI +// RUN: %clang_cc1 -triple x86_64-unknown-unknown -fblocks -ftrivial-auto-var-init=zero %s -emit-llvm -o - | FileCheck %s --check-prefixes=ZERO,ZERO-NOCHERI +// RUN: %riscv64_cheri_cc1 -fblocks -ftrivial-auto-var-init=pattern %s -emit-llvm -o - | FileCheck %s --check-prefixes=PATTERN,PATTERN-CHERI +// RUN: %riscv64_cheri_cc1 -fblocks -ftrivial-auto-var-init=zero %s -emit-llvm -o - | FileCheck %s --check-prefixes=ZERO,ZERO-CHERI // None of the synthesized globals should contain `undef`. // PATTERN-NOT: undef @@ -140,7 +142,8 @@ void test_switch(int i) { // UNINIT-LABEL: test_vla( // ZERO-LABEL: test_vla( // ZERO: %[[SIZE:[0-9]+]] = mul nuw i64 %{{.*}}, 4 -// ZERO: call void @llvm.memset{{.*}}(i8* align 16 %{{.*}}, i8 0, i64 %[[SIZE]], i1 false), !annotation [[AUTO_INIT:!.+]] +// ZERO-NOCHERI: call void @llvm.memset{{.*}}(i8* align 16 %{{.*}}, i8 0, i64 %[[SIZE]], i1 false), !annotation [[AUTO_INIT:!.+]] +// ZERO-CHERI: call void @llvm.memset{{.*}}(i8* align 4 %{{.*}}, i8 0, i64 %[[SIZE]], i1 false), !annotation [[AUTO_INIT:!.+]] // PATTERN-LABEL: test_vla( // PATTERN: %vla.iszerosized = icmp eq i64 %{{.*}}, 0 // PATTERN: br i1 %vla.iszerosized, label %vla-init.cont, label %vla-setup.loop @@ -151,7 +154,8 @@ void test_switch(int i) { // PATTERN: br label %vla-init.loop // PATTERN: vla-init.loop: // PATTERN: %vla.cur = phi i8* [ %vla.begin, %vla-setup.loop ], [ %vla.next, %vla-init.loop ] -// PATTERN: call void @llvm.memcpy{{.*}} %vla.cur, {{.*}}@__const.test_vla.vla {{.*}}), !annotation [[AUTO_INIT:!.+]] +// PATTERN-NOCHERI: call void @llvm.memcpy{{.*}} %vla.cur, {{.*}}@__const.test_vla.vla {{.*}}), !annotation [[AUTO_INIT:!.+]] +// PATTERN-CHERI: call void @llvm.memcpy{{.*}} %vla.cur, {{.*}}@__const.test_vla.vla {{.*}}) [[NOTAGS:#[0-9]+]], !annotation [[AUTO_INIT:!.+]] // PATTERN: %vla.next = getelementptr inbounds i8, i8* %vla.cur, i64 4 // PATTERN: %vla-init.isdone = icmp eq i8* %vla.next, %vla.end // PATTERN: br i1 %vla-init.isdone, label %vla-init.cont, label %vla-init.loop @@ -204,7 +208,8 @@ void test_alloca_with_align(int size) { // UNINIT-LABEL: test_struct_vla( // ZERO-LABEL: test_struct_vla( // ZERO: %[[SIZE:[0-9]+]] = mul nuw i64 %{{.*}}, 16 -// ZERO: call void @llvm.memset{{.*}}(i8* align 16 %{{.*}}, i8 0, i64 %[[SIZE]], i1 false), !annotation [[AUTO_INIT:!.+]] +// ZERO-NOCHERI: call void @llvm.memset{{.*}}(i8* align 16 %{{.*}}, i8 0, i64 %[[SIZE]], i1 false), !annotation [[AUTO_INIT:!.+]] +// ZERO-CHERI: call void @llvm.memset{{.*}}(i8* align 8 %{{.*}}, i8 0, i64 %[[SIZE]], i1 false), !annotation [[AUTO_INIT:!.+]] // PATTERN-LABEL: test_struct_vla( // PATTERN: %vla.iszerosized = icmp eq i64 %{{.*}}, 0 // PATTERN: br i1 %vla.iszerosized, label %vla-init.cont, label %vla-setup.loop @@ -215,7 +220,8 @@ void test_alloca_with_align(int size) { // PATTERN: br label %vla-init.loop // PATTERN: vla-init.loop: // PATTERN: %vla.cur = phi i8* [ %vla.begin, %vla-setup.loop ], [ %vla.next, %vla-init.loop ] -// PATTERN: call void @llvm.memcpy{{.*}} %vla.cur, {{.*}}@__const.test_struct_vla.vla {{.*}}), !annotation [[AUTO_INIT:!.+]] +// PATTERN-NOCHERI: call void @llvm.memcpy{{.*}} %vla.cur, {{.*}}@__const.test_struct_vla.vla {{.*}}), !annotation [[AUTO_INIT:!.+]] +// PATTERN-CHERI: call void @llvm.memcpy{{.*}} %vla.cur, {{.*}}@__const.test_struct_vla.vla {{.*}}) [[NOTAGS:#[0-9]+]], !annotation [[AUTO_INIT:!.+]] // PATTERN: %vla.next = getelementptr inbounds i8, i8* %vla.cur, i64 16 // PATTERN: %vla-init.isdone = icmp eq i8* %vla.next, %vla.end // PATTERN: br i1 %vla-init.isdone, label %vla-init.cont, label %vla-init.loop @@ -282,10 +288,12 @@ void test_huge_small_init() { // UNINIT-LABEL: test_huge_larger_init( // ZERO-LABEL: test_huge_larger_init( -// ZERO: call void @llvm.memcpy{{.*}} @__const.test_huge_larger_init.big, {{.*}}, i64 65536, +// ZERO-CHERI: call void @llvm.memcpy{{.*}} @__const.test_huge_larger_init.big, {{.*}}, i64 65536, i1 false) [[NOTAGS:#[0-9]+]]{{$}} +// ZERO-NOCHERI: call void @llvm.memcpy{{.*}} @__const.test_huge_larger_init.big, {{.*}}, i64 65536, i1 false){{$}} // ZERO-NOT: !annotation // PATTERN-LABEL: test_huge_larger_init( -// PATTERN: call void @llvm.memcpy{{.*}} @__const.test_huge_larger_init.big, {{.*}}, i64 65536, +// PATTERN-CHERI: call void @llvm.memcpy{{.*}} @__const.test_huge_larger_init.big, {{.*}}, i64 65536, i1 false) [[NOTAGS]]{{$}} +// PATTERN-NOCHERI: call void @llvm.memcpy{{.*}} @__const.test_huge_larger_init.big, {{.*}}, i64 65536, i1 false){{$}} // PATTERN-NOT: !annotation void test_huge_larger_init() { char big[65536] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; @@ -294,4 +302,7 @@ void test_huge_larger_init() { } // extern "C" -// CHECK: [[AUTO_INIT]] = !{ !"auto-init" } +// PATTERN-CHERI: attributes [[NOTAGS]] = { no_preserve_cheri_tags } +// ZERO-CHERI: attributes [[NOTAGS]] = { no_preserve_cheri_tags } +// PATTERN: [[AUTO_INIT]] = !{!"auto-init"} +// ZERO: [[AUTO_INIT]] = !{!"auto-init"} diff --git a/llvm/test/CodeGen/Mips/cheri/memcpy-unaligned-addrinfo.c b/llvm/test/CodeGen/Mips/cheri/memcpy-unaligned-addrinfo.c index 9a4c8e972626..ffe47c7d8893 100644 --- a/llvm/test/CodeGen/Mips/cheri/memcpy-unaligned-addrinfo.c +++ b/llvm/test/CodeGen/Mips/cheri/memcpy-unaligned-addrinfo.c @@ -1,13 +1,19 @@ // NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --function-signature -// RUN: %cheri128_purecap_cc1 %s -emit-llvm -O0 -o - | FileCheck %s -check-prefix OPTNONE -// RUN: %cheri128_purecap_cc1 %s -emit-llvm -O2 -o - | FileCheck %s -// RUN: %cheri128_purecap_cc1 %s -S -O2 -o - -verify -debug-info-kind=standalone | FileCheck %s -check-prefix ASM +// RUN: %cheri_purecap_cc1 %s -emit-llvm -O0 -o - -verify=expected,frontend | FileCheck %s -check-prefix OPTNONE +// RUN: %cheri_purecap_cc1 %s -emit-llvm -O2 -o - -verify=expected,frontend | FileCheck %s +// Note: debug info only enabled to ensure the -verify warning message is correct +// The -debug-info-kind=standalone flag is needed for precise location of diagnostics +// RUN: %cheri_purecap_cc1 %s -S -O2 -o - -debug-info-kind=standalone -verify=backend,expected | FileCheck %s -check-prefix ASM // REQUIRES: clang -// FIXME -// This test is checking a load of detail in the debug info and I can't tell -// what it actually wants to be testing, so I've disabled the middle test. -// It's also a horrible layering violation having a test that depends on clang -// in the LLVM back end. +// Check that the (potentially) tag-preserving copy is that is turned into an +// underaligned capability load/store by SROA results in a memcpy() call at run time. +// This happens because SelectionDAG expands unaligned capability loads/stores to memcpy calls. +// It may seem sensible to not perform this transform in SROA, but that appeared +// to trigger the WebKit miscompile in the first place... +/// FIXME: This test for https://github.com/CTSRD-CHERI/llvm-project/issues/301 is +/// terrible but splitting it into C -> IR and IR -> ASM is awkward + +// frontend-no-diagnostics struct addrinfo { char *b; @@ -21,7 +27,7 @@ struct addrinfo { // OPTNONE-NEXT: store i8 addrspace(200)* [[A]], i8 addrspace(200)* addrspace(200)* [[A_ADDR]], align 16 // OPTNONE-NEXT: [[TMP0:%.*]] = bitcast [[STRUCT_ADDRINFO]] addrspace(200)* [[RETVAL]] to i8 addrspace(200)* // OPTNONE-NEXT: [[TMP1:%.*]] = load i8 addrspace(200)*, i8 addrspace(200)* addrspace(200)* [[A_ADDR]], align 16 -// OPTNONE-NEXT: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 16 [[TMP0]], i8 addrspace(200)* align 1 [[TMP1]], i64 16, i1 false) +// OPTNONE-NEXT: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 16 [[TMP0]], i8 addrspace(200)* align 1 [[TMP1]], i64 16, i1 false) #[[ATTR3:[0-9]+]] // OPTNONE-NEXT: [[COERCE_DIVE:%.*]] = getelementptr inbounds [[STRUCT_ADDRINFO]], [[STRUCT_ADDRINFO]] addrspace(200)* [[RETVAL]], i32 0, i32 0 // OPTNONE-NEXT: [[TMP2:%.*]] = bitcast i8 addrspace(200)* addrspace(200)* [[COERCE_DIVE]] to { i8 addrspace(200)* } addrspace(200)* // OPTNONE-NEXT: [[TMP3:%.*]] = load { i8 addrspace(200)* }, { i8 addrspace(200)* } addrspace(200)* [[TMP2]], align 16 @@ -38,12 +44,12 @@ struct addrinfo { struct addrinfo c(char *a) { struct addrinfo d; __builtin_memcpy(&d, a, sizeof(struct addrinfo)); - // expected-warning@-1{{found underaligned load of capability type (aligned to 1 bytes instead of 16). Will use memcpy() instead of capability load to preserve tags if it is aligned correctly at runtime}} - // expected-note@-2{{use __builtin_assume_aligned() or cast}} + // backend-warning@-1{{found underaligned load of capability type (aligned to 1 bytes instead of 16). Will use memcpy() instead of capability load to preserve tags if it is aligned correctly at runtime}} + // backend-note@-2{{use __builtin_assume_aligned() or cast}} return d; // ASM: .Ltmp1: - // ASM-NEXT: .loc 1 40 3 prologue_end + // ASM-NEXT: .loc 1 [[#@LINE-6]] 3 prologue_end // ASM-NEXT: csetbounds $c4, $c3, 16 // ASM-NEXT: clcbi $c12, %capcall20(memcpy)($c1) // ASM-NEXT: csetbounds $c3, $c11, 16 @@ -66,7 +72,7 @@ void do_stuff(struct group *g); // OPTNONE-NEXT: store i8 addrspace(200)* [[A]], i8 addrspace(200)* addrspace(200)* [[A_ADDR]], align 16 // OPTNONE-NEXT: [[ARRAYDECAY:%.*]] = getelementptr inbounds [16 x i8], [16 x i8] addrspace(200)* [[BUFFER]], i64 0, i64 0 // OPTNONE-NEXT: [[TMP0:%.*]] = bitcast i8 addrspace(200)* addrspace(200)* [[A_ADDR]] to i8 addrspace(200)* -// OPTNONE-NEXT: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 1 [[ARRAYDECAY]], i8 addrspace(200)* align 16 [[TMP0]], i64 16, i1 false) #[[ATTR3:[0-9]+]] +// OPTNONE-NEXT: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 1 [[ARRAYDECAY]], i8 addrspace(200)* align 16 [[TMP0]], i64 16, i1 false) #[[ATTR4:[0-9]+]] // OPTNONE-NEXT: [[ARRAYDECAY1:%.*]] = getelementptr inbounds [16 x i8], [16 x i8] addrspace(200)* [[BUFFER]], i64 0, i64 0 // OPTNONE-NEXT: [[TMP1:%.*]] = bitcast i8 addrspace(200)* [[ARRAYDECAY1]] to [[STRUCT_GROUP]] addrspace(200)* // OPTNONE-NEXT: store [[STRUCT_GROUP]] addrspace(200)* [[TMP1]], [[STRUCT_GROUP]] addrspace(200)* addrspace(200)* [[G]], align 16 @@ -79,11 +85,11 @@ void do_stuff(struct group *g); // CHECK-NEXT: entry: // CHECK-NEXT: [[BUFFER:%.*]] = alloca i8 addrspace(200)*, align 16, addrspace(200) // CHECK-NEXT: [[TMP0:%.*]] = bitcast i8 addrspace(200)* addrspace(200)* [[BUFFER]] to i8 addrspace(200)* -// CHECK-NEXT: call void @llvm.lifetime.start.p200i8(i64 16, i8 addrspace(200)* nonnull [[TMP0]]) #[[ATTR5:[0-9]+]] +// CHECK-NEXT: call void @llvm.lifetime.start.p200i8(i64 16, i8 addrspace(200)* nonnull [[TMP0]]) #[[ATTR4:[0-9]+]] // CHECK-NEXT: store i8 addrspace(200)* [[A]], i8 addrspace(200)* addrspace(200)* [[BUFFER]], align 16 // CHECK-NEXT: [[TMP1:%.*]] = bitcast i8 addrspace(200)* addrspace(200)* [[BUFFER]] to [[STRUCT_GROUP:%.*]] addrspace(200)* -// CHECK-NEXT: call void @do_stuff([[STRUCT_GROUP]] addrspace(200)* nonnull [[TMP1]]) #[[ATTR5]] -// CHECK-NEXT: call void @llvm.lifetime.end.p200i8(i64 16, i8 addrspace(200)* nonnull [[TMP0]]) #[[ATTR5]] +// CHECK-NEXT: call void @do_stuff([[STRUCT_GROUP]] addrspace(200)* nonnull [[TMP1]]) #[[ATTR4]] +// CHECK-NEXT: call void @llvm.lifetime.end.p200i8(i64 16, i8 addrspace(200)* nonnull [[TMP0]]) #[[ATTR4]] // CHECK-NEXT: ret void // void copy_group(const char *a) { @@ -105,7 +111,7 @@ void copy_group(const char *a) { // OPTNONE-NEXT: store i8 addrspace(200)* [[BUFFER]], i8 addrspace(200)* addrspace(200)* [[BUFFER_ADDR]], align 16 // OPTNONE-NEXT: [[TMP0:%.*]] = load i8 addrspace(200)*, i8 addrspace(200)* addrspace(200)* [[BUFFER_ADDR]], align 16 // OPTNONE-NEXT: [[TMP1:%.*]] = bitcast i8 addrspace(200)* addrspace(200)* [[A_ADDR]] to i8 addrspace(200)* -// OPTNONE-NEXT: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 1 [[TMP0]], i8 addrspace(200)* align 16 [[TMP1]], i64 16, i1 false) #[[ATTR3]] +// OPTNONE-NEXT: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 1 [[TMP0]], i8 addrspace(200)* align 16 [[TMP1]], i64 16, i1 false) #[[ATTR4]] // OPTNONE-NEXT: [[TMP2:%.*]] = load i8 addrspace(200)*, i8 addrspace(200)* addrspace(200)* [[BUFFER_ADDR]], align 16 // OPTNONE-NEXT: [[TMP3:%.*]] = bitcast i8 addrspace(200)* [[TMP2]] to [[STRUCT_GROUP]] addrspace(200)* // OPTNONE-NEXT: store [[STRUCT_GROUP]] addrspace(200)* [[TMP3]], [[STRUCT_GROUP]] addrspace(200)* addrspace(200)* [[G]], align 16 @@ -119,7 +125,7 @@ void copy_group(const char *a) { // CHECK-NEXT: [[A_ADDR_0_BUFFER_ADDR_0__SROA_CAST:%.*]] = bitcast i8 addrspace(200)* [[BUFFER]] to i8 addrspace(200)* addrspace(200)* // CHECK-NEXT: store i8 addrspace(200)* [[A]], i8 addrspace(200)* addrspace(200)* [[A_ADDR_0_BUFFER_ADDR_0__SROA_CAST]], align 1 // CHECK-NEXT: [[TMP0:%.*]] = bitcast i8 addrspace(200)* [[BUFFER]] to [[STRUCT_GROUP:%.*]] addrspace(200)* -// CHECK-NEXT: tail call void @do_stuff([[STRUCT_GROUP]] addrspace(200)* [[TMP0]]) #[[ATTR5]] +// CHECK-NEXT: tail call void @do_stuff([[STRUCT_GROUP]] addrspace(200)* [[TMP0]]) #[[ATTR4]] // CHECK-NEXT: ret void // void copy_group2(const char *a, char *buffer) { @@ -127,8 +133,8 @@ void copy_group2(const char *a, char *buffer) { // derived from the unaligned memcpy used in getgrent // Note: this will result in an unaligned memcpy __builtin_memcpy(buffer, &a, sizeof(char *)); - // expected-warning@-1{{found underaligned store of capability type (aligned to 1 bytes instead of 16). Will use memcpy() instead of capability load to preserve tags if it is aligned correctly at runtime}} - // expected-note@-2{{use __builtin_assume_aligned()}} + // backend-warning@-1{{found underaligned store of capability type (aligned to 1 bytes instead of 16). Will use memcpy() instead of capability load to preserve tags if it is aligned correctly at runtime}} + // backend-note@-2{{if you know that the pointer is actually aligned to capability size}} struct group *g = (struct group *)buffer; do_stuff(g); } @@ -147,7 +153,7 @@ void copy_group2(const char *a, char *buffer) { // OPTNONE-NEXT: [[TMP0:%.*]] = load i8 addrspace(200)*, i8 addrspace(200)* addrspace(200)* [[BUFFER_ADDR]], align 16 // OPTNONE-NEXT: [[TMP1:%.*]] = bitcast [[STRUCT_GROUP]] addrspace(200)* [[A]] to i8 addrspace(200)* // OPTNONE-NEXT: [[TMP2:%.*]] = load i64, i64 addrspace(200)* [[SIZE_ADDR]], align 8 -// OPTNONE-NEXT: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 1 [[TMP0]], i8 addrspace(200)* align 16 [[TMP1]], i64 [[TMP2]], i1 false) #[[ATTR4:[0-9]+]] +// OPTNONE-NEXT: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 1 [[TMP0]], i8 addrspace(200)* align 16 [[TMP1]], i64 [[TMP2]], i1 false) #[[ATTR5:[0-9]+]] // OPTNONE-NEXT: [[TMP3:%.*]] = load i8 addrspace(200)*, i8 addrspace(200)* addrspace(200)* [[BUFFER_ADDR]], align 16 // OPTNONE-NEXT: [[TMP4:%.*]] = bitcast i8 addrspace(200)* [[TMP3]] to [[STRUCT_GROUP]] addrspace(200)* // OPTNONE-NEXT: store [[STRUCT_GROUP]] addrspace(200)* [[TMP4]], [[STRUCT_GROUP]] addrspace(200)* addrspace(200)* [[G]], align 16 @@ -163,15 +169,57 @@ void copy_group2(const char *a, char *buffer) { // CHECK-NEXT: [[A_SROA_0_0__SROA_CAST4:%.*]] = bitcast i8 addrspace(200)* addrspace(200)* [[A_SROA_0]] to i8 addrspace(200)* // CHECK-NEXT: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 1 [[BUFFER]], i8 addrspace(200)* nonnull align 16 [[A_SROA_0_0__SROA_CAST4]], i64 [[SIZE]], i1 false) #[[ATTR6:[0-9]+]] // CHECK-NEXT: [[TMP0:%.*]] = bitcast i8 addrspace(200)* [[BUFFER]] to [[STRUCT_GROUP:%.*]] addrspace(200)* -// CHECK-NEXT: tail call void @do_stuff([[STRUCT_GROUP]] addrspace(200)* [[TMP0]]) #[[ATTR5]] +// CHECK-NEXT: tail call void @do_stuff([[STRUCT_GROUP]] addrspace(200)* [[TMP0]]) #[[ATTR4]] // CHECK-NEXT: ret void // void copy_group3(char *buffer, struct group a, long size) { // derived from the unaligned memcpy used in getgrent + // No warning here since the size is not constant and always needs a memcpy (TODO: maybe we should still warn?) __builtin_memcpy(buffer, &a, size); struct group *g = (struct group *)buffer; do_stuff(g); } +// OPTNONE-LABEL: define {{[^@]+}}@copy_group4 +// OPTNONE-SAME: (i8 addrspace(200)* [[BUFFER:%.*]], [[STRUCT_GROUP:%.*]] addrspace(200)* [[A:%.*]]) addrspace(200) #[[ATTR0]] { +// OPTNONE-NEXT: entry: +// OPTNONE-NEXT: [[BUFFER_ADDR:%.*]] = alloca i8 addrspace(200)*, align 16, addrspace(200) +// OPTNONE-NEXT: [[A_ADDR:%.*]] = alloca [[STRUCT_GROUP]] addrspace(200)*, align 16, addrspace(200) +// OPTNONE-NEXT: [[G:%.*]] = alloca [[STRUCT_GROUP]] addrspace(200)*, align 16, addrspace(200) +// OPTNONE-NEXT: store i8 addrspace(200)* [[BUFFER]], i8 addrspace(200)* addrspace(200)* [[BUFFER_ADDR]], align 16 +// OPTNONE-NEXT: store [[STRUCT_GROUP]] addrspace(200)* [[A]], [[STRUCT_GROUP]] addrspace(200)* addrspace(200)* [[A_ADDR]], align 16 +// OPTNONE-NEXT: [[TMP0:%.*]] = load i8 addrspace(200)*, i8 addrspace(200)* addrspace(200)* [[BUFFER_ADDR]], align 16 +// OPTNONE-NEXT: [[TMP1:%.*]] = load [[STRUCT_GROUP]] addrspace(200)*, [[STRUCT_GROUP]] addrspace(200)* addrspace(200)* [[A_ADDR]], align 16 +// OPTNONE-NEXT: [[TMP2:%.*]] = bitcast [[STRUCT_GROUP]] addrspace(200)* [[TMP1]] to i8 addrspace(200)* +// OPTNONE-NEXT: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 1 [[TMP0]], i8 addrspace(200)* align 16 [[TMP2]], i64 48, i1 false) #[[ATTR5]] +// OPTNONE-NEXT: [[TMP3:%.*]] = load i8 addrspace(200)*, i8 addrspace(200)* addrspace(200)* [[BUFFER_ADDR]], align 16 +// OPTNONE-NEXT: [[TMP4:%.*]] = bitcast i8 addrspace(200)* [[TMP3]] to [[STRUCT_GROUP]] addrspace(200)* +// OPTNONE-NEXT: store [[STRUCT_GROUP]] addrspace(200)* [[TMP4]], [[STRUCT_GROUP]] addrspace(200)* addrspace(200)* [[G]], align 16 +// OPTNONE-NEXT: [[TMP5:%.*]] = load [[STRUCT_GROUP]] addrspace(200)*, [[STRUCT_GROUP]] addrspace(200)* addrspace(200)* [[G]], align 16 +// OPTNONE-NEXT: call void @do_stuff([[STRUCT_GROUP]] addrspace(200)* [[TMP5]]) +// OPTNONE-NEXT: ret void +// +// CHECK-LABEL: define {{[^@]+}}@copy_group4 +// CHECK-SAME: (i8 addrspace(200)* [[BUFFER:%.*]], [[STRUCT_GROUP:%.*]] addrspace(200)* nocapture readonly [[A:%.*]]) local_unnamed_addr addrspace(200) #[[ATTR2]] { +// CHECK-NEXT: entry: +// CHECK-NEXT: [[TMP0:%.*]] = bitcast [[STRUCT_GROUP]] addrspace(200)* [[A]] to i8 addrspace(200)* +// CHECK-NEXT: tail call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* noundef nonnull align 1 dereferenceable(48) [[BUFFER]], i8 addrspace(200)* noundef nonnull align 16 dereferenceable(48) [[TMP0]], i64 48, i1 false) #[[ATTR6]] +// CHECK-NEXT: [[TMP1:%.*]] = bitcast i8 addrspace(200)* [[BUFFER]] to [[STRUCT_GROUP]] addrspace(200)* +// CHECK-NEXT: tail call void @do_stuff([[STRUCT_GROUP]] addrspace(200)* [[TMP1]]) #[[ATTR4]] +// CHECK-NEXT: ret void +// +void copy_group4(char *buffer, struct group *a) { + // derived from the unaligned memcpy used in getgrent + __builtin_memcpy(buffer, a, sizeof(struct group) * 3); + // backend-warning@-1{{memcpy operation with capability argument 'struct group' and underaligned destination (aligned to 1 bytes) may be inefficient or result in CHERI tags bits being stripped}} + // backend-note@-2{{if you know that the pointer is actually aligned to capability size}} + struct group *g = (struct group *)buffer; + do_stuff(g); +} + // UTC_ARGS: --disable +// OPTNONE: attributes #[[ATTR3]] = { must_preserve_cheri_tags } +// OPTNONE: attributes #[[ATTR4]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'const char * __capability'" } +// OPTNONE: attributes #[[ATTR5]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'struct group'" } // CHECK: attributes #[[ATTR6]] = { must_preserve_cheri_tags "frontend-memtransfer-type"="'struct group'" } +// UTC_ARGS: --enable