https://github.com/adams381 updated https://github.com/llvm/llvm-project/pull/201654
>From dffed1eb7570356d7e053bbe7e6c2c2af2314170 Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> Date: Thu, 4 Jun 2026 10:41:58 -0700 Subject: [PATCH 1/4] [CIR] Handle non-zero-initializable types in emitNullInitialization Value-initializing an aggregate containing a pointer-to-data-member (e.g. 'new Inner()' where Inner has an 'int Inner::*' field) crashed with "type is not zero initializable" because emitNullInitialization unconditionally called errorNYI for types where isZeroInitializable returns false. Member pointers use -1 as the null/invalid sentinel, so a plain zero store is incorrect. Replace the errorNYI with a call to emitNullConstant, which already builds the correct per-field pattern (-1 for member-pointer fields, zero elsewhere), and store the result. Types with virtual bases are still guarded with errorNYI since emitNullConstant does not yet handle them. --- clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 15 ++++-- .../CIR/CodeGen/member-pointer-null-init.cpp | 47 +++++++++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 clang/test/CIR/CodeGen/member-pointer-null-init.cpp diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index 4ecb47a864146..f16a5e6eb1574 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -1302,10 +1302,19 @@ void CIRGenFunction::emitNullInitialization(mlir::Location loc, Address destPtr, // If the type contains a pointer to data member we can't memset it to zero. // Instead, create a null constant and copy it to the destination. - // TODO: there are other patterns besides zero that we can usefully memset, - // like -1, which happens to be the pattern used by member-pointers. + // Member pointers use -1 as the null value, so a plain zero store would be + // incorrect; emitNullConstant produces the right per-field pattern. if (!cgm.getTypes().isZeroInitializable(ty)) { - cgm.errorNYI(loc, "type is not zero initializable"); + // emitNullConstant does not yet handle types with virtual bases. + if (const auto *rd = ty->getAsCXXRecordDecl(); rd && rd->getNumVBases()) { + cgm.errorNYI(loc, + "emitNullInitialization: non-zero-init type with virtual " + "bases"); + return; + } + mlir::Value nullVal = cgm.emitNullConstant(ty, loc); + builder.createStore(loc, nullVal, destPtr); + return; } // In LLVM Codegen: otherwise, just memset the whole thing to zero using diff --git a/clang/test/CIR/CodeGen/member-pointer-null-init.cpp b/clang/test/CIR/CodeGen/member-pointer-null-init.cpp new file mode 100644 index 0000000000000..74ee344c6f407 --- /dev/null +++ b/clang/test/CIR/CodeGen/member-pointer-null-init.cpp @@ -0,0 +1,47 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++17 -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++17 -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --check-prefix=LLVM,LLVMCIR --input-file=%t-cir.ll %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++17 -emit-llvm %s -o %t.ll +// RUN: FileCheck --check-prefix=LLVM,OGCG --input-file=%t.ll %s + +struct Inner { + int Inner::*p; +}; + +struct Outer { + Inner a; + int b; +}; + +// Value-init of a heap-allocated struct containing a pointer-to-data-member. +// The member pointer is null (-1), so the stored constant must carry -1. + +// CIR-LABEL: cir.func {{.*}}@_Z8make_newv +// CIR: [[NULL:%.*]] = cir.const #cir.const_record<{#cir.int<-1> : !s64i}> : !rec_Inner +// CIR: cir.store align(8) [[NULL]], {{%.*}} : !rec_Inner, !cir.ptr<!rec_Inner> + +// LLVMCIR-LABEL: define {{.*}} ptr @_Z8make_newv +// LLVMCIR: call {{.*}} @_Znwm +// LLVMCIR: store %struct.Inner { i64 -1 }, ptr %{{.*}}, align 8 + +// OGCG: @{{.*}} = private constant %struct.Inner { i64 -1 } +// OGCG-LABEL: define {{.*}} ptr @_Z8make_newv +// OGCG: call {{.*}} @llvm.memcpy{{.*}}i64 8 + +Inner *make_new() { return new Inner(); } + +// Partial aggregate init: Inner subobject 'a' is value-initialized because +// it has no designated initializer. + +// CIR-LABEL: cir.func {{.*}}@_Z11runtime_aggi +// CIR: cir.const #cir.int<-1> : !s64i +// CIR: cir.store align(8) {{%.*}}, {{%.*}} : !s64i + +// LLVM-LABEL: define {{.*}} void @_Z11runtime_aggi +// LLVM: store i64 -1, ptr %{{.*}}, align 8 + +void runtime_agg(int x) { + Outer o = {.b = x}; + (void)o; +} >From 6bb221ecfc707b4ff6cc62f678d4282803b4e7d6 Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> Date: Tue, 9 Jun 2026 09:41:31 -0700 Subject: [PATCH 2/4] [CIR] Fix null-init for non-zero-init aggregates Value-initializing an aggregate containing a pointer-to-data-member (e.g. 'new Inner()' where Inner has an 'int Inner::*' field) crashed with "type is not zero initializable" because emitNullInitialization unconditionally called errorNYI for types where isZeroInitializable returns false. Member pointers use -1 as the null/invalid sentinel, so a plain zero store is incorrect. Replace the errorNYI with a call to emitNullConstant, which already builds the correct per-field pattern (-1 for member-pointer fields, zero elsewhere), and store the result. Types with virtual bases are still guarded with errorNYI since emitNullConstant does not yet handle them. --- clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index f16a5e6eb1574..0a392b4202625 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -1302,10 +1302,15 @@ void CIRGenFunction::emitNullInitialization(mlir::Location loc, Address destPtr, // If the type contains a pointer to data member we can't memset it to zero. // Instead, create a null constant and copy it to the destination. - // Member pointers use -1 as the null value, so a plain zero store would be - // incorrect; emitNullConstant produces the right per-field pattern. + // TODO: there are other patterns besides zero that we can usefully memset, + // like -1, which happens to be the pattern used by member-pointers. if (!cgm.getTypes().isZeroInitializable(ty)) { - // emitNullConstant does not yet handle types with virtual bases. + // Classic codegen handles non-zero-init VLAs here via emitNonZeroVLAInit. + // In CIR, getTypeSizeInChars returns 0 for VLAs, so they are caught by + // the errorNYI above. + // + // Guard: emitNullConstant calls errorNYI for virtual bases and returns {}, + // which would crash builder.getConstant; report the NYI here instead. if (const auto *rd = ty->getAsCXXRecordDecl(); rd && rd->getNumVBases()) { cgm.errorNYI(loc, "emitNullInitialization: non-zero-init type with virtual " >From 1ce4474755e14efd2223b98f5f2f11d962f220f8 Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> Date: Mon, 22 Jun 2026 10:31:07 -0700 Subject: [PATCH 3/4] [CIR] Assert vbases in emitNullInitialization Replace the virtual-base errorNYI guard in emitNullInitialization with an assert. A record only reaches the non-zero-initializable branch because it contains a pointer to data member, and emitNullConstant already reports its own NYI for the constant shapes it cannot build, so the separate guard here was redundant. Virtual bases are the one reachable shape emitNullConstant cannot lower yet; until there is test coverage matching the classic output for that case, assert they are absent rather than emitting an untested constant. --- clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index 0a392b4202625..aa5b95c66358c 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -1305,18 +1305,14 @@ void CIRGenFunction::emitNullInitialization(mlir::Location loc, Address destPtr, // TODO: there are other patterns besides zero that we can usefully memset, // like -1, which happens to be the pattern used by member-pointers. if (!cgm.getTypes().isZeroInitializable(ty)) { - // Classic codegen handles non-zero-init VLAs here via emitNonZeroVLAInit. - // In CIR, getTypeSizeInChars returns 0 for VLAs, so they are caught by - // the errorNYI above. - // - // Guard: emitNullConstant calls errorNYI for virtual bases and returns {}, - // which would crash builder.getConstant; report the NYI here instead. - if (const auto *rd = ty->getAsCXXRecordDecl(); rd && rd->getNumVBases()) { - cgm.errorNYI(loc, - "emitNullInitialization: non-zero-init type with virtual " - "bases"); - return; - } + // The only non-zero-initializable shape exercised here is an aggregate + // whose null pattern comes from a pointer to data member, which + // emitNullConstant fills in per field (-1 for member-pointer fields). + // Virtual bases are the one reachable shape it cannot lower yet; assert + // they are absent rather than emitting an untested constant. + [[maybe_unused]] const auto *rd = ty->getAsCXXRecordDecl(); + assert((!rd || rd->getNumVBases() == 0) && + "emitNullInitialization: virtual bases not yet supported"); mlir::Value nullVal = cgm.emitNullConstant(ty, loc); builder.createStore(loc, nullVal, destPtr); return; >From a705e5fe42a9e854dbc7f08e9b2596d8e1d15600 Mon Sep 17 00:00:00 2001 From: Adam Smith <[email protected]> Date: Tue, 23 Jun 2026 09:49:13 -0700 Subject: [PATCH 4/4] [CIR] Assert null-init handles only tested shape emitNullInitialization's non-zero-initializable branch previously asserted that the type had no virtual bases. That guard was redundant: emitNullConstant already reports the NYI for virtual bases (and for non-zero-init arrays), so this call site does not need to. Replace it with the assert the path actually wants -- that the type is the pointer-to-data-member case the tests cover. Inside the !isZeroInitializable branch an Itanium record is non-zero-initializable only because it transitively contains a pointer to data member, so isMemberDataPointerType() || isRecordType() pins exactly that case; any other shape (e.g. a non-zero-init array) now trips the assert instead of emitting an untested constant. --- clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index aa5b95c66358c..dd733216f8268 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -1305,14 +1305,12 @@ void CIRGenFunction::emitNullInitialization(mlir::Location loc, Address destPtr, // TODO: there are other patterns besides zero that we can usefully memset, // like -1, which happens to be the pattern used by member-pointers. if (!cgm.getTypes().isZeroInitializable(ty)) { - // The only non-zero-initializable shape exercised here is an aggregate - // whose null pattern comes from a pointer to data member, which - // emitNullConstant fills in per field (-1 for member-pointer fields). - // Virtual bases are the one reachable shape it cannot lower yet; assert - // they are absent rather than emitting an untested constant. - [[maybe_unused]] const auto *rd = ty->getAsCXXRecordDecl(); - assert((!rd || rd->getNumVBases() == 0) && - "emitNullInitialization: virtual bases not yet supported"); + // Only the pointer-to-data-member case is tested here; emitNullConstant + // owns the NYIs for shapes it cannot build (virtual bases, non-zero-init + // arrays). + assert((ty->isMemberDataPointerType() || ty->isRecordType()) && + "emitNullInitialization: only pointer-to-data-member (directly or " + "within a record) null initialization is implemented"); mlir::Value nullVal = cgm.emitNullConstant(ty, loc); builder.createStore(loc, nullVal, destPtr); return; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
