From 99507ad48e5214222f4b68569bdc3091c2c55f07 Mon Sep 17 00:00:00 2001 From: Andy Kaylor Date: Wed, 12 Nov 2025 15:39:55 -0800 Subject: [PATCH 1/2] [CIR] Upstream lvalue emission for ExprWithCleanups This adds the necessary handler for emitting an l-value for an ExprWithCleanups expression. --- clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 29 ++++++ .../CIR/CodeGen/temporary-materialization.cpp | 98 +++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 clang/test/CIR/CodeGen/temporary-materialization.cpp diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index 885a32cf16862..9baa1fc1dc006 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -921,6 +921,35 @@ LValue CIRGenFunction::emitLValue(const Expr *e) { case Expr::CXXOperatorCallExprClass: case Expr::UserDefinedLiteralClass: return emitCallExprLValue(cast(e)); + case Expr::ExprWithCleanupsClass: { + const auto *cleanups = cast(e); + LValue lv; + + mlir::Location scopeLoc = getLoc(e->getSourceRange()); + [[maybe_unused]] auto scope = cir::ScopeOp::create( + builder, scopeLoc, /*scopeBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + CIRGenFunction::LexicalScope lexScope{*this, loc, + builder.getInsertionBlock()}; + + lv = emitLValue(cleanups->getSubExpr()); + if (lv.isSimple()) { + // Defend against branches out of gnu statement expressions + // surrounded by cleanups. + Address addr = lv.getAddress(); + mlir::Value v = addr.getPointer(); + assert(!cir::MissingFeatures::addressIsKnownNonNull()); + assert(!cir::MissingFeatures::opTBAA()); + assert(!cir::MissingFeatures::objCGC()); + lv = LValue::makeAddr(addr.withPointer(v), lv.getType(), + lv.getBaseInfo()); + } + }); + + // FIXME: Is it possible to create an ExprWithCleanups that produces a + // bitfield lvalue or some other non-simple lvalue? + return lv; + } case Expr::ParenExprClass: return emitLValue(cast(e)->getSubExpr()); case Expr::GenericSelectionExprClass: diff --git a/clang/test/CIR/CodeGen/temporary-materialization.cpp b/clang/test/CIR/CodeGen/temporary-materialization.cpp new file mode 100644 index 0000000000000..e7088f8bb4d9a --- /dev/null +++ b/clang/test/CIR/CodeGen/temporary-materialization.cpp @@ -0,0 +1,98 @@ +// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --input-file=%t.cir %s --check-prefix=CIR +// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefix=LLVM +// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll +// RUN: FileCheck --input-file=%t.ll %s --check-prefix=OGCG + +int make_int(); + +int test() { + const int &x = make_int(); + return x; +} + +// CIR: cir.func {{.*}} @_Z4testv() +// CIR: %[[TEMP_SLOT:.*]] = cir.alloca !s32i, !cir.ptr, ["ref.tmp0", init] +// CIR-NEXT: %[[X:.*]] = cir.alloca !cir.ptr, !cir.ptr>, ["x", init, const] +// CIR-NEXT: cir.scope { +// CIR-NEXT: %[[TEMP_VALUE:.*]] = cir.call @_Z8make_intv() : () -> !s32i +// CIR-NEXT: cir.store{{.*}} %[[TEMP_VALUE]], %[[TEMP_SLOT]] +// CIR-NEXT: } +// CIR-NEXT: cir.store{{.*}} %[[TEMP_SLOT]], %[[X]] + +// LLVM: define {{.*}} i32 @_Z4testv() +// LLVM: %[[RETVAL:.*]] = alloca i32 +// LLVM: %[[TEMP_SLOT:.*]] = alloca i32 +// LLVM: %[[X:.*]] = alloca ptr +// LLVM: br label %[[SCOPE_LABEL:.*]] +// LLVM: [[SCOPE_LABEL]]: +// LLVM: %[[TEMP_VALUE:.*]] = call i32 @_Z8make_intv() +// LLVM: store i32 %[[TEMP_VALUE]], ptr %[[TEMP_SLOT]] +// LLVM: br label %[[SCOPE_END_LABEL:.*]] +// LLVM: [[SCOPE_END_LABEL]]: +// LLVM: store ptr %[[TEMP_SLOT]], ptr %[[X]] + +// OGCG: define {{.*}} i32 @_Z4testv() +// OGCG: %[[X:.*]] = alloca ptr +// OGCG: %[[TEMP_SLOT:.*]] = alloca i32 +// OGCG: %[[TEMP_VALUE:.*]] = call noundef i32 @_Z8make_intv() +// OGCG: store i32 %[[TEMP_VALUE]], ptr %[[TEMP_SLOT]] +// OGCG: store ptr %[[TEMP_SLOT]], ptr %[[X]] + +int test_scoped() { + int x = make_int(); + { + const int &y = make_int(); + x = y; + } + return x; +} + +// CIR: cir.func {{.*}} @_Z11test_scopedv() +// CIR: %[[X:.*]] = cir.alloca !s32i, !cir.ptr, ["x", init] +// CIR: cir.scope { +// CIR-NEXT: %[[TEMP_SLOT:.*]] = cir.alloca !s32i, !cir.ptr, ["ref.tmp0", init] +// CIR-NEXT: %[[Y_ADDR:.*]] = cir.alloca !cir.ptr, !cir.ptr>, ["y", init, const] +// CIR-NEXT: cir.scope { +// CIR-NEXT: %[[TEMP_VALUE:.*]] = cir.call @_Z8make_intv() : () -> !s32i +// CIR-NEXT: cir.store{{.*}} %[[TEMP_VALUE]], %[[TEMP_SLOT]] : !s32i, !cir.ptr +// CIR-NEXT: } +// CIR-NEXT: cir.store{{.*}} %[[TEMP_SLOT]], %[[Y_ADDR]] : !cir.ptr, !cir.ptr> +// CIR-NEXT: %[[Y_REF:.*]] = cir.load %[[Y_ADDR]] : !cir.ptr>, !cir.ptr +// CIR-NEXT: %[[Y_VALUE:.*]] = cir.load{{.*}} %[[Y_REF]] : !cir.ptr, !s32i +// CIR-NEXT: cir.store{{.*}} %[[Y_VALUE]], %[[X]] : !s32i, !cir.ptr +// CIR-NEXT: } + +// LLVM: define {{.*}} i32 @_Z11test_scopedv() +// LLVM: %[[TEMP_SLOT:.*]] = alloca i32 +// LLVM: %[[Y_ADDR:.*]] = alloca ptr +// LLVM: %[[RETVAL:.*]] = alloca i32 +// LLVM: %[[X:.*]] = alloca i32 +// LLVM: %[[TEMP_VALUE1:.*]] = call i32 @_Z8make_intv() +// LLVM: store i32 %[[TEMP_VALUE1]], ptr %[[X]] +// LLVM: br label %[[SCOPE_LABEL:.*]] +// LLVM: [[SCOPE_LABEL]]: +// LLVM: br label %[[INNER_SCOPE_LABEL:.*]] +// LLVM: [[INNER_SCOPE_LABEL]]: +// LLVM: %[[TEMP_VALUE2:.*]] = call i32 @_Z8make_intv() +// LLVM: store i32 %[[TEMP_VALUE2]], ptr %[[TEMP_SLOT]] +// LLVM: br label %[[INNER_SCOPE_END_LABEL:.*]] +// LLVM: [[INNER_SCOPE_END_LABEL]]: +// LLVM: store ptr %[[TEMP_SLOT]], ptr %[[Y_ADDR]] +// LLVM: %[[Y_REF:.*]] = load ptr, ptr %[[Y_ADDR]] +// LLVM: %[[Y_VALUE:.*]] = load i32, ptr %[[Y_REF]] +// LLVM: store i32 %[[Y_VALUE]], ptr %[[X]] + +// OGCG: define {{.*}} i32 @_Z11test_scopedv() +// OGCG: %[[X:.*]] = alloca i32 +// OGCG: %[[Y_ADDR:.*]] = alloca ptr +// OGCG: %[[TEMP_SLOT:.*]] = alloca i32 +// OGCG: %[[TEMP_VALUE1:.*]] = call noundef i32 @_Z8make_intv() +// OGCG: store i32 %[[TEMP_VALUE1]], ptr %[[X]] +// OGCG: %[[TEMP_VALUE2:.*]] = call noundef i32 @_Z8make_intv() +// OGCG: store i32 %[[TEMP_VALUE2]], ptr %[[TEMP_SLOT]] +// OGCG: store ptr %[[TEMP_SLOT]], ptr %[[Y_ADDR]] +// OGCG: %[[Y_REF:.*]] = load ptr, ptr %[[Y_ADDR]] +// OGCG: %[[Y_VALUE:.*]] = load i32, ptr %[[Y_REF]] +// OGCG: store i32 %[[Y_VALUE]], ptr %[[X]] From fe28db8e861d26e669b1bf58a4503b4e3d518c12 Mon Sep 17 00:00:00 2001 From: Andy Kaylor Date: Thu, 13 Nov 2025 13:51:00 -0800 Subject: [PATCH 2/2] Replace non-working scope handling with a missing feature --- clang/include/clang/CIR/MissingFeatures.h | 1 + clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 28 ++----------------- .../CIR/CodeGen/temporary-materialization.cpp | 20 +++---------- 3 files changed, 8 insertions(+), 41 deletions(-) diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index 5f32abca70baa..567c79a27c07b 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -226,6 +226,7 @@ struct MissingFeatures { static bool cleanupAppendInsts() { return false; } static bool cleanupBranchThrough() { return false; } static bool cleanupIndexAndBIAdjustment() { return false; } + static bool cleanupWithPreservedValues() { return false; } static bool cleanupsToDeactivate() { return false; } static bool constEmitterAggILE() { return false; } static bool constEmitterArrayILE() { return false; } diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index 9baa1fc1dc006..f1be14222434f 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -923,31 +923,9 @@ LValue CIRGenFunction::emitLValue(const Expr *e) { return emitCallExprLValue(cast(e)); case Expr::ExprWithCleanupsClass: { const auto *cleanups = cast(e); - LValue lv; - - mlir::Location scopeLoc = getLoc(e->getSourceRange()); - [[maybe_unused]] auto scope = cir::ScopeOp::create( - builder, scopeLoc, /*scopeBuilder=*/ - [&](mlir::OpBuilder &b, mlir::Location loc) { - CIRGenFunction::LexicalScope lexScope{*this, loc, - builder.getInsertionBlock()}; - - lv = emitLValue(cleanups->getSubExpr()); - if (lv.isSimple()) { - // Defend against branches out of gnu statement expressions - // surrounded by cleanups. - Address addr = lv.getAddress(); - mlir::Value v = addr.getPointer(); - assert(!cir::MissingFeatures::addressIsKnownNonNull()); - assert(!cir::MissingFeatures::opTBAA()); - assert(!cir::MissingFeatures::objCGC()); - lv = LValue::makeAddr(addr.withPointer(v), lv.getType(), - lv.getBaseInfo()); - } - }); - - // FIXME: Is it possible to create an ExprWithCleanups that produces a - // bitfield lvalue or some other non-simple lvalue? + RunCleanupsScope scope(*this); + LValue lv = emitLValue(cleanups->getSubExpr()); + assert(!cir::MissingFeatures::cleanupWithPreservedValues()); return lv; } case Expr::ParenExprClass: diff --git a/clang/test/CIR/CodeGen/temporary-materialization.cpp b/clang/test/CIR/CodeGen/temporary-materialization.cpp index e7088f8bb4d9a..b936ddfe20bf5 100644 --- a/clang/test/CIR/CodeGen/temporary-materialization.cpp +++ b/clang/test/CIR/CodeGen/temporary-materialization.cpp @@ -15,22 +15,16 @@ int test() { // CIR: cir.func {{.*}} @_Z4testv() // CIR: %[[TEMP_SLOT:.*]] = cir.alloca !s32i, !cir.ptr, ["ref.tmp0", init] // CIR-NEXT: %[[X:.*]] = cir.alloca !cir.ptr, !cir.ptr>, ["x", init, const] -// CIR-NEXT: cir.scope { -// CIR-NEXT: %[[TEMP_VALUE:.*]] = cir.call @_Z8make_intv() : () -> !s32i -// CIR-NEXT: cir.store{{.*}} %[[TEMP_VALUE]], %[[TEMP_SLOT]] -// CIR-NEXT: } +// CIR-NEXT: %[[TEMP_VALUE:.*]] = cir.call @_Z8make_intv() : () -> !s32i +// CIR-NEXT: cir.store{{.*}} %[[TEMP_VALUE]], %[[TEMP_SLOT]] // CIR-NEXT: cir.store{{.*}} %[[TEMP_SLOT]], %[[X]] // LLVM: define {{.*}} i32 @_Z4testv() // LLVM: %[[RETVAL:.*]] = alloca i32 // LLVM: %[[TEMP_SLOT:.*]] = alloca i32 // LLVM: %[[X:.*]] = alloca ptr -// LLVM: br label %[[SCOPE_LABEL:.*]] -// LLVM: [[SCOPE_LABEL]]: // LLVM: %[[TEMP_VALUE:.*]] = call i32 @_Z8make_intv() // LLVM: store i32 %[[TEMP_VALUE]], ptr %[[TEMP_SLOT]] -// LLVM: br label %[[SCOPE_END_LABEL:.*]] -// LLVM: [[SCOPE_END_LABEL]]: // LLVM: store ptr %[[TEMP_SLOT]], ptr %[[X]] // OGCG: define {{.*}} i32 @_Z4testv() @@ -54,10 +48,8 @@ int test_scoped() { // CIR: cir.scope { // CIR-NEXT: %[[TEMP_SLOT:.*]] = cir.alloca !s32i, !cir.ptr, ["ref.tmp0", init] // CIR-NEXT: %[[Y_ADDR:.*]] = cir.alloca !cir.ptr, !cir.ptr>, ["y", init, const] -// CIR-NEXT: cir.scope { -// CIR-NEXT: %[[TEMP_VALUE:.*]] = cir.call @_Z8make_intv() : () -> !s32i -// CIR-NEXT: cir.store{{.*}} %[[TEMP_VALUE]], %[[TEMP_SLOT]] : !s32i, !cir.ptr -// CIR-NEXT: } +// CIR-NEXT: %[[TEMP_VALUE:.*]] = cir.call @_Z8make_intv() : () -> !s32i +// CIR-NEXT: cir.store{{.*}} %[[TEMP_VALUE]], %[[TEMP_SLOT]] : !s32i, !cir.ptr // CIR-NEXT: cir.store{{.*}} %[[TEMP_SLOT]], %[[Y_ADDR]] : !cir.ptr, !cir.ptr> // CIR-NEXT: %[[Y_REF:.*]] = cir.load %[[Y_ADDR]] : !cir.ptr>, !cir.ptr // CIR-NEXT: %[[Y_VALUE:.*]] = cir.load{{.*}} %[[Y_REF]] : !cir.ptr, !s32i @@ -73,12 +65,8 @@ int test_scoped() { // LLVM: store i32 %[[TEMP_VALUE1]], ptr %[[X]] // LLVM: br label %[[SCOPE_LABEL:.*]] // LLVM: [[SCOPE_LABEL]]: -// LLVM: br label %[[INNER_SCOPE_LABEL:.*]] -// LLVM: [[INNER_SCOPE_LABEL]]: // LLVM: %[[TEMP_VALUE2:.*]] = call i32 @_Z8make_intv() // LLVM: store i32 %[[TEMP_VALUE2]], ptr %[[TEMP_SLOT]] -// LLVM: br label %[[INNER_SCOPE_END_LABEL:.*]] -// LLVM: [[INNER_SCOPE_END_LABEL]]: // LLVM: store ptr %[[TEMP_SLOT]], ptr %[[Y_ADDR]] // LLVM: %[[Y_REF:.*]] = load ptr, ptr %[[Y_ADDR]] // LLVM: %[[Y_VALUE:.*]] = load i32, ptr %[[Y_REF]]