llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clangir Author: Andy Kaylor (andykaylor) <details> <summary>Changes</summary> This adds the implementation of aggEmitFinalDestCopy for the case where the destination value is not ignored. This requires adding the cir.copy operation and associated interface code. --- Patch is 26.46 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/155697.diff 15 Files Affected: - (modified) clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h (+5) - (modified) clang/include/clang/CIR/Dialect/IR/CIROps.td (+45) - (modified) clang/include/clang/CIR/MissingFeatures.h (+1) - (modified) clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp (+94-12) - (modified) clang/lib/CIR/CodeGen/CIRGenFunction.h (+10) - (modified) clang/lib/CIR/CodeGen/CIRGenValue.h (+2) - (modified) clang/lib/CIR/Dialect/IR/CIRDialect.cpp (+15) - (modified) clang/lib/CIR/Dialect/IR/CIRMemorySlot.cpp (+38) - (modified) clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp (+12) - (modified) clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h (+9) - (modified) clang/test/CIR/CodeGen/statement-exprs.c (+2-3) - (modified) clang/test/CIR/CodeGen/variable-decomposition.cpp (+11-1) - (modified) clang/test/CIR/CodeGenOpenACC/compute-firstprivate-clause.c (+22-6) - (added) clang/test/CIR/IR/copy.cir (+10) - (added) clang/test/CIR/IR/invalid-copy.cir (+21) ``````````diff diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h index d29e5687d2544..fd77db2680903 100644 --- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h +++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h @@ -247,6 +247,11 @@ class CIRBaseBuilderTy : public mlir::OpBuilder { return createGetGlobal(global.getLoc(), global); } + /// Create a copy with inferred length. + cir::CopyOp createCopy(mlir::Value dst, mlir::Value src) { + return cir::CopyOp::create(*this, dst.getLoc(), dst, src); + } + cir::StoreOp createStore(mlir::Location loc, mlir::Value val, mlir::Value dst, bool isVolatile = false, mlir::IntegerAttr align = {}, diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 2b7a709b80c26..40a297cb7a807 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -2431,6 +2431,51 @@ def CIR_CallOp : CIR_CallOpBase<"call", [NoRegionArguments]> { ]; } +//===----------------------------------------------------------------------===// +// CopyOp +//===----------------------------------------------------------------------===// + +def CIR_CopyOp : CIR_Op<"copy",[ + SameTypeOperands, + DeclareOpInterfaceMethods<PromotableMemOpInterface> +]> { + let arguments = (ins + Arg<CIR_PointerType, "", [MemWrite]>:$dst, + Arg<CIR_PointerType, "", [MemRead]>:$src + ); + let summary = "Copies contents from a CIR pointer to another"; + let description = [{ + Given two CIR pointers, `src` and `dst`, `cir.copy` will copy the memory + pointed by `src` to the memory pointed by `dst`. + + The number of bytes copied is inferred from the pointee type. The pointee + type of `src` and `dst` must match and both must implement the + `DataLayoutTypeInterface`. + + Examples: + + ```mlir + // Copying contents from one record to another: + cir.copy %0 to %1 : !cir.ptr<!record_ty> + ``` + }]; + + let assemblyFormat = [{$src `to` $dst + attr-dict `:` qualified(type($dst)) + }]; + let hasVerifier = 1; + + let extraClassDeclaration = [{ + /// Returns the pointer type being copied. + cir::PointerType getType() { return getSrc().getType(); } + + /// Returns the number of bytes to be copied. + unsigned getLength() { + return mlir::DataLayout::closest(*this).getTypeSize(getType().getPointee()); + } + }]; +} + //===----------------------------------------------------------------------===// // ReturnAddrOp and FrameAddrOp //===----------------------------------------------------------------------===// diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index a8be2a2374d6e..8a49ee4bc9c71 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -168,6 +168,7 @@ struct MissingFeatures { // Misc static bool abiArgInfo() { return false; } static bool addHeapAllocSiteMetadata() { return false; } + static bool aggEmitFinalDestCopyRValue() { return false; } static bool aggValueSlot() { return false; } static bool aggValueSlotAlias() { return false; } static bool aggValueSlotDestructedFlag() { return false; } diff --git a/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp b/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp index 113f9961d2b48..07a2e39dc924c 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp @@ -62,12 +62,19 @@ class AggExprEmitter : public StmtVisitor<AggExprEmitter> { /// Perform the final copy to DestPtr, if desired. void emitFinalDestCopy(QualType type, const LValue &src); + void emitCopy(QualType type, const AggValueSlot &dest, + const AggValueSlot &src); + void emitInitializationToLValue(Expr *e, LValue lv); void emitNullInitializationToLValue(mlir::Location loc, LValue lv); void Visit(Expr *e) { StmtVisitor<AggExprEmitter>::Visit(e); } + void VisitArraySubscriptExpr(ArraySubscriptExpr *e) { + emitAggLoadOfLValue(e); + } + void VisitCallExpr(const CallExpr *e); void VisitStmtExpr(const StmtExpr *e) { CIRGenFunction::StmtExprEvaluation eval(cgf); @@ -91,13 +98,6 @@ class AggExprEmitter : public StmtVisitor<AggExprEmitter> { } // Stubs -- These should be moved up when they are implemented. - void VisitCXXFunctionalCastExpr(CXXFunctionalCastExpr *e) { - // We shouldn't really get here, but we do because of missing handling for - // emitting constant aggregate initializers. If we just ignore this, a - // fallback handler will do the right thing. - assert(!cir::MissingFeatures::constEmitterAggILE()); - return; - } void VisitCastExpr(CastExpr *e) { switch (e->getCastKind()) { case CK_LValueToRValue: @@ -163,10 +163,6 @@ class AggExprEmitter : public StmtVisitor<AggExprEmitter> { cgf.cgm.errorNYI(e->getSourceRange(), "AggExprEmitter: VisitCompoundLiteralExpr"); } - void VisitArraySubscriptExpr(ArraySubscriptExpr *e) { - cgf.cgm.errorNYI(e->getSourceRange(), - "AggExprEmitter: VisitArraySubscriptExpr"); - } void VisitPredefinedExpr(const PredefinedExpr *e) { cgf.cgm.errorNYI(e->getSourceRange(), "AggExprEmitter: VisitPredefinedExpr"); @@ -456,7 +452,31 @@ void AggExprEmitter::emitFinalDestCopy(QualType type, const LValue &src) { if (dest.isIgnored()) return; - cgf.cgm.errorNYI("emitFinalDestCopy: non-ignored dest is NYI"); + assert(!cir::MissingFeatures::aggValueSlotVolatile()); + assert(!cir::MissingFeatures::aggEmitFinalDestCopyRValue()); + assert(!cir::MissingFeatures::aggValueSlotGC()); + + AggValueSlot srcAgg = AggValueSlot::forLValue(src, AggValueSlot::IsDestructed, + AggValueSlot::IsAliased, + AggValueSlot::MayOverlap); + emitCopy(type, dest, srcAgg); +} + +/// Perform a copy from the source into the destination. +/// +/// \param type - the type of the aggregate being copied; qualifiers are +/// ignored +void AggExprEmitter::emitCopy(QualType type, const AggValueSlot &dest, + const AggValueSlot &src) { + assert(!cir::MissingFeatures::aggValueSlotGC()); + + // If the result of the assignment is used, copy the LHS there also. + // It's volatile if either side is. Use the minimum alignment of + // the two sides. + LValue destLV = cgf.makeAddrLValue(dest.getAddress(), type); + LValue srcLV = cgf.makeAddrLValue(src.getAddress(), type); + assert(!cir::MissingFeatures::aggValueSlotVolatile()); + cgf.emitAggregateCopy(destLV, srcLV, type, dest.mayOverlap()); } void AggExprEmitter::emitInitializationToLValue(Expr *e, LValue lv) { @@ -708,6 +728,68 @@ void CIRGenFunction::emitAggExpr(const Expr *e, AggValueSlot slot) { AggExprEmitter(*this, slot).Visit(const_cast<Expr *>(e)); } +void CIRGenFunction::emitAggregateCopy(LValue dest, LValue src, QualType ty, + AggValueSlot::Overlap_t mayOverlap) { + // TODO(cir): this function needs improvements, commented code for now since + // this will be touched again soon. + assert(!ty->isAnyComplexType() && "Unexpected copy of complex"); + + Address destPtr = dest.getAddress(); + Address srcPtr = src.getAddress(); + + if (getLangOpts().CPlusPlus) { + if (auto *record = ty->getAsCXXRecordDecl()) { + assert((record->hasTrivialCopyConstructor() || + record->hasTrivialCopyAssignment() || + record->hasTrivialMoveConstructor() || + record->hasTrivialMoveAssignment() || + record->hasAttr<TrivialABIAttr>() || record->isUnion()) && + "Trying to aggregate-copy a type without a trivial copy/move " + "constructor or assignment operator"); + // Ignore empty classes in C++. + if (record->isEmpty()) + return; + } + } + + assert(!cir::MissingFeatures::cudaSupport()); + + // Aggregate assignment turns into llvm.memcpy. This is almost valid per + // C99 6.5.16.1p3, which states "If the value being stored in an object is + // read from another object that overlaps in anyway the storage of the first + // object, then the overlap shall be exact and the two objects shall have + // qualified or unqualified versions of a compatible type." + // + // memcpy is not defined if the source and destination pointers are exactly + // equal, but other compilers do this optimization, and almost every memcpy + // implementation handles this case safely. If there is a libc that does not + // safely handle this, we can add a target hook. + + // Get data size info for this aggregate. Don't copy the tail padding if this + // might be a potentially-overlapping subobject, since the tail padding might + // be occupied by a different object. Otherwise, copying it is fine. + TypeInfoChars typeInfo; + if (mayOverlap) + typeInfo = getContext().getTypeInfoDataSizeInChars(ty); + else + typeInfo = getContext().getTypeInfoInChars(ty); + + assert(!cir::MissingFeatures::aggValueSlotVolatile()); + + // NOTE(cir): original codegen would normally convert destPtr and srcPtr to + // i8* since memcpy operates on bytes. We don't need that in CIR because + // cir.copy will operate on any CIR pointer that points to a sized type. + + // Don't do any of the memmove_collectable tests if GC isn't set. + if (cgm.getLangOpts().getGC() != LangOptions::NonGC) + cgm.errorNYI("emitAggregateCopy: GC"); + + [[maybe_unused]] cir::CopyOp copyOp = + builder.createCopy(destPtr.getPointer(), srcPtr.getPointer()); + + assert(!cir::MissingFeatures::opTBAA()); +} + LValue CIRGenFunction::emitAggExprToLValue(const Expr *e) { assert(hasAggregateEvaluationKind(e->getType()) && "Invalid argument!"); Address temp = createMemTemp(e->getType(), getLoc(e->getSourceRange())); diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index c799ecdc27538..6802d6ee85c72 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -965,6 +965,16 @@ class CIRGenFunction : public CIRGenTypeCache { LValue emitAggExprToLValue(const Expr *e); + /// Emit an aggregate copy. + /// + /// \param isVolatile \c true iff either the source or the destination is + /// volatile. + /// \param MayOverlap Whether the tail padding of the destination might be + /// occupied by some other object. More efficient code can often be + /// generated if not. + void emitAggregateCopy(LValue dest, LValue src, QualType eltTy, + AggValueSlot::Overlap_t mayOverlap); + /// Emit code to compute the specified expression which can have any type. The /// result is returned as an RValue struct. If this is an aggregate /// expression, the aggloc/agglocvolatile arguments indicate where the result diff --git a/clang/lib/CIR/CodeGen/CIRGenValue.h b/clang/lib/CIR/CodeGen/CIRGenValue.h index ac7e1cc1a1db6..ea8625a0fbee5 100644 --- a/clang/lib/CIR/CodeGen/CIRGenValue.h +++ b/clang/lib/CIR/CodeGen/CIRGenValue.h @@ -379,6 +379,8 @@ class AggValueSlot { mlir::Value getPointer() const { return addr.getPointer(); } + Overlap_t mayOverlap() const { return Overlap_t(overlapFlag); } + IsZeroed_t isZeroed() const { return IsZeroed_t(zeroedFlag); } RValue asRValue() const { diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index 83fff09d4fab3..d4f975234e3b0 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -1919,6 +1919,21 @@ OpFoldResult cir::UnaryOp::fold(FoldAdaptor adaptor) { return {}; } +//===----------------------------------------------------------------------===// +// CopyOp Definitions +//===----------------------------------------------------------------------===// + +LogicalResult cir::CopyOp::verify() { + // A data layout is required for us to know the number of bytes to be copied. + if (!getType().getPointee().hasTrait<DataLayoutTypeInterface::Trait>()) + return emitError() << "missing data layout for pointee type"; + + if (getSrc() == getDst()) + return emitError() << "source and destination are the same"; + + return mlir::success(); +} + //===----------------------------------------------------------------------===// // GetMemberOp Definitions //===----------------------------------------------------------------------===// diff --git a/clang/lib/CIR/Dialect/IR/CIRMemorySlot.cpp b/clang/lib/CIR/Dialect/IR/CIRMemorySlot.cpp index 2550c369a9277..7c341ee589e61 100644 --- a/clang/lib/CIR/Dialect/IR/CIRMemorySlot.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRMemorySlot.cpp @@ -118,6 +118,44 @@ DeletionKind cir::StoreOp::removeBlockingUses( return DeletionKind::Delete; } +//===----------------------------------------------------------------------===// +// Interfaces for CopyOp +//===----------------------------------------------------------------------===// + +bool cir::CopyOp::loadsFrom(const MemorySlot &slot) { + return getSrc() == slot.ptr; +} + +bool cir::CopyOp::storesTo(const MemorySlot &slot) { + return getDst() == slot.ptr; +} + +Value cir::CopyOp::getStored(const MemorySlot &slot, OpBuilder &builder, + Value reachingDef, const DataLayout &dataLayout) { + return cir::LoadOp::create(builder, getLoc(), slot.elemType, getSrc()); +} + +DeletionKind cir::CopyOp::removeBlockingUses( + const MemorySlot &slot, const SmallPtrSetImpl<OpOperand *> &blockingUses, + OpBuilder &builder, mlir::Value reachingDefinition, + const DataLayout &dataLayout) { + if (loadsFrom(slot)) + cir::StoreOp::create(builder, getLoc(), reachingDefinition, getDst(), + /*alignment=*/mlir::IntegerAttr{}, + /*mem-order=*/cir::MemOrderAttr()); + return DeletionKind::Delete; +} + +bool cir::CopyOp::canUsesBeRemoved( + const MemorySlot &slot, const SmallPtrSetImpl<OpOperand *> &blockingUses, + SmallVectorImpl<OpOperand *> &newBlockingUses, + const DataLayout &dataLayout) { + if (getDst() == getSrc()) + return false; + + return getLength() == dataLayout.getTypeSize(slot.elemType); +} + //===----------------------------------------------------------------------===// // Interfaces for CastOp //===----------------------------------------------------------------------===// diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index 03955dc737828..efe249c2bbca3 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -173,6 +173,17 @@ mlir::LLVM::Linkage convertLinkage(cir::GlobalLinkageKind linkage) { llvm_unreachable("Unknown CIR linkage type"); } +mlir::LogicalResult CIRToLLVMCopyOpLowering::matchAndRewrite( + cir::CopyOp op, OpAdaptor adaptor, + mlir::ConversionPatternRewriter &rewriter) const { + const mlir::Value length = mlir::LLVM::ConstantOp::create( + rewriter, op.getLoc(), rewriter.getI32Type(), op.getLength()); + assert(!cir::MissingFeatures::aggValueSlotVolatile()); + rewriter.replaceOpWithNewOp<mlir::LLVM::MemcpyOp>( + op, adaptor.getDst(), adaptor.getSrc(), length, /*isVolatile=*/false); + return mlir::success(); +} + static mlir::Value getLLVMIntCast(mlir::ConversionPatternRewriter &rewriter, mlir::Value llvmSrc, mlir::Type llvmDstIntTy, bool isUnsigned, uint64_t cirSrcWidth, @@ -2419,6 +2430,7 @@ void ConvertCIRToLLVMPass::runOnOperation() { CIRToLLVMComplexRealOpLowering, CIRToLLVMComplexRealPtrOpLowering, CIRToLLVMComplexSubOpLowering, + CIRToLLVMCopyOpLowering, CIRToLLVMConstantOpLowering, CIRToLLVMExpectOpLowering, CIRToLLVMFAbsOpLowering, diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h index 513ad37839f1b..c1228c1961c7e 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h @@ -170,6 +170,15 @@ class CIRToLLVMCastOpLowering : public mlir::OpConversionPattern<cir::CastOp> { mlir::ConversionPatternRewriter &) const override; }; +class CIRToLLVMCopyOpLowering : public mlir::OpConversionPattern<cir::CopyOp> { +public: + using mlir::OpConversionPattern<cir::CopyOp>::OpConversionPattern; + + mlir::LogicalResult + matchAndRewrite(cir::CopyOp op, OpAdaptor, + mlir::ConversionPatternRewriter &) const override; +}; + class CIRToLLVMExpectOpLowering : public mlir::OpConversionPattern<cir::ExpectOp> { public: diff --git a/clang/test/CIR/CodeGen/statement-exprs.c b/clang/test/CIR/CodeGen/statement-exprs.c index 1b54edfe7ec30..f6ec9ecd1b67e 100644 --- a/clang/test/CIR/CodeGen/statement-exprs.c +++ b/clang/test/CIR/CodeGen/statement-exprs.c @@ -5,9 +5,6 @@ // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll // RUN: FileCheck --input-file=%t.ll %s --check-prefix=OGCG -// This fails because of a non-ignored copy of an aggregate in test3. -// XFAIL: * - int f19(void) { return ({ 3;;4;; }); } @@ -229,6 +226,7 @@ int test3() { return ({ struct S s = {1}; s; }).x; } // CIR: %[[GEP_X_S:.+]] = cir.get_member %[[S]][0] {name = "x"} : !cir.ptr<!rec_S> -> !cir.ptr<!s32i> // CIR: %[[C1:.+]] = cir.const #cir.int<1> : !s32i // CIR: cir.store {{.*}} %[[C1]], %[[GEP_X_S]] : !s32i, !cir.ptr<!s32i> +// CIR: cir.copy %[[S]] to %[[REF_TMP0]] : !cir.ptr<!rec_S> // CIR: } // CIR: %[[GEP_X_TMP:.+]] = cir.get_member %[[REF_TMP0]][0] {name = "x"} : !cir.ptr<!rec_S> -> !cir.ptr<!s32i> // CIR: %[[XVAL:.+]] = cir.load {{.*}} %[[GEP_X_TMP]] : !cir.ptr<!s32i>, !s32i @@ -249,6 +247,7 @@ int test3() { return ({ struct S s = {1}; s; }).x; } // LLVM: [[LBL6]]: // LLVM: %[[GEP_S:.+]] = getelementptr %struct.S, ptr %[[VAR3]], i32 0, i32 0 // LLVM: store i32 1, ptr %[[GEP_S]] +// LLVM: call void @llvm.memcpy.p0.p0.i32(ptr %[[VAR1]], ptr %[[VAR3]], i32 4, i1 false) // LLVM: br label %[[LBL8:.+]] // LLVM: [[LBL8]]: // LLVM: %[[GEP_VAR1:.+]] = getelementptr %struct.S, ptr %[[VAR1]], i32 0, i32 0 diff --git a/clang/test/CIR/CodeGen/variable-decomposition.cpp b/clang/test/CIR/CodeGen/variable-decomposition.cpp index 022d06a97e369..40dfe73c411c9 100644 --- a/clang/test/CIR/CodeGen/variable-decomposition.cpp +++ b/clang/test/CIR/CodeGen/variable-decomposition.cpp @@ -18,7 +18,13 @@ float function() { // CIR-LABEL: cir.func dso_local @_Z8functionv() -> !cir.float // CIR: %[[RETVAL:.+]] = cir.alloca !cir.float, !cir.ptr<!cir.float>, ["__retval"] -// CIR: %[[STRUCT:.+]] = cir.alloca !rec_some_struct, !cir.ptr<!rec_some_struct>, [""] +// CIR: %[[STRUCT:.+]] = cir.alloca !rec_some_struct, !cir.ptr<!rec_some_struct>, ["", init] +// CIR: %[[MEMBER_A:.+]] = cir.get_member %[[STRUCT]][0] {name = "a"} : !cir.ptr<!rec_some_struct> -> !cir.ptr<!s32i> +// CIR: %[[CONST_1:.+]] = cir.const #cir.int<1> : !s32i +// CIR: cir.store{{.*}} %[[CONST_1]], %[[MEMBER_A]] +// CIR: %[[MEMBER_B:.+]] = cir.get_member %[[STRUCT]][1] {name = "b"} : !cir.ptr<!rec_some_struct> -> !cir.ptr<!cir.float> +// CIR: %[[TWO_FP:.+]] = cir.const #cir.fp<2.000000e+00> : !cir.float +// CIR: cir.store{{.*}} %[[TWO_FP]], %[[MEMBER_B]] // CIR: %[[MEMBER_A:.+]] = cir.get_member %[[STRUCT]][0] {name = "a"} : !cir.ptr<!rec_some_struct> -> !cir.ptr<!s32i> // CIR: %[[LOAD_A:.+]] = cir.load align(4) %[[MEMBER_A]] : !cir.ptr<!s32i>, !s32i // CIR: %[[CAST_A:.+]] = cir.cast(int_to_float, %[[LOAD_A]] : !s32i), !cir.float @@ -33,6 +39,10 @@ float function() { // LLVM: %[[RETVAL:.+]] = alloca float, i64 1 // LLVM: %[[STRUCT:.+]] = alloca %struct.some_struct, i64 1 // LLVM: %[[GEP_A:.+]] = getelementptr %struct.some_struct, ptr %[[STRUCT]], i32 0, i32 0 +// LLVM: store i32 1, ptr %[[GEP_A]] +// LLVM: %[[GEP_B:.+]] = getelementptr %struct.some_struct, ptr %[[STRUCT]], i32 0, i32 1 +// LLVM: ... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/155697 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits