Index: lib/AST/ExprConstant.cpp =================================================================== --- lib/AST/ExprConstant.cpp +++ lib/AST/ExprConstant.cpp @@ -259,29 +259,9 @@ MostDerivedPathLength = Entries.size(); } void diagnosePointerArithmetic(EvalInfo &Info, const Expr *E, uint64_t N); - /// Add N to the address of this subobject. - void adjustIndex(EvalInfo &Info, const Expr *E, uint64_t N) { - if (Invalid) return; - if (MostDerivedPathLength == Entries.size() && MostDerivedArraySize) { - Entries.back().ArrayIndex += N; - if (Entries.back().ArrayIndex > MostDerivedArraySize) { - diagnosePointerArithmetic(Info, E, Entries.back().ArrayIndex); - setInvalid(); - } - return; - } - // [expr.add]p4: For the purposes of these operators, a pointer to a - // nonarray object behaves the same as a pointer to the first element of - // an array of length one with the type of the object as its element type. - if (IsOnePastTheEnd && N == (uint64_t)-1) - IsOnePastTheEnd = false; - else if (!IsOnePastTheEnd && N == 1) - IsOnePastTheEnd = true; - else if (N != 0) { - diagnosePointerArithmetic(Info, E, uint64_t(IsOnePastTheEnd) + N); - setInvalid(); - } - } + + /// Add N to the index of this subobject. + void adjustIndex(EvalInfo &Info, const Expr *E, uint64_t N); }; /// A stack frame in the constexpr call stack. @@ -492,14 +472,33 @@ /// optimizer if we don't constant fold them here, but in an unevaluated /// context we try to fold them immediately since the optimizer never /// gets a chance to look at it. - EM_PotentialConstantExpressionUnevaluated + EM_PotentialConstantExpressionUnevaluated, + + /// Evaluate as a potential constant expression, ignoring any side-effects + /// that may occur. The intent of this mode is to determine an LValue's + /// Offset, so things not ordinarily allowed in constexprs + /// (reinterpret_casts, OOB array indices, etc.) are allowed. As such, the + /// Offset of any given LValue may not be a multiple of the LValue's + /// type's size (e.g. in + /// short s[2]; + /// char *p = (char*)s + 1;) + /// + /// Additionally, this mode is allowed to continue evaluating if an LValue + /// base is determined to be invalid, but the members of the base can be + /// determined. + EM_OffsetFold, + + /// Identical to EM_OffsetFold, but we're evaluating as though the + /// expression is a constant expression. + EM_ConstantExpressionOffsetFold, } EvalMode; /// Are we checking whether the expression is a potential constant /// expression? bool checkingPotentialConstantExpression() const { return EvalMode == EM_PotentialConstantExpression || - EvalMode == EM_PotentialConstantExpressionUnevaluated; + EvalMode == EM_PotentialConstantExpressionUnevaluated || + EvalMode == EM_OffsetFold; } /// Are we checking an expression for overflow? @@ -595,6 +594,8 @@ case EM_PotentialConstantExpression: case EM_ConstantExpressionUnevaluated: case EM_PotentialConstantExpressionUnevaluated: + case EM_OffsetFold: + case EM_ConstantExpressionOffsetFold: HasActiveDiagnostic = false; return OptionalDiagnostic(); } @@ -669,11 +670,13 @@ case EM_PotentialConstantExpressionUnevaluated: case EM_EvaluateForOverflow: case EM_IgnoreSideEffects: + case EM_OffsetFold: return true; case EM_ConstantExpression: case EM_ConstantExpressionUnevaluated: case EM_ConstantFold: + case EM_ConstantExpressionOffsetFold: return false; } llvm_unreachable("Missed EvalMode case"); @@ -696,16 +699,38 @@ case EM_PotentialConstantExpression: case EM_PotentialConstantExpressionUnevaluated: case EM_EvaluateForOverflow: + case EM_OffsetFold: return true; case EM_ConstantExpression: case EM_ConstantExpressionUnevaluated: case EM_ConstantFold: case EM_IgnoreSideEffects: + case EM_ConstantExpressionOffsetFold: return false; } llvm_unreachable("Missed EvalMode case"); } + + bool allowReinterpretCasts() const { + return EvalMode == EM_OffsetFold || + EvalMode == EM_ConstantExpressionOffsetFold; + } + + bool allowOutOfBoundsIndices() const { + return EvalMode == EM_OffsetFold || + EvalMode == EM_ConstantExpressionOffsetFold; + } + + bool allowNonObjectBoundaryOffsets() const { + return EvalMode == EM_OffsetFold || + EvalMode == EM_ConstantExpressionOffsetFold; + } + + bool allowInvalidBaseExpr() const { + return EvalMode == EM_OffsetFold || + EvalMode == EM_ConstantExpressionOffsetFold; + } }; /// Object used to treat all foldable expressions as constant expressions. @@ -736,6 +761,22 @@ } }; + /// RAII object used to treat the current evaluation as the correct pointer + /// offset fold for the current EvalMode + struct FoldOffsetRAII { + EvalInfo &Info; + EvalInfo::EvaluationMode OldMode; + explicit FoldOffsetRAII(EvalInfo &Info) + : Info(Info), OldMode(Info.EvalMode) { + if (Info.checkingPotentialConstantExpression()) + Info.EvalMode = EvalInfo::EM_OffsetFold; + else + Info.EvalMode = EvalInfo::EM_ConstantExpressionOffsetFold; + } + + ~FoldOffsetRAII() { Info.EvalMode = OldMode; } + }; + /// RAII object used to suppress diagnostics and side-effects from a /// speculative evaluation. class SpeculativeEvaluationRAII { @@ -818,6 +859,30 @@ setInvalid(); } +void SubobjectDesignator::adjustIndex(EvalInfo &Info, const Expr *E, uint64_t N) { + if (Invalid) return; + if (MostDerivedPathLength == Entries.size() && MostDerivedArraySize) { + Entries.back().ArrayIndex += N; + if (!Info.allowOutOfBoundsIndices() && + Entries.back().ArrayIndex > MostDerivedArraySize) { + diagnosePointerArithmetic(Info, E, Entries.back().ArrayIndex); + setInvalid(); + } + return; + } + // [expr.add]p4: For the purposes of these operators, a pointer to a + // nonarray object behaves the same as a pointer to the first element of + // an array of length one with the type of the object as its element type. + if (IsOnePastTheEnd && N == (uint64_t)-1) + IsOnePastTheEnd = false; + else if (!IsOnePastTheEnd && N == 1) + IsOnePastTheEnd = true; + else if (N != 0) { + diagnosePointerArithmetic(Info, E, uint64_t(IsOnePastTheEnd) + N); + setInvalid(); + } +} + CallStackFrame::CallStackFrame(EvalInfo &Info, SourceLocation CallLoc, const FunctionDecl *Callee, const LValue *This, APValue *Arguments) @@ -917,7 +982,8 @@ struct LValue { APValue::LValueBase Base; CharUnits Offset; - unsigned CallIndex; + bool InvalidBase : 1; + unsigned CallIndex : 31; SubobjectDesignator Designator; const APValue::LValueBase getLValueBase() const { return Base; } @@ -938,17 +1004,23 @@ assert(V.isLValue()); Base = V.getLValueBase(); Offset = V.getLValueOffset(); + InvalidBase = false; CallIndex = V.getLValueCallIndex(); Designator = SubobjectDesignator(Ctx, V); } - void set(APValue::LValueBase B, unsigned I = 0) { + void set(APValue::LValueBase B, unsigned I = 0, bool BInvalid = false) { Base = B; Offset = CharUnits::Zero(); + InvalidBase = BInvalid; CallIndex = I; Designator = SubobjectDesignator(getType(B)); } + void setInvalid(APValue::LValueBase B, unsigned I = 0) { + set(B, I, true); + } + // Check that this LValue is not based on a null pointer. If it is, produce // a diagnostic and mark the designator as invalid. bool checkNullPointer(EvalInfo &Info, const Expr *E, @@ -1905,8 +1977,33 @@ return false; // Compute the new offset in the appropriate width. - LVal.Offset += Adjustment * SizeOfPointee; - LVal.adjustIndex(Info, E, Adjustment); + CharUnits AddedOffset = Adjustment * SizeOfPointee; + int64_t IndexAdjustment = Adjustment; + + // If we allow offsets that aren't on object boundaries, we need to take + // into account any additional offsets that aren't fully accounted for + // (already) by indices + if (Info.allowNonObjectBoundaryOffsets()) { + QualType ArrayType = LVal.Designator.MostDerivedType; + // If ArrayType is null, we're offsetting a constant, not an array index. + if (!ArrayType.isNull() && !ArrayType->isIncompleteType() && + ArrayType != EltTy) { + CharUnits SizeOfArray; + if (!HandleSizeof(Info, E->getExprLoc(), ArrayType, SizeOfArray)) + return false; + + if (SizeOfArray != SizeOfPointee) { + CharUnits OffBoundary = + CharUnits::fromQuantity(LVal.Offset % SizeOfArray); + AddedOffset += OffBoundary; + LVal.Offset -= OffBoundary; + IndexAdjustment = AddedOffset / SizeOfArray; + } + } + } + + LVal.Offset += AddedOffset; + LVal.adjustIndex(Info, E, IndexAdjustment); return true; } @@ -3903,6 +4000,12 @@ bool DerivedZeroInitialization(const Expr *E) { return static_cast(this)->ZeroInitialization(E); } + // Called when we couldn't evaluate the LValue Base of a member expression, + // but the members could be visited properly. + bool DerivedInvalidBase(const APValue &V, const Expr *E) { + assert(Info.allowInvalidBaseExpr() && "This shouldn't be allowed"); + return static_cast(this)->InvalidBase(V, E); + } // Check whether a conditional operator with a non-constant condition is a // potential constant expression. If neither arm is a potential constant @@ -3952,6 +4055,11 @@ return Info.CCEDiag(E, D); } + // Some expr evaluators can't cleanly handle an invalid LValue base + bool InvalidBase(const APValue &V, const Expr *E) { + return DerivedZeroInitialization(E); + } + bool ZeroInitialization(const Expr *E) { return Error(E); } public: @@ -4189,10 +4297,16 @@ /// A member expression where the object is a prvalue is itself a prvalue. bool VisitMemberExpr(const MemberExpr *E) { assert(!E->isArrow() && "missing call to bound member function?"); - APValue Val; - if (!Evaluate(Val, Info, E->getBase())) - return false; + bool BaseInvalid = false; + if (!Evaluate(Val, Info, E->getBase())) { + if (!Info.allowInvalidBaseExpr()) + return false; + CharUnits Offset = CharUnits::Zero(); + APValue::NoLValuePath NoPath; + Val.setLValue(E->getBase(), Offset, NoPath, 0); + BaseInvalid = true; + } QualType BaseTy = E->getBase()->getType(); @@ -4207,8 +4321,13 @@ Designator.addDeclUnchecked(FD); APValue Result; - return extractSubobject(Info, E, Obj, Designator, Result) && - DerivedSuccess(Result, E); + if (!extractSubobject(Info, E, Obj, Designator, Result)) + return false; + + if (BaseInvalid) + return DerivedInvalidBase(Result, E); + else + return DerivedSuccess(Result, E); } bool VisitCastExpr(const CastExpr *E) { @@ -4337,23 +4456,31 @@ return true; } + bool InvalidBase(const APValue &V, const Expr *E) { + Result.setInvalid(E); + return true; + } + bool VisitMemberExpr(const MemberExpr *E) { // Handle non-static data members. QualType BaseTy; + bool EvalOK; if (E->isArrow()) { - if (!EvaluatePointer(E->getBase(), Result, this->Info)) - return false; + EvalOK = EvaluatePointer(E->getBase(), Result, this->Info); BaseTy = E->getBase()->getType()->castAs()->getPointeeType(); } else if (E->getBase()->isRValue()) { assert(E->getBase()->getType()->isRecordType()); - if (!EvaluateTemporary(E->getBase(), Result, this->Info)) - return false; + EvalOK = EvaluateTemporary(E->getBase(), Result, this->Info); BaseTy = E->getBase()->getType(); } else { - if (!this->Visit(E->getBase())) - return false; + EvalOK = this->Visit(E->getBase()); BaseTy = E->getBase()->getType(); } + if (!EvalOK) { + if (!this->Info.allowInvalidBaseExpr()) + return false; + Result.setInvalid(E->getBase()); + } const ValueDecl *MD = E->getMemberDecl(); if (const FieldDecl *FD = dyn_cast(E->getMemberDecl())) { @@ -4623,13 +4750,11 @@ } bool LValueExprEvaluator::VisitMemberExpr(const MemberExpr *E) { - // Handle static data members. if (const VarDecl *VD = dyn_cast(E->getMemberDecl())) { VisitIgnoredValue(E->getBase()); return VisitVarDecl(E, VD); } - // Handle static member functions. if (const CXXMethodDecl *MD = dyn_cast(E->getMemberDecl())) { if (MD->isStatic()) { VisitIgnoredValue(E->getBase()); @@ -4755,6 +4880,12 @@ Result.setFrom(Info.Ctx, V); return true; } + + bool InvalidBase(const APValue &V, const Expr *E) { + Result.setInvalid(E); + return true; + } + bool ZeroInitialization(const Expr *E) { return Success((Expr*)nullptr); } @@ -4765,7 +4896,7 @@ bool VisitObjCStringLiteral(const ObjCStringLiteral *E) { return Success(E); } bool VisitObjCBoxedExpr(const ObjCBoxedExpr *E) - { return Success(E); } + { return Success(E); } bool VisitAddrLabelExpr(const AddrLabelExpr *E) { return Success(E); } bool VisitCallExpr(const CallExpr *E); @@ -4846,7 +4977,7 @@ // Bitcasts to cv void* are static_casts, not reinterpret_casts, so are // permitted in constant expressions in C++11. Bitcasts from cv void* are // also static_casts, but we disallow them as a resolution to DR1312. - if (!E->getType()->isVoidPointerType()) { + if (!Info.allowReinterpretCasts() && !E->getType()->isVoidPointerType()) { Result.Designator.setInvalid(); if (SubExpr->getType()->isVoidPointerType()) CCEDiag(E, diag::note_constexpr_invalid_cast) @@ -6165,7 +6296,7 @@ /// Retrieves the "underlying object type" of the given expression, /// as used by __builtin_object_size. -static QualType getObjectType(APValue::LValueBase B) { +static QualType GetObjectType(APValue::LValueBase B) { if (const ValueDecl *D = B.dyn_cast()) { if (const VarDecl *VD = dyn_cast(D)) return VD->getType(); @@ -6186,16 +6317,15 @@ // If there are any, but we can determine the pointed-to object anyway, then // ignore the side-effects. SpeculativeEvaluationRAII SpeculativeEval(Info); - FoldConstant Fold(Info, true); + FoldOffsetRAII Fold(Info); if (!EvaluatePointer(E->getArg(0), Base, Info)) return false; } CharUnits BaseOffset = Base.getLValueOffset(); - - // If we point to before the start of the object, there are no - // accessible bytes. - if (BaseOffset < CharUnits::Zero()) + // If we point to before the start of the object, there are no accessible + // bytes. + if (BaseOffset.isNegative()) return Success(0, E); // MostDerivedType is null if we're dealing with a literal such as nullptr or @@ -6205,6 +6335,11 @@ if (Base.Designator.MostDerivedType.isNull()) return Error(E); + // If Type & 1 is 0, we need to be able to statically guarantee that the bytes + // exist. If we can't verify the base, then we can't do that. + if ((Type & 1) == 0 && Base.InvalidBase) + return Error(E); + // If Type & 1 is 0, the object in question is the complete object; reset to // a complete object designator in that case. // @@ -6212,8 +6347,8 @@ // object instead. (If Type is 3, that's not correct behavior and we should // return 0 instead.) LValue End = Base; - if (((Type & 1) == 0) || (End.Designator.Invalid && Type == 1)) { - QualType T = getObjectType(End.getLValueBase()); + if ((Type & 1) == 0 || (End.Designator.Invalid && Type == 1)) { + QualType T = GetObjectType(End.getLValueBase()); if (T.isNull()) End.Designator.setInvalid(); else { @@ -6222,15 +6357,14 @@ } } - // FIXME: We should produce a valid object size for an unknown object with a - // known designator, if Type & 1 is 1. For instance: + // We produce a valid object size for an unknown object with a known + // designator, if Type & 1 is 1. For instance: // // extern struct X { char buff[32]; int a, b, c; } *p; // int a = __builtin_object_size(p->buff + 4, 3); // returns 28 // int b = __builtin_object_size(p->buff + 4, 2); // returns 0, not 40 // - // This is GCC's behavior. We currently don't do this, but (hopefully) will in - // the near future. + // This matches GCC's behavior. // If it is not possible to determine which objects ptr points to at compile // time, __builtin_object_size should return (size_t) -1 for type 0 or 1 @@ -6244,23 +6378,29 @@ int64_t AmountToAdd = 1; if (End.Designator.MostDerivedArraySize && End.Designator.Entries.size() == End.Designator.MostDerivedPathLength) { - // We got a pointer to an array. Step to its end. + // We got a pointer to an array. Step to its end. Note that this can be + // negative (because array indices can be negative/OOB). AmountToAdd = End.Designator.MostDerivedArraySize - - End.Designator.Entries.back().ArrayIndex; - } else if (End.Designator.IsOnePastTheEnd) { + End.Designator.Entries.back().ArrayIndex; + } else if (End.Designator.isOnePastTheEnd()) { // We're already pointing at the end of the object. AmountToAdd = 0; } - if (End.Designator.MostDerivedType->isIncompleteType() || - End.Designator.MostDerivedType->isFunctionType()) + QualType PointeeType = End.Designator.MostDerivedType; + assert(!PointeeType.isNull()); + if (PointeeType->isIncompleteType() || PointeeType->isFunctionType()) return Error(E); - if (!HandleLValueArrayAdjustment(Info, E, End, End.Designator.MostDerivedType, - AmountToAdd)) + CharUnits SizeOfPointee; + if (!HandleSizeof(Info, E->getExprLoc(), PointeeType, SizeOfPointee)) return false; - auto EndOffset = End.getLValueOffset(); + CharUnits EndOffset = End.getLValueOffset(); + // If Start's Designator is offset from an object boundary, End's is, as well. + // We need to account for this. + EndOffset += SizeOfPointee * AmountToAdd; + EndOffset -= CharUnits::fromQuantity(EndOffset % SizeOfPointee); if (BaseOffset > EndOffset) return Success(0, E); @@ -6297,6 +6437,8 @@ case EvalInfo::EM_ConstantFold: case EvalInfo::EM_EvaluateForOverflow: case EvalInfo::EM_IgnoreSideEffects: + case EvalInfo::EM_OffsetFold: + case EvalInfo::EM_ConstantExpressionOffsetFold: // Leave it to IR generation. return Error(E); case EvalInfo::EM_ConstantExpressionUnevaluated: Index: test/CodeGen/object-size.c =================================================================== --- test/CodeGen/object-size.c +++ test/CodeGen/object-size.c @@ -161,6 +161,15 @@ gi = __builtin_object_size(&foo.a, 2); // CHECK: store i32 4 gi = __builtin_object_size(&foo.a, 3); + + // CHECK: store i32 4 + gi = __builtin_object_size(&foo.b, 0); + // CHECK: store i32 4 + gi = __builtin_object_size(&foo.b, 1); + // CHECK: store i32 4 + gi = __builtin_object_size(&foo.b, 2); + // CHECK: store i32 4 + gi = __builtin_object_size(&foo.b, 3); } // CHECK: @test20 @@ -221,25 +230,59 @@ gi = __builtin_object_size(&t[9].t[10], 2); // CHECK: store i32 0 gi = __builtin_object_size(&t[9].t[10], 3); + + // CHECK: store i32 0 + gi = __builtin_object_size((char*)&t[0] + sizeof(t), 0); + // CHECK: store i32 0 + gi = __builtin_object_size((char*)&t[0] + sizeof(t), 1); + // CHECK: store i32 0 + gi = __builtin_object_size((char*)&t[0] + sizeof(t), 2); + // CHECK: store i32 0 + gi = __builtin_object_size((char*)&t[0] + sizeof(t), 3); + + // CHECK: store i32 0 + gi = __builtin_object_size((char*)&t[9].t[0] + 10*sizeof(t[0].t), 0); + // CHECK: store i32 0 + gi = __builtin_object_size((char*)&t[9].t[0] + 10*sizeof(t[0].t), 1); + // CHECK: store i32 0 + gi = __builtin_object_size((char*)&t[9].t[0] + 10*sizeof(t[0].t), 2); + // CHECK: store i32 0 + gi = __builtin_object_size((char*)&t[9].t[0] + 10*sizeof(t[0].t), 3); } -struct Test23Ty { int t[10]; }; +struct Test23Ty { int a; int t[10]; }; // CHECK: @test23 -void test23(struct Test22Ty *p) { +void test23(struct Test23Ty *p) { // CHECK: call i64 @llvm.objectsize.i64.p0i8(i8* %{{.*}}, i1 false) gi = __builtin_object_size(p, 0); // CHECK: call i64 @llvm.objectsize.i64.p0i8(i8* %{{.*}}, i1 false) gi = __builtin_object_size(p, 1); // CHECK: call i64 @llvm.objectsize.i64.p0i8(i8* %{{.*}}, i1 true) gi = __builtin_object_size(p, 2); - // Note: this is currently fixed at 0 because LLVM doesn't have sufficient // data to correctly handle type=3 // CHECK: store i32 0 gi = __builtin_object_size(p, 3); -} + // CHECK: call i64 @llvm.objectsize.i64.p0i8(i8* %{{.*}}, i1 false) + gi = __builtin_object_size(&p->a, 0); + // CHECK: store i32 4 + gi = __builtin_object_size(&p->a, 1); + // CHECK: call i64 @llvm.objectsize.i64.p0i8(i8* %{{.*}}, i1 true) + gi = __builtin_object_size(&p->a, 2); + // CHECK: store i32 4 + gi = __builtin_object_size(&p->a, 3); + + // CHECK: call i64 @llvm.objectsize.i64.p0i8(i8* %{{.*}}, i1 false) + gi = __builtin_object_size(&p->t[5], 0); + // CHECK: store i32 20 + gi = __builtin_object_size(&p->t[5], 1); + // CHECK: call i64 @llvm.objectsize.i64.p0i8(i8* %{{.*}}, i1 true) + gi = __builtin_object_size(&p->t[5], 2); + // CHECK: store i32 20 + gi = __builtin_object_size(&p->t[5], 3); +} // PR24493 -- ICE if __builtin_object_size called with NULL and (Type & 1) != 0 // CHECK @test24 @@ -280,3 +323,72 @@ // CHECK: store i32 0 gi = __builtin_object_size((void*)0 + 0x1000, 3); } + +// CHECK: @test26 +void test26(struct Test23Ty *p) { + struct { int t[10]; } t[10]; + + // CHECK: store i32 356 + gi = __builtin_object_size((char*)&t[1].t[1], 0); + // CHECK: store i32 36 + gi = __builtin_object_size((char*)&t[1].t[1], 1); + // CHECK: store i32 356 + gi = __builtin_object_size((char*)&t[1].t[1], 2); + // CHECK: store i32 36 + gi = __builtin_object_size((char*)&t[1].t[1], 3); +} + +// CHECK: @test27 +void test27() { + struct { int t[10]; } t[10]; + + // CHECK: store i32 359 + gi = __builtin_object_size((char*)&t[1].t[0]+1, 0); + // CHECK: store i32 39 + gi = __builtin_object_size((char*)&t[1].t[0]+1, 1); + // CHECK: store i32 359 + gi = __builtin_object_size((char*)&t[1].t[0]+1, 2); + // CHECK: store i32 39 + gi = __builtin_object_size((char*)&t[1].t[0]+1, 3); +} + +// CHECK: @test28 +void test28() { + struct { int v[10]; } t[10]; + + // CHECK: store i32 356 + gi = __builtin_object_size(&t[0].v[11], 0); + // CHECK: store i32 0 + gi = __builtin_object_size(&t[0].v[12], 1); + // CHECK: store i32 348 + gi = __builtin_object_size(&t[0].v[13], 2); + // CHECK: store i32 0 + gi = __builtin_object_size(&t[0].v[14], 3); +} + +struct Test29IncompleteTy; + +// CHECK: @test29 +void test29(struct Test29IncompleteTy *t) { + // CHECK: call i64 @llvm.objectsize.i64.p0i8(i8* %{{.*}}, i1 false) + gi = __builtin_object_size(t, 0); + // CHECK: call i64 @llvm.objectsize.i64.p0i8(i8* %{{.*}}, i1 false) + gi = __builtin_object_size(t, 1); + // CHECK: call i64 @llvm.objectsize.i64.p0i8(i8* %{{.*}}, i1 true) + gi = __builtin_object_size(t, 2); + // Note: this is currently fixed at 0 because LLVM doesn't have sufficient + // data to correctly handle type=3 + // CHECK: store i32 0 + gi = __builtin_object_size(t, 3); + + // CHECK: call i64 @llvm.objectsize.i64.p0i8(i8* {{.*}}, i1 false) + gi = __builtin_object_size(&test29, 0); + // CHECK: call i64 @llvm.objectsize.i64.p0i8(i8* {{.*}}, i1 false) + gi = __builtin_object_size(&test29, 1); + // CHECK: call i64 @llvm.objectsize.i64.p0i8(i8* {{.*}}, i1 true) + gi = __builtin_object_size(&test29, 2); + // Note: this is currently fixed at 0 because LLVM doesn't have sufficient + // data to correctly handle type=3 + // CHECK: store i32 0 + gi = __builtin_object_size(&test29, 3); +}