Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 42 additions & 11 deletions clang/lib/AST/ByteCode/Compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,18 @@ template <class Emitter> class OptionScope final {
public:
/// Root constructor, compiling or discarding primitives.
OptionScope(Compiler<Emitter> *Ctx, bool NewDiscardResult,
bool NewInitializing)
bool NewInitializing, bool NewToLValue)
: Ctx(Ctx), OldDiscardResult(Ctx->DiscardResult),
OldInitializing(Ctx->Initializing) {
OldInitializing(Ctx->Initializing), OldToLValue(NewToLValue) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should you be using Ctx->ToLValue to initialize OldToLValue?

Ctx->DiscardResult = NewDiscardResult;
Ctx->Initializing = NewInitializing;
Ctx->ToLValue = NewToLValue;
}

~OptionScope() {
Ctx->DiscardResult = OldDiscardResult;
Ctx->Initializing = OldInitializing;
Ctx->ToLValue = OldToLValue;
}

private:
Expand All @@ -78,6 +80,7 @@ template <class Emitter> class OptionScope final {
/// Old discard flag to restore.
bool OldDiscardResult;
bool OldInitializing;
bool OldToLValue;
};

template <class Emitter>
Expand Down Expand Up @@ -222,6 +225,9 @@ bool Compiler<Emitter>::VisitCastExpr(const CastExpr *CE) {

switch (CE->getCastKind()) {
case CK_LValueToRValue: {
if (ToLValue && CE->getType()->isPointerType())
return this->delegate(SubExpr);

if (SubExpr->getType().isVolatileQualified())
return this->emitInvalidCast(CastKind::Volatile, /*Fatal=*/true, CE);

Expand Down Expand Up @@ -4140,13 +4146,13 @@ bool Compiler<Emitter>::VisitStmtExpr(const StmtExpr *E) {

template <class Emitter> bool Compiler<Emitter>::discard(const Expr *E) {
OptionScope<Emitter> Scope(this, /*NewDiscardResult=*/true,
/*NewInitializing=*/false);
/*NewInitializing=*/false, /*ToLValue=*/false);
return this->Visit(E);
}

template <class Emitter> bool Compiler<Emitter>::delegate(const Expr *E) {
// We're basically doing:
// OptionScope<Emitter> Scope(this, DicardResult, Initializing);
// OptionScope<Emitter> Scope(this, DicardResult, Initializing, ToLValue);
// but that's unnecessary of course.
return this->Visit(E);
}
Expand Down Expand Up @@ -4174,7 +4180,7 @@ template <class Emitter> bool Compiler<Emitter>::visit(const Expr *E) {
// Otherwise,we have a primitive return value, produce the value directly
// and push it on the stack.
OptionScope<Emitter> Scope(this, /*NewDiscardResult=*/false,
/*NewInitializing=*/false);
/*NewInitializing=*/false, /*ToLValue=*/ToLValue);
return this->Visit(E);
}

Expand All @@ -4183,7 +4189,13 @@ bool Compiler<Emitter>::visitInitializer(const Expr *E) {
assert(!canClassify(E->getType()));

OptionScope<Emitter> Scope(this, /*NewDiscardResult=*/false,
/*NewInitializing=*/true);
/*NewInitializing=*/true, /*ToLValue=*/false);
return this->Visit(E);
}

template <class Emitter> bool Compiler<Emitter>::visitAsLValue(const Expr *E) {
OptionScope<Emitter> Scope(this, /*NewDiscardResult=*/false,
/*NewInitializing=*/false, /*ToLValue=*/true);
return this->Visit(E);
}

Expand Down Expand Up @@ -4944,7 +4956,6 @@ bool Compiler<Emitter>::visitAPValueInitializer(const APValue &Val,
template <class Emitter>
bool Compiler<Emitter>::VisitBuiltinCallExpr(const CallExpr *E,
unsigned BuiltinID) {

if (BuiltinID == Builtin::BI__builtin_constant_p) {
// Void argument is always invalid and harder to handle later.
if (E->getArg(0)->getType()->isVoidType()) {
Expand Down Expand Up @@ -4989,12 +5000,32 @@ bool Compiler<Emitter>::VisitBuiltinCallExpr(const CallExpr *E,
return false;
}

if (!Context::isUnevaluatedBuiltin(BuiltinID)) {
// Put arguments on the stack.
for (const auto *Arg : E->arguments()) {
if (!this->visit(Arg))
// Prepare function arguments including special cases.
switch (BuiltinID) {
case Builtin::BI__builtin_object_size:
case Builtin::BI__builtin_dynamic_object_size: {
assert(E->getNumArgs() == 2);
const Expr *Arg0 = E->getArg(0);
if (Arg0->isGLValue()) {
if (!this->visit(Arg0))
return false;

} else {
if (!this->visitAsLValue(Arg0))
return false;
}
if (!this->visit(E->getArg(1)))
return false;

} break;
default:
if (!Context::isUnevaluatedBuiltin(BuiltinID)) {
// Put arguments on the stack.
for (const auto *Arg : E->arguments()) {
if (!this->visit(Arg))
return false;
}
}
}

if (!this->emitCallBI(E, BuiltinID, E))
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/AST/ByteCode/Compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
/// been created. visitInitializer() then relies on a pointer to this
/// variable being on top of the stack.
bool visitInitializer(const Expr *E);
bool visitAsLValue(const Expr *E);
/// Evaluates an expression for side effects and discards the result.
bool discard(const Expr *E);
/// Just pass evaluation on to \p E. This leaves all the parsing flags
Expand Down Expand Up @@ -426,6 +427,7 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
bool DiscardResult = false;

bool InStmtExpr = false;
bool ToLValue = false;

/// Flag inidicating if we're initializing an already created
/// variable. This is set in visitInitializer().
Expand Down
150 changes: 129 additions & 21 deletions clang/lib/AST/ByteCode/InterpBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2170,29 +2170,32 @@ static bool interp__builtin_memchr(InterpState &S, CodePtr OpPC,
return true;
}

static unsigned computeFullDescSize(const ASTContext &ASTCtx,
const Descriptor *Desc) {

static std::optional<unsigned> computeFullDescSize(const ASTContext &ASTCtx,
const Descriptor *Desc) {
if (Desc->isPrimitive())
return ASTCtx.getTypeSizeInChars(Desc->getType()).getQuantity();

if (Desc->isArray())
return ASTCtx.getTypeSizeInChars(Desc->getElemQualType()).getQuantity() *
Desc->getNumElems();
if (Desc->isRecord()) {
// Can't use Descriptor::getType() as that may return a pointer type. Look
// at the decl directly.
return ASTCtx
.getTypeSizeInChars(
ASTCtx.getCanonicalTagType(Desc->ElemRecord->getDecl()))
.getQuantity();
}

if (Desc->isRecord())
return ASTCtx.getTypeSizeInChars(Desc->getType()).getQuantity();

llvm_unreachable("Unhandled descriptor type");
return 0;
return std::nullopt;
}

/// Compute the byte offset of \p Ptr in the full declaration.
static unsigned computePointerOffset(const ASTContext &ASTCtx,
const Pointer &Ptr) {
unsigned Result = 0;

Pointer P = Ptr;
while (P.isArrayElement() || P.isField()) {
while (P.isField() || P.isArrayElement()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you switched the order b/d isField is way more likely to be true and short circuit? If so that deserve a comment for future folks to keep in mind.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the previous order just caused some assertion failures in isArrayElement IIRC. This version works for more cases.

P = P.expand();
const Descriptor *D = P.getFieldDesc();

Expand All @@ -2205,7 +2208,6 @@ static unsigned computePointerOffset(const ASTContext &ASTCtx,
Result += ElemSize * P.getIndex();
P = P.expand().getArray();
} else if (P.isBaseClass()) {

const auto *RD = cast<CXXRecordDecl>(D->asDecl());
bool IsVirtual = Ptr.isVirtualBaseClass();
P = P.getBase();
Expand Down Expand Up @@ -2234,30 +2236,136 @@ static unsigned computePointerOffset(const ASTContext &ASTCtx,
return Result;
}

/// Does Ptr point to the last subobject?
static bool pointsToLastObject(const Pointer &Ptr) {
Pointer P = Ptr;
while (!P.isRoot()) {

if (P.isArrayElement()) {
P = P.expand().getArray();
continue;
}
if (P.isBaseClass()) {
if (P.getRecord()->getNumFields() > 0)
return false;
P = P.getBase();
continue;
}

Pointer Base = P.getBase();
if (const Record *R = Base.getRecord()) {
assert(P.getField());
if (P.getField()->getFieldIndex() != R->getNumFields() - 1)
return false;
}
P = Base;
}

return true;
}

/// Does Ptr point to the last object AND to a flexible array member?
static bool isUserWritingOffTheEnd(const ASTContext &Ctx, const Pointer &Ptr) {
auto isFlexibleArrayMember = [&](const Descriptor *FieldDesc) {
using FAMKind = LangOptions::StrictFlexArraysLevelKind;
FAMKind StrictFlexArraysLevel =
Ctx.getLangOpts().getStrictFlexArraysLevel();

if (StrictFlexArraysLevel == FAMKind::Default)
return true;

unsigned NumElems = FieldDesc->getNumElems();
if (NumElems == 0 && StrictFlexArraysLevel != FAMKind::IncompleteOnly)
return true;

if (NumElems == 1 && StrictFlexArraysLevel == FAMKind::OneZeroOrIncomplete)
return true;
return false;
};

const Descriptor *FieldDesc = Ptr.getFieldDesc();
if (!FieldDesc->isArray())
return false;

return Ptr.isDummy() && pointsToLastObject(Ptr) &&
isFlexibleArrayMember(FieldDesc);
}

static bool interp__builtin_object_size(InterpState &S, CodePtr OpPC,
const InterpFrame *Frame,
const CallExpr *Call) {
const ASTContext &ASTCtx = S.getASTContext();
PrimType KindT = *S.getContext().classify(Call->getArg(1));
[[maybe_unused]] unsigned Kind = popToAPSInt(S.Stk, KindT).getZExtValue();

// From the GCC docs:
// Kind is an integer constant from 0 to 3. If the least significant bit is
// clear, objects are whole variables. If it is set, a closest surrounding
// subobject is considered the object a pointer points to. The second bit
// determines if maximum or minimum of remaining bytes is computed.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It unhelpfully does not tell you whether set of unset means maximum. I am guessing you had to derive that empirically. It would be nice if the documentation was more explicit.

unsigned Kind = popToAPSInt(S.Stk, KindT).getZExtValue();
assert(Kind <= 3 && "unexpected kind");

bool UseFieldDesc = (Kind & 1u);
bool ReportMinimum = (Kind & 2u);
const Pointer &Ptr = S.Stk.pop<Pointer>();

if (Ptr.isZero())
if (Call->getArg(0)->HasSideEffects(ASTCtx)) {
// "If there are any side effects in them, it returns (size_t) -1
// for type 0 or 1 and (size_t) 0 for type 2 or 3."
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe instead of calling Kind you could call it Type b/c you edited the gcc documentation previously but failed to do so here. I think being more consistent w/ the documentation would help in future.

pushInteger(S, Kind <= 1 ? -1 : 0, Call->getType());
return true;
}

if (Ptr.isZero() || !Ptr.isBlockPointer())
return false;

const Descriptor *DeclDesc = Ptr.getDeclDesc();
if (!DeclDesc)
// We can't load through pointers.
if (Ptr.isDummy() && Ptr.getType()->isPointerType())
return false;

const ASTContext &ASTCtx = S.getASTContext();
bool DetermineForCompleteObject = Ptr.getFieldDesc() == Ptr.getDeclDesc();
const Descriptor *DeclDesc = Ptr.getDeclDesc();
assert(DeclDesc);

unsigned ByteOffset = computePointerOffset(ASTCtx, Ptr);
unsigned FullSize = computeFullDescSize(ASTCtx, DeclDesc);
if (!UseFieldDesc || DetermineForCompleteObject) {
// Lower bound, so we can't fall back to this.
if (ReportMinimum && !DetermineForCompleteObject)
return false;

pushInteger(S, FullSize - ByteOffset, Call->getType());
// Can't read beyond the pointer decl desc.
if (!UseFieldDesc && !ReportMinimum && DeclDesc->getType()->isPointerType())
return false;
} else {
if (isUserWritingOffTheEnd(ASTCtx, Ptr.expand())) {
// If we cannot determine the size of the initial allocation, then we
// can't given an accurate upper-bound. However, we are still able to give
// conservative lower-bounds for Type=3.
if (Kind == 1)
return false;
}
}

const Descriptor *Desc = UseFieldDesc ? Ptr.getFieldDesc() : DeclDesc;
assert(Desc);

std::optional<unsigned> FullSize = computeFullDescSize(ASTCtx, Desc);
if (!FullSize)
return false;

unsigned ByteOffset;
if (UseFieldDesc) {
if (Ptr.isBaseClass())
ByteOffset = computePointerOffset(ASTCtx, Ptr.getBase()) -
computePointerOffset(ASTCtx, Ptr);
else
ByteOffset =
computePointerOffset(ASTCtx, Ptr) -
computePointerOffset(ASTCtx, Ptr.expand().atIndex(0).narrow());
} else
ByteOffset = computePointerOffset(ASTCtx, Ptr);

assert(ByteOffset <= *FullSize);
unsigned Result = *FullSize - ByteOffset;

pushInteger(S, Result, Call->getType());
return true;
}

Expand Down
4 changes: 2 additions & 2 deletions clang/lib/AST/ByteCode/Program.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,8 @@ unsigned Program::getOrCreateDummy(const DeclTy &D) {
const auto *VD = cast<ValueDecl>(cast<const Decl *>(D));
IsWeak = VD->isWeak();
QT = VD->getType();
if (const auto *RT = QT->getAs<ReferenceType>())
QT = RT->getPointeeType();
if (QT->isPointerOrReferenceType())
QT = QT->getPointeeType();
}
assert(!QT.isNull());

Expand Down
Loading