[clang][bytecode] Handle __builtin_memcmp (#119544)

This commit is contained in:
Timm Baeder
2024-12-12 08:59:35 +01:00
committed by GitHub
parent 9c50182bf4
commit 8713914d76
5 changed files with 119 additions and 5 deletions

View File

@@ -18,6 +18,8 @@ namespace interp {
enum class Endian { Little, Big };
struct Bytes;
/// A quantity in bits.
struct Bits {
size_t N = 0;
@@ -30,6 +32,7 @@ struct Bits {
bool isFullByte() const { return N % 8 == 0; }
bool nonZero() const { return N != 0; }
bool isZero() const { return N == 0; }
Bytes toBytes() const;
Bits operator-(Bits Other) const { return Bits(N - Other.N); }
Bits operator+(Bits Other) const { return Bits(N + Other.N); }
@@ -56,6 +59,11 @@ struct Bytes {
Bits toBits() const { return Bits(N * 8); }
};
inline Bytes Bits::toBytes() const {
assert(isFullByte());
return Bytes(N / 8);
}
/// A bit range. Both Start and End are inclusive.
struct BitRange {
Bits Start;
@@ -83,6 +91,7 @@ struct BitcastBuffer {
/// Returns the buffer size in bits.
Bits size() const { return FinalBitSize; }
Bytes byteSize() const { return FinalBitSize.toBytes(); }
/// Returns \c true if all bits in the buffer have been initialized.
bool allInitialized() const;

View File

@@ -1830,6 +1830,7 @@ static bool interp__builtin_elementwise_popcount(InterpState &S, CodePtr OpPC,
return true;
}
static bool interp__builtin_memcpy(InterpState &S, CodePtr OpPC,
const InterpFrame *Frame,
const Function *Func, const CallExpr *Call) {
@@ -1900,6 +1901,67 @@ static bool interp__builtin_memcpy(InterpState &S, CodePtr OpPC,
return true;
}
/// Determine if T is a character type for which we guarantee that
/// sizeof(T) == 1.
static bool isOneByteCharacterType(QualType T) {
return T->isCharType() || T->isChar8Type();
}
static bool interp__builtin_memcmp(InterpState &S, CodePtr OpPC,
const InterpFrame *Frame,
const Function *Func, const CallExpr *Call) {
assert(Call->getNumArgs() == 3);
unsigned ID = Func->getBuiltinID();
const Pointer &PtrA = getParam<Pointer>(Frame, 0);
const Pointer &PtrB = getParam<Pointer>(Frame, 1);
const APSInt &Size =
peekToAPSInt(S.Stk, *S.getContext().classify(Call->getArg(2)));
if (ID == Builtin::BImemcmp)
diagnoseNonConstexprBuiltin(S, OpPC, ID);
if (Size.isZero()) {
pushInteger(S, 0, Call->getType());
return true;
}
// FIXME: This is an arbitrary limitation the current constant interpreter
// had. We could remove this.
if (!isOneByteCharacterType(PtrA.getType()) ||
!isOneByteCharacterType(PtrB.getType())) {
S.FFDiag(S.Current->getSource(OpPC),
diag::note_constexpr_memcmp_unsupported)
<< ("'" + S.getASTContext().BuiltinInfo.getName(ID) + "'").str()
<< PtrA.getType() << PtrB.getType();
return false;
}
if (PtrA.isDummy() || PtrB.isDummy())
return false;
// Now, read both pointers to a buffer and compare those.
BitcastBuffer BufferA(
Bits(S.getASTContext().getTypeSize(PtrA.getFieldDesc()->getType())));
readPointerToBuffer(S.getContext(), PtrA, BufferA, false);
BitcastBuffer BufferB(
Bits(S.getASTContext().getTypeSize(PtrB.getFieldDesc()->getType())));
readPointerToBuffer(S.getContext(), PtrB, BufferB, false);
size_t MinBufferSize = std::min(BufferA.byteSize().getQuantity(),
BufferB.byteSize().getQuantity());
size_t CmpSize = std::min(MinBufferSize, Size.getZExtValue());
int Result = std::memcmp(BufferA.Data.get(), BufferB.Data.get(), CmpSize);
if (Result == 0)
pushInteger(S, 0, Call->getType());
else if (Result < 0)
pushInteger(S, -1, Call->getType());
else
pushInteger(S, 1, Call->getType());
return true;
}
bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const Function *F,
const CallExpr *Call, uint32_t BuiltinID) {
const InterpFrame *Frame = S.Current;
@@ -2373,6 +2435,12 @@ bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const Function *F,
return false;
break;
case Builtin::BI__builtin_memcmp:
case Builtin::BImemcmp:
if (!interp__builtin_memcmp(S, OpPC, Frame, F, Call))
return false;
break;
default:
S.FFDiag(S.Current->getLocation(OpPC),
diag::note_invalid_subexpr_in_const_expr)

View File

@@ -259,8 +259,10 @@ static bool CheckBitcastType(InterpState &S, CodePtr OpPC, QualType T,
return true;
}
static bool readPointerToBuffer(const Context &Ctx, const Pointer &FromPtr,
BitcastBuffer &Buffer, bool ReturnOnUninit) {
bool clang::interp::readPointerToBuffer(const Context &Ctx,
const Pointer &FromPtr,
BitcastBuffer &Buffer,
bool ReturnOnUninit) {
const ASTContext &ASTCtx = Ctx.getASTContext();
Endian TargetEndianness =
ASTCtx.getTargetInfo().isLittleEndian() ? Endian::Little : Endian::Big;

View File

@@ -6,8 +6,8 @@
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_AST_INTERP_BUILITN_BIT_CAST_H
#define LLVM_CLANG_AST_INTERP_BUILITN_BIT_CAST_H
#ifndef LLVM_CLANG_AST_INTERP_BUILTIN_BIT_CAST_H
#define LLVM_CLANG_AST_INTERP_BUILTIN_BIT_CAST_H
#include "BitcastBuffer.h"
#include <cstddef>
@@ -17,6 +17,7 @@ namespace interp {
class Pointer;
class InterpState;
class CodePtr;
class Context;
bool DoBitCast(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
std::byte *Buff, Bits BitWidth, Bits FullBitWidth,
@@ -25,7 +26,8 @@ bool DoBitCastPtr(InterpState &S, CodePtr OpPC, const Pointer &FromPtr,
Pointer &ToPtr);
bool DoBitCastPtr(InterpState &S, CodePtr OpPC, const Pointer &FromPtr,
Pointer &ToPtr, size_t Size);
bool readPointerToBuffer(const Context &Ctx, const Pointer &FromPtr,
BitcastBuffer &Buffer, bool ReturnOnUninit);
} // namespace interp
} // namespace clang

View File

@@ -1223,3 +1223,36 @@ namespace BuiltinMemcpy {
static_assert(test_memcpy(0, 1, sizeof(int) * 2) == 2334); // both-error {{not an integral constant expression}} \
// both-note {{in call}}
}
namespace Memcmp {
constexpr unsigned char ku00fe00[] = {0x00, 0xfe, 0x00};
constexpr unsigned char ku00feff[] = {0x00, 0xfe, 0xff};
constexpr signed char ks00fe00[] = {0, -2, 0};
constexpr signed char ks00feff[] = {0, -2, -1};
static_assert(__builtin_memcmp(ku00feff, ks00fe00, 2) == 0);
static_assert(__builtin_memcmp(ku00feff, ks00fe00, 99) == 1);
static_assert(__builtin_memcmp(ku00fe00, ks00feff, 99) == -1);
static_assert(__builtin_memcmp(ks00feff, ku00fe00, 2) == 0);
static_assert(__builtin_memcmp(ks00feff, ku00fe00, 99) == 1);
static_assert(__builtin_memcmp(ks00fe00, ku00feff, 99) == -1);
static_assert(__builtin_memcmp(ks00fe00, ks00feff, 2) == 0);
static_assert(__builtin_memcmp(ks00feff, ks00fe00, 99) == 1);
static_assert(__builtin_memcmp(ks00fe00, ks00feff, 99) == -1);
struct Bool3Tuple { bool bb[3]; };
constexpr Bool3Tuple kb000100 = {{false, true, false}};
static_assert(sizeof(bool) != 1u || __builtin_memcmp(ks00fe00, kb000100.bb, 1) == 0); // both-error {{constant}} \
// both-note {{not supported}}
constexpr char a = 'a';
constexpr char b = 'a';
static_assert(__builtin_memcmp(&a, &b, 1) == 0);
extern struct Incomplete incomplete;
static_assert(__builtin_memcmp(&incomplete, "", 0u) == 0);
static_assert(__builtin_memcmp("", &incomplete, 0u) == 0);
static_assert(__builtin_memcmp(&incomplete, "", 1u) == 42); // both-error {{not an integral constant}} \
// both-note {{not supported}}
static_assert(__builtin_memcmp("", &incomplete, 1u) == 42); // both-error {{not an integral constant}} \
// both-note {{not supported}}
}