llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang @llvm/pr-subscribers-clangir Author: Amr Hesham (AmrDeveloper) <details> <summary>Changes</summary> Upstream, the basic support for the C++ try catch statement with an empty try block and with a catch-all statement Issue #<!-- -->154992 --- Patch is 26.96 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/162528.diff 12 Files Affected: - (modified) clang/include/clang/CIR/Dialect/IR/CIROps.td (+62-2) - (modified) clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp (+4) - (modified) clang/lib/CIR/CodeGen/CIRGenCXXABI.h (+3) - (modified) clang/lib/CIR/CodeGen/CIRGenCleanup.cpp (+7) - (modified) clang/lib/CIR/CodeGen/CIRGenCleanup.h (+92-2) - (modified) clang/lib/CIR/CodeGen/CIRGenException.cpp (+155) - (modified) clang/lib/CIR/CodeGen/CIRGenFunction.h (+21) - (modified) clang/lib/CIR/CodeGen/CIRGenStmt.cpp (+2-1) - (modified) clang/lib/CIR/CodeGen/EHScopeStack.h (+8) - (modified) clang/lib/CIR/Dialect/IR/CIRDialect.cpp (+133) - (modified) clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp (+30-2) - (added) clang/test/CIR/CodeGen/try-catch.cpp (+29) ``````````diff diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 66c4c04f23108..d472445362e6f 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -537,7 +537,7 @@ def CIR_StoreOp : CIR_Op<"store", [ defvar CIR_ReturnableScopes = [ "FuncOp", "ScopeOp", "IfOp", "SwitchOp", "CaseOp", - "DoWhileOp", "WhileOp", "ForOp" + "DoWhileOp", "WhileOp", "ForOp", "TryOp" ]; def CIR_ReturnOp : CIR_Op<"return", [ @@ -684,7 +684,7 @@ def CIR_ConditionOp : CIR_Op<"condition", [ defvar CIR_YieldableScopes = [ "ArrayCtor", "ArrayDtor", "CaseOp", "DoWhileOp", "ForOp", "GlobalOp", "IfOp", - "ScopeOp", "SwitchOp", "TernaryOp", "WhileOp" + "ScopeOp", "SwitchOp", "TernaryOp", "WhileOp", "TryOp" ]; def CIR_YieldOp : CIR_Op<"yield", [ @@ -4202,6 +4202,66 @@ def CIR_AllocExceptionOp : CIR_Op<"alloc.exception"> { }]; } +//===----------------------------------------------------------------------===// +// TryOp +//===----------------------------------------------------------------------===// + +// Represents the unwind region where unwind continues or +// the program std::terminate's. +def CIR_CatchUnwind : CIR_UnitAttr<"CatchUnwind", "unwind"> { + let storageType = [{ CatchUnwind }]; +} + +// Represents the catch_all region. +def CIR_CatchAll : CIR_UnitAttr<"CatchAll", "all"> { + let storageType = [{ CatchAllAttr }]; +} + +def CIR_TryOp : CIR_Op<"try",[ + DeclareOpInterfaceMethods<RegionBranchOpInterface>, + RecursivelySpeculatable, AutomaticAllocationScope, NoRegionArguments +]> { + let summary = "C++ try block"; + let description = [{ + ```mlir + + Holds the lexical scope of `try {}`. Note that resources used on catch + clauses are usually allocated in the same parent as `cir.try`. + + `synthetic`: use `cir.try` to represent try/catches not originally + present in the source code (e.g. `g = new Class` under `-fexceptions`). + + `cleanup`: signal to targets (LLVM for now) that this try/catch, needs + to specially tag their landing pads as needing "cleanup". + + Example: TBD + ``` + }]; + + let arguments = (ins UnitAttr:$synthetic, UnitAttr:$cleanup, + OptionalAttr<ArrayAttr>:$catch_types); + let regions = (region AnyRegion:$try_region, + VariadicRegion<AnyRegion>:$catch_regions); + + let assemblyFormat = [{ + (`synthetic` $synthetic^)? + (`cleanup` $cleanup^)? + $try_region + custom<CatchRegions>($catch_regions, $catch_types) + attr-dict + }]; + + // Everything already covered elsewhere. + let builders = [ + OpBuilder<(ins + "llvm::function_ref<void(mlir::OpBuilder &, mlir::Location)>":$tryBuilder, + "llvm::function_ref<void(mlir::OpBuilder &, mlir::Location, mlir::OperationState &)>" + :$catchBuilder)>, + ]; + + let hasLLVMLowering = false; +} + //===----------------------------------------------------------------------===// // Atomic operations //===----------------------------------------------------------------------===// diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp index 5f1faabde22a5..9c5ea54c3adb6 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp @@ -36,6 +36,10 @@ CIRGenCXXABI::AddedStructorArgCounts CIRGenCXXABI::addImplicitConstructorArgs( addedArgs.suffix.size()); } +CatchTypeInfo CIRGenCXXABI::getCatchAllTypeInfo() { + return CatchTypeInfo{nullptr, 0}; +} + void CIRGenCXXABI::buildThisParam(CIRGenFunction &cgf, FunctionArgList ¶ms) { const auto *md = cast<CXXMethodDecl>(cgf.curGD.getDecl()); diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h index be66240c280ec..fc16e15fd6950 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h +++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h @@ -15,6 +15,7 @@ #define LLVM_CLANG_LIB_CIR_CIRGENCXXABI_H #include "CIRGenCall.h" +#include "CIRGenCleanup.h" #include "CIRGenFunction.h" #include "CIRGenModule.h" @@ -147,6 +148,8 @@ class CIRGenCXXABI { /// Loads the incoming C++ this pointer as it was passed by the caller. mlir::Value loadIncomingCXXThis(CIRGenFunction &cgf); + virtual CatchTypeInfo getCatchAllTypeInfo(); + /// Emit constructor variants required by this ABI. virtual void emitCXXConstructors(const clang::CXXConstructorDecl *d) = 0; diff --git a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp index 870069715df22..aabe4bbdf18c8 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp @@ -108,6 +108,13 @@ void EHScopeStack::popCleanup() { assert(!cir::MissingFeatures::ehCleanupBranchFixups()); } +EHCatchScope *EHScopeStack::pushCatch(unsigned numHandlers) { + char *buffer = allocate(EHCatchScope::getSizeForNumHandlers(numHandlers)); + assert(!cir::MissingFeatures::innermostEHScope()); + EHCatchScope *scope = new (buffer) EHCatchScope(numHandlers); + return scope; +} + static void emitCleanup(CIRGenFunction &cgf, EHScopeStack::Cleanup *cleanup) { // Ask the cleanup to emit itself. assert(cgf.haveInsertPoint() && "expected insertion point"); diff --git a/clang/lib/CIR/CodeGen/CIRGenCleanup.h b/clang/lib/CIR/CodeGen/CIRGenCleanup.h index 30f5607d655da..5cce1cad57f44 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCleanup.h +++ b/clang/lib/CIR/CodeGen/CIRGenCleanup.h @@ -14,14 +14,22 @@ #ifndef CLANG_LIB_CIR_CODEGEN_CIRGENCLEANUP_H #define CLANG_LIB_CIR_CODEGEN_CIRGENCLEANUP_H -#include "Address.h" #include "EHScopeStack.h" -#include "mlir/IR/Value.h" +#include "mlir/IR/BuiltinAttributeInterfaces.h" +#include "clang/CIR/MissingFeatures.h" namespace clang::CIRGen { +/// The MS C++ ABI needs a pointer to RTTI data plus some flags to describe the +/// type of a catch handler, so we use this wrapper. +struct CatchTypeInfo { + mlir::TypedAttr rtti; + unsigned flags; +}; + /// A protected scope for zero-cost EH handling. class EHScope { + class CommonBitFields { friend class EHScope; unsigned kind : 3; @@ -29,6 +37,13 @@ class EHScope { enum { NumCommonBits = 3 }; protected: + class CatchBitFields { + friend class EHCatchScope; + unsigned : NumCommonBits; + + unsigned numHandlers : 32 - NumCommonBits; + }; + class CleanupBitFields { friend class EHCleanupScope; unsigned : NumCommonBits; @@ -58,6 +73,7 @@ class EHScope { union { CommonBitFields commonBits; + CatchBitFields catchBits; CleanupBitFields cleanupBits; }; @@ -67,6 +83,72 @@ class EHScope { EHScope(Kind kind) { commonBits.kind = kind; } Kind getKind() const { return static_cast<Kind>(commonBits.kind); } + + bool hasEHBranches() const { + // Traditional LLVM codegen also checks for `!block->use_empty()`, but + // in CIRGen the block content is not important, just used as a way to + // signal `hasEHBranches`. + assert(!cir::MissingFeatures::ehstackBranches()); + return false; + } +}; + +/// A scope which attempts to handle some, possibly all, types of +/// exceptions. +/// +/// Objective C \@finally blocks are represented using a cleanup scope +/// after the catch scope. + +class EHCatchScope : public EHScope { + // In effect, we have a flexible array member + // Handler Handlers[0]; + // But that's only standard in C99, not C++, so we have to do + // annoying pointer arithmetic instead. + +public: + struct Handler { + /// A type info value, or null (C++ null, not an LLVM null pointer) + /// for a catch-all. + CatchTypeInfo type; + + /// The catch handler for this type. + mlir::Block *block; + }; + +private: + friend class EHScopeStack; + + Handler *getHandlers() { return reinterpret_cast<Handler *>(this + 1); } + +public: + static size_t getSizeForNumHandlers(unsigned n) { + return sizeof(EHCatchScope) + n * sizeof(Handler); + } + + EHCatchScope(unsigned numHandlers) : EHScope(Catch) { + catchBits.numHandlers = numHandlers; + assert(catchBits.numHandlers == numHandlers && "NumHandlers overflow?"); + } + + unsigned getNumHandlers() const { return catchBits.numHandlers; } + + void setHandler(unsigned i, CatchTypeInfo type, mlir::Block *block) { + assert(i < getNumHandlers()); + getHandlers()[i].type = type; + getHandlers()[i].block = block; + } + + // Clear all handler blocks. + // FIXME: it's better to always call clearHandlerBlocks in DTOR and have a + // 'takeHandler' or some such function which removes ownership from the + // EHCatchScope object if the handlers should live longer than EHCatchScope. + void clearHandlerBlocks() { + // The blocks are owned by TryOp, nothing to delete. + } + + static bool classof(const EHScope *scope) { + return scope->getKind() == Catch; + } }; /// A cleanup scope which generates the cleanup blocks lazily. @@ -147,5 +229,13 @@ EHScopeStack::find(stable_iterator savePoint) const { return iterator(endOfBuffer - savePoint.size); } +inline void EHScopeStack::popCatch() { + assert(!empty() && "popping exception stack when not empty"); + + EHCatchScope &scope = llvm::cast<EHCatchScope>(*begin()); + assert(!cir::MissingFeatures::innermostEHScope()); + deallocate(EHCatchScope::getSizeForNumHandlers(scope.getNumHandlers())); +} + } // namespace clang::CIRGen #endif // CLANG_LIB_CIR_CODEGEN_CIRGENCLEANUP_H diff --git a/clang/lib/CIR/CodeGen/CIRGenException.cpp b/clang/lib/CIR/CodeGen/CIRGenException.cpp index 645384383711b..975e031ed5cd2 100644 --- a/clang/lib/CIR/CodeGen/CIRGenException.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenException.cpp @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// #include "CIRGenCXXABI.h" +#include "CIRGenCleanup.h" #include "CIRGenFunction.h" #include "clang/AST/StmtVisitor.h" @@ -64,3 +65,157 @@ void CIRGenFunction::emitAnyExprToExn(const Expr *e, Address addr) { // Deactivate the cleanup block. assert(!cir::MissingFeatures::ehCleanupScope()); } + +mlir::LogicalResult CIRGenFunction::emitCXXTryStmt(const CXXTryStmt &s) { + auto loc = getLoc(s.getSourceRange()); + + // Create a scope to hold try local storage for catch params. + mlir::OpBuilder::InsertPoint scopeIP; + cir::ScopeOp::create(builder, loc, /*scopeBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + scopeIP = builder.saveInsertionPoint(); + }); + + mlir::LogicalResult result = mlir::success(); + { + mlir::OpBuilder::InsertionGuard guard(builder); + builder.restoreInsertionPoint(scopeIP); + result = emitCXXTryStmtUnderScope(s); + cir::YieldOp::create(builder, loc); + } + + return result; +} + +mlir::LogicalResult +CIRGenFunction::emitCXXTryStmtUnderScope(const CXXTryStmt &s) { + const llvm::Triple &t = getTarget().getTriple(); + // If we encounter a try statement on in an OpenMP target region offloaded to + // a GPU, we treat it as a basic block. + const bool isTargetDevice = + (cgm.getLangOpts().OpenMPIsTargetDevice && (t.isNVPTX() || t.isAMDGCN())); + if (isTargetDevice) { + cgm.errorNYI( + "emitCXXTryStmtUnderScope: OpenMP target region offloaded to GPU"); + return mlir::success(); + } + + auto hasCatchAll = [&]() { + if (!s.getNumHandlers()) + return false; + unsigned lastHandler = s.getNumHandlers() - 1; + return s.getHandler(lastHandler)->getExceptionDecl() == nullptr; + }; + + unsigned numHandlers = s.getNumHandlers(); + mlir::Location tryLoc = getLoc(s.getBeginLoc()); + + mlir::OpBuilder::InsertPoint beginInsertTryBody; + + // Create the scope to represent only the C/C++ `try {}` part. However, + // don't populate right away. Reserve some space to store the exception + // info but don't emit the bulk right away, for now only make sure the + // scope returns the exception information. + auto tryOp = cir::TryOp::create( + builder, tryLoc, + /*tryBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + beginInsertTryBody = builder.saveInsertionPoint(); + }, + /*catchBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc, + mlir::OperationState &result) { + mlir::OpBuilder::InsertionGuard guard(b); + unsigned numRegionsToCreate = + hasCatchAll() ? numHandlers : numHandlers + 1; + for (unsigned i = 0; i != numRegionsToCreate; ++i) { + builder.createBlock(result.addRegion()); + } + }); + + // Finally emit the body for try/catch. + auto emitTryCatchBody = [&]() -> mlir::LogicalResult { + mlir::Location loc = tryOp.getLoc(); + mlir::OpBuilder::InsertionGuard guard(builder); + builder.restoreInsertionPoint(beginInsertTryBody); + CIRGenFunction::LexicalScope tryScope{*this, loc, + builder.getInsertionBlock()}; + + tryScope.setAsTry(tryOp); + + // Attach the basic blocks for the catch regions. + enterCXXTryStmt(s, tryOp); + + // Emit the body for the `try {}` part. + { + mlir::OpBuilder::InsertionGuard guard(builder); + CIRGenFunction::LexicalScope tryBodyScope{*this, loc, + builder.getInsertionBlock()}; + if (emitStmt(s.getTryBlock(), /*useCurrentScope=*/true).failed()) + return mlir::failure(); + } + + // Emit catch clauses. + exitCXXTryStmt(s); + return mlir::success(); + }; + + return emitTryCatchBody(); +} + +void CIRGenFunction::enterCXXTryStmt(const CXXTryStmt &s, cir::TryOp tryOp, + bool isFnTryBlock) { + + unsigned numHandlers = s.getNumHandlers(); + EHCatchScope *catchScope = ehStack.pushCatch(numHandlers); + for (unsigned i = 0; i != numHandlers; ++i) { + const CXXCatchStmt *catchStmt = s.getHandler(i); + if (catchStmt->getExceptionDecl()) { + cgm.errorNYI("enterCXXTryStmt: CatchStmt with ExceptionDecl"); + return; + } + + mlir::Block *handler = &tryOp.getCatchRegions()[i].getBlocks().front(); + + // No exception decl indicates '...', a catch-all. + catchScope->setHandler(i, cgm.getCXXABI().getCatchAllTypeInfo(), handler); + + // Under async exceptions, catch(...) need to catch HW exception too + // Mark scope with SehTryBegin as a SEH __try scope + if (getLangOpts().EHAsynch) { + cgm.errorNYI("enterCXXTryStmt: EHAsynch"); + return; + } + } +} + +void CIRGenFunction::exitCXXTryStmt(const CXXTryStmt &s, bool isFnTryBlock) { + unsigned numHandlers = s.getNumHandlers(); + EHCatchScope &catchScope = cast<EHCatchScope>(*ehStack.begin()); + assert(catchScope.getNumHandlers() == numHandlers); + cir::TryOp tryOp = curLexScope->getTry(); + + // If the catch was not required, bail out now. + if (!catchScope.hasEHBranches()) { + catchScope.clearHandlerBlocks(); + ehStack.popCatch(); + + // Drop all basic block from all catch regions. + SmallVector<mlir::Block *> eraseBlocks; + for (mlir::Region ®ion : tryOp.getCatchRegions()) { + if (region.empty()) + continue; + + for (mlir::Block &b : region.getBlocks()) + eraseBlocks.push_back(&b); + } + + for (mlir::Block *b : eraseBlocks) + b->erase(); + + tryOp.setCatchTypesAttr({}); + return; + } + + cgm.errorNYI("exitCXXTryStmt: Required catch"); +} diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index d10d058ef289a..c0adab3abb4fd 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -883,6 +883,9 @@ class CIRGenFunction : public CIRGenTypeCache { LexicalScope *parentScope = nullptr; + // Holds actual value for ScopeKind::Try + cir::TryOp tryOp = nullptr; + // Only Regular is used at the moment. Support for other kinds will be // added as the relevant statements/expressions are upstreamed. enum Kind { @@ -942,6 +945,10 @@ class CIRGenFunction : public CIRGenTypeCache { void setAsGlobalInit() { scopeKind = Kind::GlobalInit; } void setAsSwitch() { scopeKind = Kind::Switch; } void setAsTernary() { scopeKind = Kind::Ternary; } + void setAsTry(cir::TryOp op) { + scopeKind = Kind::Try; + tryOp = op; + } // Lazy create cleanup block or return what's available. mlir::Block *getOrCreateCleanupBlock(mlir::OpBuilder &builder) { @@ -964,6 +971,11 @@ class CIRGenFunction : public CIRGenTypeCache { return cleanupBlock; } + cir::TryOp getTry() { + assert(isTry()); + return tryOp; + } + // --- // Return handling. // --- @@ -1265,6 +1277,15 @@ class CIRGenFunction : public CIRGenTypeCache { void emitCXXThrowExpr(const CXXThrowExpr *e); + mlir::LogicalResult emitCXXTryStmt(const clang::CXXTryStmt &s); + + mlir::LogicalResult emitCXXTryStmtUnderScope(const clang::CXXTryStmt &s); + + void enterCXXTryStmt(const CXXTryStmt &s, cir::TryOp tryOp, + bool isFnTryBlock = false); + + void exitCXXTryStmt(const CXXTryStmt &s, bool isFnTryBlock = false); + void emitCtorPrologue(const clang::CXXConstructorDecl *ctor, clang::CXXCtorType ctorType, FunctionArgList &args); diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp index 0b8f8bfdfb046..cfd48a227ed20 100644 --- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp @@ -154,6 +154,8 @@ mlir::LogicalResult CIRGenFunction::emitStmt(const Stmt *s, return emitWhileStmt(cast<WhileStmt>(*s)); case Stmt::DoStmtClass: return emitDoStmt(cast<DoStmt>(*s)); + case Stmt::CXXTryStmtClass: + return emitCXXTryStmt(cast<CXXTryStmt>(*s)); case Stmt::CXXForRangeStmtClass: return emitCXXForRangeStmt(cast<CXXForRangeStmt>(*s), attr); case Stmt::OpenACCComputeConstructClass: @@ -199,7 +201,6 @@ mlir::LogicalResult CIRGenFunction::emitStmt(const Stmt *s, case Stmt::CoroutineBodyStmtClass: return emitCoroutineBody(cast<CoroutineBodyStmt>(*s)); case Stmt::CoreturnStmtClass: - case Stmt::CXXTryStmtClass: case Stmt::IndirectGotoStmtClass: case Stmt::OMPParallelDirectiveClass: case Stmt::OMPTaskwaitDirectiveClass: diff --git a/clang/lib/CIR/CodeGen/EHScopeStack.h b/clang/lib/CIR/CodeGen/EHScopeStack.h index 66c1f76094c58..1dce5ac50b09d 100644 --- a/clang/lib/CIR/CodeGen/EHScopeStack.h +++ b/clang/lib/CIR/CodeGen/EHScopeStack.h @@ -158,6 +158,14 @@ class EHScopeStack { /// Pops a cleanup scope off the stack. This is private to CIRGenCleanup.cpp. void popCleanup(); + /// Push a set of catch handlers on the stack. The catch is + /// uninitialized and will need to have the given number of handlers + /// set on it. + class EHCatchScope *pushCatch(unsigned numHandlers); + + /// Pops a catch scope off the stack. This is private to CIRGenException.cpp. + void popCatch(); + /// Determines whether the exception-scopes stack is empty. bool empty() const { return startOfData == endOfBuffer; } diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index cdd4e3c96ca98..c6c142255e16b 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -1083,6 +1083,139 @@ LogicalResult cir::ScopeOp::verify() { return success(); } +//===----------------------------------------------------------------------===// +// TryOp +//===----------------------------------------------------------------------===// + +void cir::TryOp::build( + OpBuilder &builder, OperationState &result, + function_ref<void(OpBuilder &, Location)> tryBodyBuilder, + function_ref<void(OpBuilder &, Location, OperationState &)> catchBuilder) { + assert(tryBodyBuilder && "expected builder callback for 'cir.try' body"); + + OpBuilder::InsertionGuard guard(builder); + + // Try body region + Region *tryBodyRegion = result.addRegion(); + + // Create try body region and set insertion point + builder.createBlock(tryBodyRegion); + tryBodyBuilder(builder, result.location); + catchBuilder(builder, result.location, result); +} + +void cir::TryOp::getSuccessorRegions( + mlir::RegionBranchPoint point, SmallVectorImpl<RegionSuccessor> ®ions) { + // If any i... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/162528 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
