From: John McCall Date: Tue, 8 Feb 2011 03:07:00 +0000 (+0000) Subject: Extend the const capture optimization to C++ record types with no X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=461c9c1bc39ed8cbe8311f396f7ee3839e9fda53;p=clang Extend the const capture optimization to C++ record types with no mutable fields and with trivial destructors and copy constructors. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@125073 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/lib/CodeGen/CGBlocks.cpp b/lib/CodeGen/CGBlocks.cpp index 1a7abd242a..e78e175a5a 100644 --- a/lib/CodeGen/CGBlocks.cpp +++ b/lib/CodeGen/CGBlocks.cpp @@ -201,6 +201,42 @@ namespace { } } +/// Determines if the given record type has a mutable field. +static bool hasMutableField(const CXXRecordDecl *record) { + for (CXXRecordDecl::field_iterator + i = record->field_begin(), e = record->field_end(); i != e; ++i) + if ((*i)->isMutable()) + return true; + + for (CXXRecordDecl::base_class_const_iterator + i = record->bases_begin(), e = record->bases_end(); i != e; ++i) { + const RecordType *record = i->getType()->castAs(); + if (hasMutableField(cast(record->getDecl()))) + return true; + } + + return false; +} + +/// Determines if the given type is safe for constant capture in C++. +static bool isSafeForCXXConstantCapture(QualType type) { + const RecordType *recordType = + type->getBaseElementTypeUnsafe()->getAs(); + + // Only records can be unsafe. + if (!recordType) return true; + + const CXXRecordDecl *record = cast(recordType->getDecl()); + + // Maintain semantics for classes with non-trivial dtors or copy ctors. + if (!record->hasTrivialDestructor()) return false; + if (!record->hasTrivialCopyConstructor()) return false; + + // Otherwise, we just have to make sure there aren't any mutable + // fields that might have changed since initialization. + return !hasMutableField(record); +} + /// It is illegal to modify a const object after initialization. /// Therefore, if a const object has a constant initializer, we don't /// actually need to keep storage for it in the block; we'll just @@ -214,11 +250,12 @@ static llvm::Constant *tryCaptureAsConstant(CodeGenModule &CGM, // We can only do this if the variable is const. if (!type.isConstQualified()) return 0; - // Furthermore, in C++ we can't do this for classes. TODO: we might - // actually be able to get away with it for classes with a trivial - // destructor and a trivial copy constructor and no mutable fields. - if (CGM.getLangOptions().CPlusPlus && - type->getBaseElementTypeUnsafe()->isRecordType()) + // Furthermore, in C++ we have to worry about mutable fields: + // C++ [dcl.type.cv]p4: + // Except that any class member declared mutable can be + // modified, any attempt to modify a const object during its + // lifetime results in undefined behavior. + if (CGM.getLangOptions().CPlusPlus && !isSafeForCXXConstantCapture(type)) return 0; // If the variable doesn't have any initializer (shouldn't this be diff --git a/test/CodeGenCXX/blocks.cpp b/test/CodeGenCXX/blocks.cpp index 568f9b1e4b..ea174b57e4 100644 --- a/test/CodeGenCXX/blocks.cpp +++ b/test/CodeGenCXX/blocks.cpp @@ -8,3 +8,50 @@ namespace test0 { ^{ ^{ (void) x; }; }; } } + +extern void (^out)(); + +namespace test1 { + // Capturing const objects doesn't require a local block. + // CHECK: define void @_ZN5test15test1Ev() + // CHECK: store void ()* bitcast ({{.*}} @__block_literal_global{{.*}} to void ()*), void ()** @out + void test1() { + const int NumHorsemen = 4; + out = ^{ (void) NumHorsemen; }; + } + + // That applies to structs too... + // CHECK: define void @_ZN5test15test2Ev() + // CHECK: store void ()* bitcast ({{.*}} @__block_literal_global{{.*}} to void ()*), void ()** @out + struct loc { double x, y; }; + void test2() { + const loc target = { 5, 6 }; + out = ^{ (void) target; }; + } + + // ...unless they have mutable fields... + // CHECK: define void @_ZN5test15test3Ev() + // CHECK: [[BLOCK:%.*]] = alloca [[BLOCK_T:%.*]], + // CHECK: [[T0:%.*]] = bitcast [[BLOCK_T]]* [[BLOCK]] to void ()* + // CHECK: store void ()* [[T0]], void ()** @out + struct mut { mutable int x; }; + void test3() { + const mut obj = { 5 }; + out = ^{ (void) obj; }; + } + + // ...or non-trivial destructors... + // CHECK: define void @_ZN5test15test4Ev() + // CHECK: [[OBJ:%.*]] = alloca + // CHECK: [[BLOCK:%.*]] = alloca [[BLOCK_T:%.*]], + // CHECK: [[T0:%.*]] = bitcast [[BLOCK_T]]* [[BLOCK]] to void ()* + // CHECK: store void ()* [[T0]], void ()** @out + struct scope { int x; ~scope(); }; + void test4() { + const scope obj = { 5 }; + out = ^{ (void) obj; }; + } + + // ...or non-trivial copy constructors, but it's not clear how to do + // that and still have a constant initializer in '03. +}