diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index 97a6391bc8a0f..bfd106991f085 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 2cb8fd537f063..92ed559992ed8 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -11730,6 +11730,62 @@ 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; + } + } else if (RD->field_empty()) { + // A struct without fields could be used as an opaque type -> assume it + // might contain capabilities + 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) { + // Not a record type, and the check above ensured this is not a capability + // type, so this type can't contain capabilities. + return true; + } + 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 e38260116099f..a12e4a080676b 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 c665c8569d988..53e0c73fc11d8 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. + auto PreserveTags = CGF.getTypes().copyShouldPreserveTagsForPointee( + RecordTy, /*EffectiveTypeKnown=*/true, MemcpySize); emitMemcpyIR( Dest.isBitField() ? Dest.getBitFieldAddress() : Dest.getAddress(CGF), Src.isBitField() ? Src.getBitFieldAddress() : Src.getAddress(CGF), - MemcpySize, llvm::PreserveCheriTags::TODO); + MemcpySize, PreserveTags); reset(); } diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp index 22d9c0058f077..6de4da6c4c1e2 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. + // Copy from a global (and therefore the effective type of the variable is + // known). + auto PreserveTags = + IsAutoInit || !ContainsCaps + ? llvm::PreserveCheriTags::Unnecessary + : CGM.getTypes().copyShouldPreserveTagsForPointee( + D.getType(), /*EffectiveTypeKnown=*/true, SizeVal); auto *I = Builder.CreateMemCpy(Loc, createUnnamedGlobalForMemcpyFrom( CGM, D, Builder, constant, Loc.getAlignment()), - SizeVal, llvm::PreserveCheriTags::TODO, isVolatile); + SizeVal, PreserveTags, 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); + // Pattern init never writes valid tags, so we can pass + // PreserveCheriTags::Unnecessary to the CreateMemCpy() call auto *I = Builder.CreateMemCpy( Address(Cur, CurAlign), createUnnamedGlobalForMemcpyFrom(CGM, D, Builder, Constant, ConstantAlign), - BaseSizeInChars, llvm::PreserveCheriTags::TODO, isVolatile); + BaseSizeInChars, 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 5a1bbbef20123..30f14f2204c0a 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,13 @@ 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 PreserveTags = getTypes().copyShouldPreserveTagsForPointee( + Ty, /*EffectiveTypeKnown=*/true, SizeVal); + auto Inst = + Builder.CreateMemCpy(DestPtr, SrcPtr, SizeVal, PreserveTags, 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 08c723a023572..d5c9eb6cd465a 100644 --- a/clang/lib/CodeGen/CodeGenTypes.cpp +++ b/clang/lib/CodeGen/CodeGenTypes.cpp @@ -952,6 +952,153 @@ bool CodeGenTypes::isZeroInitializable(QualType T) { return true; } +static bool isLessThanCapSize(const ASTContext &Context, + Optional Size) { + return Size && *Size < Context.toCharUnitsFromBits( + Context.getTargetInfo().getCHERICapabilityWidth()); +} + +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; + if (isLessThanCapSize(Context, Size)) { + // Copies smaller than capability size do not need to preserve tag bits. + return llvm::PreserveCheriTags::Unnecessary; + } + 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(); + const Expr *UnderlyingExpr = E->IgnoreParenCasts(); + if (const auto *SL = dyn_cast(UnderlyingExpr)) { + // String literals can never contain tag bits. + return llvm::PreserveCheriTags::Unnecessary; + } + // 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; + if (isLessThanCapSize(Context, Size)) { + // Copies smaller than capability size do not need to preserve tag bits. + return llvm::PreserveCheriTags::Unnecessary; + } + 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; + } + + if (!copiesAtMostTypeSize(Pointee, Context, Size)) { + // We can only mark the copy as non-tag-preserving is we know the size + // remains within the bounds of the copied type. For example with 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. + return llvm::PreserveCheriTags::Unknown; + } + + if (Context.cannotContainCapabilities(Pointee)) { + // If the type cannot contain capabilities and we are copying at most + // sizeof(type), then we can use a non-tag-preserving copy. + // This is useful to optimize cases such as assignment of something like + // `struct { long a; long b; }`: + // In this case the type is as large as a capability but the known + // alignment is only 4/8 bytes, so the backend needs to be conservative and + // assume that the memcpy might copy 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; + } + // 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 this 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 99bf091606a22..23cc0e8d72996 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 91cb39587c813..1f9006ccc3b82 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 6ee6b7bed475f..45cf0d98a6445 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 6924b93d7b8d4..a938696bdc899 100644 --- a/clang/test/CodeGen/cheri/memcpy-unaligned.c +++ b/clang/test/CodeGen/cheri/memcpy-unaligned.c @@ -1,59 +1,65 @@ // 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*}} // 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:#[0-9]+]] + // CHECK-SAME: [[PRESERVE_TAGS_ATTRIB_TYPE_A:#[0-9]+]]{{$}} memcpy(align2, src, sizeof(*src)); // 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,83 @@ 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) [[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) [[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: align 2 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 16, i1 false) [[PRESERVE_TAGS_ATTRIB_TYPE_UINTCAP:#[0-9]+]]{{$}} memcpy(align2, 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 2 %{{.+}}, i8 addrspace(200)* align 16 %{{.+}}, i64 32, i1 false) [[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 +158,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 +264,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 +284,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 +297,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 +315,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'" } +// CHECK-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 new file mode 100644 index 0000000000000..9c2a3f114b73b --- /dev/null +++ b/clang/test/CodeGen/cheri/no-tag-copy-attribute-nocap-struct.c @@ -0,0 +1,152 @@ +// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --function-signature +/// Check that we can inline memcpy() calls for small structures that are known +/// 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). +// 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) +#else +#define CAP_SIZE (2 * sizeof(long)) +#endif + +void *memcpy(void *, const void *, unsigned long); + +// Test case from lib/libc/sys/__vdso_gettimeofday.c binuptime +struct bintime { + long sec; + long frac; +}; +struct vdso_timehands { + /* some more fields */ + struct bintime th_offset; + struct bintime th_boottime; +}; + +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) [[NO_PRESERVE_TAGS_ATTR:#[0-9]+]]{{$}} +} + +struct cap_size_buffer { + int data[CAP_SIZE / sizeof(int)]; +}; + +void test_cap_size_buffer_copy(struct cap_size_buffer *a, struct cap_size_buffer *b) { + *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) [[NO_PRESERVE_TAGS_ATTR]]{{$}} +} + +union large_union_align_half_cap_size { + struct cap_size_buffer data[4]; + int i; + float f; + long l; +}; + +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(). + // 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) [[NO_PRESERVE_TAGS_ATTR]]{{$}} +} + +union large_union_with_char_array { + struct cap_size_buffer data; + int i; + float f; + long l; + char bigdata[4 * CAP_SIZE]; +}; + +void test_union_with_char_array(union large_union_with_char_array *a, union large_union_with_char_array *b) { + // There are no tags in this union, but it contains a char[], so we can't inline this memcpy safely. + // TODO: Since the char[] is not sufficiently aligned to store capabilities we could still add the attribute + *a = *b; + // CHECK-LABEL: void @test_union_with_char_array( + // 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){{$}} +} + +void test_align_copy_voidptr(void *a, void *b) { + // 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.]+}}, + // CHECK-SAME: i64 16, i1 false){{$}} +} + +void test_align_copy_charptr(char *a, char *b) { + // char* could contain caps since it's (unfortunately) basically the same as void*, + // 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.]+}}, + // CHECK-SAME: i64 16, i1 false){{$}} +} + +void test_align_copy_longptr(long *a, long *b) { + // 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){{$}} +} + +#if __has_feature(capabilities) +void test_align_copy_capptr(unsigned __intcap *a, unsigned __intcap *b) { + memcpy(a, b, CAP_SIZE); + // CHECK-LABEL: void @test_align_copy_capptr( + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64(i8 addrspace(200)* align 16 {{%[0-9a-zA-Z.]+}}, i8 addrspace(200)* align 16 {{%[0-9a-zA-Z.]+}}, + // CHECK-SAME: i64 16, i1 false) [[MUST_PRESERVE_TAGS_ATTR:#[0-9]+]]{{$}} +} +#endif + +struct fwddecl; + +void test_align_copy_fwd_declared(struct fwddecl *a, struct fwddecl *b) { + // We don't know if src contains capabilities -> don't add the attribute no-preserve + // attribute but also don't add the must-preserve attribute and let the backend decide + memcpy(a, b, CAP_SIZE); + // CHECK-LABEL: void @test_align_copy_fwd_declared( + // 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.]+}}, + // CHECK-SAME: i64 16, i1 false){{$}} +} + +void test_align_copy_fwd_declared_2(void *a, struct fwddecl *b) { + // We don't know if src contains capabilities -> don't add the attribute no-preserve + // attribute but also don't add the must-preserve attribute and let the backend decide + memcpy(a, b, CAP_SIZE); + // CHECK-LABEL: void @test_align_copy_fwd_declared_2( + // 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.]+}}, + // CHECK-SAME: i64 16, i1 false){{$}} +} + +void test_align_copy_fwd_declared_dst_notag(long *a, struct fwddecl *b) { + // 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 = { +// 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 new file mode 100644 index 0000000000000..c9a40af666246 --- /dev/null +++ b/clang/test/CodeGen/cheri/no-tag-copy-attribute-with-caps.c @@ -0,0 +1,198 @@ +// REQUIRES: riscv-registered-target +// 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 + +struct OneCap { + void *__capability b; +}; + +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) [[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) [[NO_PRESERVE_ATTR]]{{$}} + + // 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]+}} + // 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:#[0-9]+]]{{$}} + __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]+}} + // 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) { + // CHECK-LABEL: void @test_small_copy( + __builtin_memmove(cap1, cap2, sizeof(*cap1)); + // 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]]{{$}} + __builtin_memmove(cap1, cap2, 2); + // This copy is too small -> no need to 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]+}} + // CHECK-SAME: , i64 2, i1 false) [[NO_PRESERVE_ATTR]]{{$}} +} + +struct strbuf { + char data[16]; +}; + +void test_addrof_char_buf(struct OneCap *cap, struct strbuf s) { + // CHECK-LABEL: void @test_addrof_char_buf( + + // Since this is an address-of expression we should be able to detect that + // the source does not contain tags. However, the struct contains a char[] + // so we have to conservatively assume that it might be used to hold tags. + // 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]+}} + // 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]]{{$}} +} + +void test_array_decay(struct OneCap *cap) { + // CHECK-LABEL: void @test_array_decay( + // array-decay -> We can tell that the buffer does not contain tags since it's not a char[] + 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]+}} + // 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]]{{$}} + + // char array aligned to one byte -> while it does not contain tags we conservatively assume it might. + // TODO: should we only do this for aligned char arrays? + 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) [[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]+}} + // CHECK-SAME: , i64 16, i1 false) [[MUST_PRESERVE_WITH_TYPE_ATTR]]{{$}} + // TODO-SAME: , i64 16, i1 false) [[NO_PRESERVE_ATTR]]{{$}} + + // We can't add the attribute for aligned char array decay since those are + // often used for buffers that contain anything. + _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) [[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]]{{$}} +} + +void test_string_constant(struct OneCap *cap) { + // CHECK-LABEL: void @test_string_constant( + // Same for string -> char* + __builtin_memmove(cap, "abcdefghijklmnopqrstuvwxyz", sizeof(*cap)); + // 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) [[NO_PRESERVE_ATTR]]{{$}} + /// Check that the explicit cast does not affect the analysis. + __builtin_memmove(cap, (void *)"abcdefghijklmnopqrstuvwxyz", sizeof(*cap)); + // 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) [[NO_PRESERVE_ATTR]]{{$}} +} + +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) [[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_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) [[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_int_buffer(struct OneCap *cap, int *buf) { + // CHECK-LABEL: void @test_int_buffer( + // 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) [[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@-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'" } +// CHECK-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 0000000000000..66d1fb2a2a1b4 --- /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 b6ce89b040285..921029f543f48 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 0000000000000..c4c614da60786 --- /dev/null +++ b/clang/test/CodeGenCXX/cheri/no-tag-copy-copy-ctor.cpp @@ -0,0 +1,146 @@ +// 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. +/// NB: Unfortunately, we have to special-case char[] fields even though +/// they will usually hold string data and not capabilities. + +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 +} + +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]+}} + // NB: we have to be conservative here and not set the attribute since there is + // an overaligned char[] that could potentially hold capabilities. + // CHECK-SAME: , i64 32, i1 false){{$}} + // CHECK-NEXT: ret void +} + +struct TestOveralignedUCharArray { + // NB: we have to assume this array can be used to store tags. + alignas(32) unsigned char array[32]; + TestOveralignedUCharArray(const TestOveralignedUCharArray &) = default; +}; + +TestOveralignedUCharArray test_copy_ctor_overaligned_uchar_array(const TestOveralignedUCharArray &t) { + return t; + // CHECK-LABEL: @_Z38test_copy_ctor_overaligned_uchar_arrayRK25TestOveralignedUCharArray( + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64( + // CHECK-SAME: i8 addrspace(200)* align 32 {{%[0-9]+}}, i8 addrspace(200)* align 32 {{%[0-9]+}} + // NB: we have to be conservative here and not set the attribute since there is + // an overaligned unsigned char[] that could potentially hold capabilities. + // CHECK-SAME: , i64 32, i1 false){{$}} + // CHECK-NEXT: ret void +} + +struct TestOveralignedSCharArray { + // NB: we have to assume this array can be used to store tags. + alignas(32) signed char array[32]; + TestOveralignedSCharArray(const TestOveralignedSCharArray &) = default; +}; + +TestOveralignedSCharArray test_copy_ctor_overaligned_schar_array(const TestOveralignedSCharArray &t) { + return t; + // CHECK-LABEL: @_Z38test_copy_ctor_overaligned_schar_arrayRK25TestOveralignedSCharArray( + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64( + // CHECK-SAME: i8 addrspace(200)* align 32 {{%[0-9]+}}, i8 addrspace(200)* align 32 {{%[0-9]+}} + // NB: we have to be conservative here and not set the attribute since there is + // an overaligned signed char[] that could potentially hold capabilities. + // CHECK-SAME: , i64 32, i1 false){{$}} + // CHECK-NEXT: ret void +} + +namespace std { +enum class byte : unsigned char {}; +} + +struct TestOveralignedStdByteArray { + // NB: we have to assume this array can be used to store tags. + alignas(32) std::byte array[32]; + TestOveralignedStdByteArray(const TestOveralignedStdByteArray &) = default; +}; + +TestOveralignedStdByteArray _Z41test_copy_ctor_overaligned_std_byte_arrayRK27TestOveralignedStdByteArray(const TestOveralignedStdByteArray &t) { + return t; + // CHECK-LABEL: @_Z76_Z41test_copy_ctor_overaligned_std_byte_arrayRK27TestOveralignedStdByteArrayRK27TestOveralignedStdByteArray( + // CHECK: call void @llvm.memcpy.p200i8.p200i8.i64( + // CHECK-SAME: i8 addrspace(200)* align 32 {{%[0-9]+}}, i8 addrspace(200)* align 32 {{%[0-9]+}} + // NB: we have to be conservative here and not set the attribute since there is + // an overaligned std::byte[] that could potentially hold capabilities. + // CHECK-SAME: , i64 32, i1 false){{$}} + // CHECK-NEXT: ret void +} + +// NB: we conservatively assume that char[] could be used to hold tags. +// TODO: should we assume this cannot be used to store tags since it is underaligned? +struct TestCharArray { + char array[32]; + TestCharArray(const TestCharArray &) = default; +}; + +TestCharArray test_copy_ctor_unaligned_char_array(const TestCharArray &t) { + return t; + // CHECK-LABEL: @_Z35test_copy_ctor_unaligned_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){{$}} + // TODO-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 513222cb3f1d1..04e920f244de1 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/lib/CodeGen/AsmPrinter/DebugLocEntry.h b/llvm/lib/CodeGen/AsmPrinter/DebugLocEntry.h index 62ebadaf3cbeb..1102f40ed73df 100644 --- a/llvm/lib/CodeGen/AsmPrinter/DebugLocEntry.h +++ b/llvm/lib/CodeGen/AsmPrinter/DebugLocEntry.h @@ -214,6 +214,11 @@ class DebugLocEntry { // Sort the pieces by offset. // Remove any duplicate entries by dropping all but the first. void sortUniqueValues() { + // Values is either 1 item that does not have a fragment, or many items + // that all do. No need to sort if the former and also prevents operator< + // being called on a non fragment item when _GLIBCXX_DEBUG is defined. + if (Values.size() == 1) + return; llvm::sort(Values); Values.erase(std::unique(Values.begin(), Values.end(), [](const DbgValueLoc &A, const DbgValueLoc &B) { diff --git a/llvm/test/CodeGen/Mips/cheri/memcpy-unaligned-addrinfo.c b/llvm/test/CodeGen/Mips/cheri/memcpy-unaligned-addrinfo.c index 9a4c8e9726262..ffe47c7d8893a 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