Skip to content

Commit 0d05c42

Browse files
authored
[clang][bytecode] Improve __builtin_{,dynamic_}object_size implementation (#153601)
1 parent bcab8ac commit 0d05c42

File tree

8 files changed

+268
-47
lines changed

8 files changed

+268
-47
lines changed

clang/lib/AST/ByteCode/Compiler.cpp

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,18 @@ template <class Emitter> class OptionScope final {
6060
public:
6161
/// Root constructor, compiling or discarding primitives.
6262
OptionScope(Compiler<Emitter> *Ctx, bool NewDiscardResult,
63-
bool NewInitializing)
63+
bool NewInitializing, bool NewToLValue)
6464
: Ctx(Ctx), OldDiscardResult(Ctx->DiscardResult),
65-
OldInitializing(Ctx->Initializing) {
65+
OldInitializing(Ctx->Initializing), OldToLValue(NewToLValue) {
6666
Ctx->DiscardResult = NewDiscardResult;
6767
Ctx->Initializing = NewInitializing;
68+
Ctx->ToLValue = NewToLValue;
6869
}
6970

7071
~OptionScope() {
7172
Ctx->DiscardResult = OldDiscardResult;
7273
Ctx->Initializing = OldInitializing;
74+
Ctx->ToLValue = OldToLValue;
7375
}
7476

7577
private:
@@ -78,6 +80,7 @@ template <class Emitter> class OptionScope final {
7880
/// Old discard flag to restore.
7981
bool OldDiscardResult;
8082
bool OldInitializing;
83+
bool OldToLValue;
8184
};
8285

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

223226
switch (CE->getCastKind()) {
224227
case CK_LValueToRValue: {
228+
if (ToLValue && CE->getType()->isPointerType())
229+
return this->delegate(SubExpr);
230+
225231
if (SubExpr->getType().isVolatileQualified())
226232
return this->emitInvalidCast(CastKind::Volatile, /*Fatal=*/true, CE);
227233

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

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

41474153
template <class Emitter> bool Compiler<Emitter>::delegate(const Expr *E) {
41484154
// We're basically doing:
4149-
// OptionScope<Emitter> Scope(this, DicardResult, Initializing);
4155+
// OptionScope<Emitter> Scope(this, DicardResult, Initializing, ToLValue);
41504156
// but that's unnecessary of course.
41514157
return this->Visit(E);
41524158
}
@@ -4174,7 +4180,7 @@ template <class Emitter> bool Compiler<Emitter>::visit(const Expr *E) {
41744180
// Otherwise,we have a primitive return value, produce the value directly
41754181
// and push it on the stack.
41764182
OptionScope<Emitter> Scope(this, /*NewDiscardResult=*/false,
4177-
/*NewInitializing=*/false);
4183+
/*NewInitializing=*/false, /*ToLValue=*/ToLValue);
41784184
return this->Visit(E);
41794185
}
41804186

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

41854191
OptionScope<Emitter> Scope(this, /*NewDiscardResult=*/false,
4186-
/*NewInitializing=*/true);
4192+
/*NewInitializing=*/true, /*ToLValue=*/false);
4193+
return this->Visit(E);
4194+
}
4195+
4196+
template <class Emitter> bool Compiler<Emitter>::visitAsLValue(const Expr *E) {
4197+
OptionScope<Emitter> Scope(this, /*NewDiscardResult=*/false,
4198+
/*NewInitializing=*/false, /*ToLValue=*/true);
41874199
return this->Visit(E);
41884200
}
41894201

@@ -4944,7 +4956,6 @@ bool Compiler<Emitter>::visitAPValueInitializer(const APValue &Val,
49444956
template <class Emitter>
49454957
bool Compiler<Emitter>::VisitBuiltinCallExpr(const CallExpr *E,
49464958
unsigned BuiltinID) {
4947-
49484959
if (BuiltinID == Builtin::BI__builtin_constant_p) {
49494960
// Void argument is always invalid and harder to handle later.
49504961
if (E->getArg(0)->getType()->isVoidType()) {
@@ -4989,12 +5000,32 @@ bool Compiler<Emitter>::VisitBuiltinCallExpr(const CallExpr *E,
49895000
return false;
49905001
}
49915002

4992-
if (!Context::isUnevaluatedBuiltin(BuiltinID)) {
4993-
// Put arguments on the stack.
4994-
for (const auto *Arg : E->arguments()) {
4995-
if (!this->visit(Arg))
5003+
// Prepare function arguments including special cases.
5004+
switch (BuiltinID) {
5005+
case Builtin::BI__builtin_object_size:
5006+
case Builtin::BI__builtin_dynamic_object_size: {
5007+
assert(E->getNumArgs() == 2);
5008+
const Expr *Arg0 = E->getArg(0);
5009+
if (Arg0->isGLValue()) {
5010+
if (!this->visit(Arg0))
5011+
return false;
5012+
5013+
} else {
5014+
if (!this->visitAsLValue(Arg0))
49965015
return false;
49975016
}
5017+
if (!this->visit(E->getArg(1)))
5018+
return false;
5019+
5020+
} break;
5021+
default:
5022+
if (!Context::isUnevaluatedBuiltin(BuiltinID)) {
5023+
// Put arguments on the stack.
5024+
for (const auto *Arg : E->arguments()) {
5025+
if (!this->visit(Arg))
5026+
return false;
5027+
}
5028+
}
49985029
}
49995030

50005031
if (!this->emitCallBI(E, BuiltinID, E))

clang/lib/AST/ByteCode/Compiler.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
282282
/// been created. visitInitializer() then relies on a pointer to this
283283
/// variable being on top of the stack.
284284
bool visitInitializer(const Expr *E);
285+
bool visitAsLValue(const Expr *E);
285286
/// Evaluates an expression for side effects and discards the result.
286287
bool discard(const Expr *E);
287288
/// Just pass evaluation on to \p E. This leaves all the parsing flags
@@ -426,6 +427,7 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
426427
bool DiscardResult = false;
427428

428429
bool InStmtExpr = false;
430+
bool ToLValue = false;
429431

430432
/// Flag inidicating if we're initializing an already created
431433
/// variable. This is set in visitInitializer().

clang/lib/AST/ByteCode/InterpBuiltin.cpp

Lines changed: 129 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2170,29 +2170,32 @@ static bool interp__builtin_memchr(InterpState &S, CodePtr OpPC,
21702170
return true;
21712171
}
21722172

2173-
static unsigned computeFullDescSize(const ASTContext &ASTCtx,
2174-
const Descriptor *Desc) {
2175-
2173+
static std::optional<unsigned> computeFullDescSize(const ASTContext &ASTCtx,
2174+
const Descriptor *Desc) {
21762175
if (Desc->isPrimitive())
21772176
return ASTCtx.getTypeSizeInChars(Desc->getType()).getQuantity();
2178-
21792177
if (Desc->isArray())
21802178
return ASTCtx.getTypeSizeInChars(Desc->getElemQualType()).getQuantity() *
21812179
Desc->getNumElems();
2180+
if (Desc->isRecord()) {
2181+
// Can't use Descriptor::getType() as that may return a pointer type. Look
2182+
// at the decl directly.
2183+
return ASTCtx
2184+
.getTypeSizeInChars(
2185+
ASTCtx.getCanonicalTagType(Desc->ElemRecord->getDecl()))
2186+
.getQuantity();
2187+
}
21822188

2183-
if (Desc->isRecord())
2184-
return ASTCtx.getTypeSizeInChars(Desc->getType()).getQuantity();
2185-
2186-
llvm_unreachable("Unhandled descriptor type");
2187-
return 0;
2189+
return std::nullopt;
21882190
}
21892191

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

21942197
Pointer P = Ptr;
2195-
while (P.isArrayElement() || P.isField()) {
2198+
while (P.isField() || P.isArrayElement()) {
21962199
P = P.expand();
21972200
const Descriptor *D = P.getFieldDesc();
21982201

@@ -2205,7 +2208,6 @@ static unsigned computePointerOffset(const ASTContext &ASTCtx,
22052208
Result += ElemSize * P.getIndex();
22062209
P = P.expand().getArray();
22072210
} else if (P.isBaseClass()) {
2208-
22092211
const auto *RD = cast<CXXRecordDecl>(D->asDecl());
22102212
bool IsVirtual = Ptr.isVirtualBaseClass();
22112213
P = P.getBase();
@@ -2234,30 +2236,136 @@ static unsigned computePointerOffset(const ASTContext &ASTCtx,
22342236
return Result;
22352237
}
22362238

2239+
/// Does Ptr point to the last subobject?
2240+
static bool pointsToLastObject(const Pointer &Ptr) {
2241+
Pointer P = Ptr;
2242+
while (!P.isRoot()) {
2243+
2244+
if (P.isArrayElement()) {
2245+
P = P.expand().getArray();
2246+
continue;
2247+
}
2248+
if (P.isBaseClass()) {
2249+
if (P.getRecord()->getNumFields() > 0)
2250+
return false;
2251+
P = P.getBase();
2252+
continue;
2253+
}
2254+
2255+
Pointer Base = P.getBase();
2256+
if (const Record *R = Base.getRecord()) {
2257+
assert(P.getField());
2258+
if (P.getField()->getFieldIndex() != R->getNumFields() - 1)
2259+
return false;
2260+
}
2261+
P = Base;
2262+
}
2263+
2264+
return true;
2265+
}
2266+
2267+
/// Does Ptr point to the last object AND to a flexible array member?
2268+
static bool isUserWritingOffTheEnd(const ASTContext &Ctx, const Pointer &Ptr) {
2269+
auto isFlexibleArrayMember = [&](const Descriptor *FieldDesc) {
2270+
using FAMKind = LangOptions::StrictFlexArraysLevelKind;
2271+
FAMKind StrictFlexArraysLevel =
2272+
Ctx.getLangOpts().getStrictFlexArraysLevel();
2273+
2274+
if (StrictFlexArraysLevel == FAMKind::Default)
2275+
return true;
2276+
2277+
unsigned NumElems = FieldDesc->getNumElems();
2278+
if (NumElems == 0 && StrictFlexArraysLevel != FAMKind::IncompleteOnly)
2279+
return true;
2280+
2281+
if (NumElems == 1 && StrictFlexArraysLevel == FAMKind::OneZeroOrIncomplete)
2282+
return true;
2283+
return false;
2284+
};
2285+
2286+
const Descriptor *FieldDesc = Ptr.getFieldDesc();
2287+
if (!FieldDesc->isArray())
2288+
return false;
2289+
2290+
return Ptr.isDummy() && pointsToLastObject(Ptr) &&
2291+
isFlexibleArrayMember(FieldDesc);
2292+
}
2293+
22372294
static bool interp__builtin_object_size(InterpState &S, CodePtr OpPC,
22382295
const InterpFrame *Frame,
22392296
const CallExpr *Call) {
2297+
const ASTContext &ASTCtx = S.getASTContext();
22402298
PrimType KindT = *S.getContext().classify(Call->getArg(1));
2241-
[[maybe_unused]] unsigned Kind = popToAPSInt(S.Stk, KindT).getZExtValue();
2242-
2299+
// From the GCC docs:
2300+
// Kind is an integer constant from 0 to 3. If the least significant bit is
2301+
// clear, objects are whole variables. If it is set, a closest surrounding
2302+
// subobject is considered the object a pointer points to. The second bit
2303+
// determines if maximum or minimum of remaining bytes is computed.
2304+
unsigned Kind = popToAPSInt(S.Stk, KindT).getZExtValue();
22432305
assert(Kind <= 3 && "unexpected kind");
2244-
2306+
bool UseFieldDesc = (Kind & 1u);
2307+
bool ReportMinimum = (Kind & 2u);
22452308
const Pointer &Ptr = S.Stk.pop<Pointer>();
22462309

2247-
if (Ptr.isZero())
2310+
if (Call->getArg(0)->HasSideEffects(ASTCtx)) {
2311+
// "If there are any side effects in them, it returns (size_t) -1
2312+
// for type 0 or 1 and (size_t) 0 for type 2 or 3."
2313+
pushInteger(S, Kind <= 1 ? -1 : 0, Call->getType());
2314+
return true;
2315+
}
2316+
2317+
if (Ptr.isZero() || !Ptr.isBlockPointer())
22482318
return false;
22492319

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

2254-
const ASTContext &ASTCtx = S.getASTContext();
2324+
bool DetermineForCompleteObject = Ptr.getFieldDesc() == Ptr.getDeclDesc();
2325+
const Descriptor *DeclDesc = Ptr.getDeclDesc();
2326+
assert(DeclDesc);
22552327

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

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

2346+
const Descriptor *Desc = UseFieldDesc ? Ptr.getFieldDesc() : DeclDesc;
2347+
assert(Desc);
2348+
2349+
std::optional<unsigned> FullSize = computeFullDescSize(ASTCtx, Desc);
2350+
if (!FullSize)
2351+
return false;
2352+
2353+
unsigned ByteOffset;
2354+
if (UseFieldDesc) {
2355+
if (Ptr.isBaseClass())
2356+
ByteOffset = computePointerOffset(ASTCtx, Ptr.getBase()) -
2357+
computePointerOffset(ASTCtx, Ptr);
2358+
else
2359+
ByteOffset =
2360+
computePointerOffset(ASTCtx, Ptr) -
2361+
computePointerOffset(ASTCtx, Ptr.expand().atIndex(0).narrow());
2362+
} else
2363+
ByteOffset = computePointerOffset(ASTCtx, Ptr);
2364+
2365+
assert(ByteOffset <= *FullSize);
2366+
unsigned Result = *FullSize - ByteOffset;
2367+
2368+
pushInteger(S, Result, Call->getType());
22612369
return true;
22622370
}
22632371

clang/lib/AST/ByteCode/Program.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,8 @@ unsigned Program::getOrCreateDummy(const DeclTy &D) {
164164
const auto *VD = cast<ValueDecl>(cast<const Decl *>(D));
165165
IsWeak = VD->isWeak();
166166
QT = VD->getType();
167-
if (const auto *RT = QT->getAs<ReferenceType>())
168-
QT = RT->getPointeeType();
167+
if (QT->isPointerOrReferenceType())
168+
QT = QT->getPointeeType();
169169
}
170170
assert(!QT.isNull());
171171

0 commit comments

Comments
 (0)