[clang] Implement CWG2627 Bit-fields and narrowing conversions#78112
[clang] Implement CWG2627 Bit-fields and narrowing conversions#78112
Conversation
|
@llvm/pr-subscribers-clang Author: Mital Ashok (MitalAshok) ChangesI've implemented this to apply to C++11 to 20 as well without a warning. Should this be a SFINAE-able error before C++23? (It isn't currently) Full diff: https://github.com/llvm/llvm-project/pull/78112.diff 5 Files Affected:
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index dc8a6fe506bce6..6fd84d9c3364d3 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -226,6 +226,10 @@ C++2c Feature Support
Resolutions to C++ Defect Reports
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+- Implemented `CWG2627 <https://wg21.link/CWG2627>`_ which means casts from
+ a bit-field to an integral type is now not considered narrowing if the
+ width of the bit-field means that all potential values are in the range
+ of the target type, even if the type of the bit-field is larger.
C Language Changes
------------------
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 23b9bc0fe2d6e2..b053b9ddfa5f26 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -433,7 +433,11 @@ NarrowingKind StandardConversionSequence::getNarrowingKind(
// -- from an integer type or unscoped enumeration type to an integer type
// that cannot represent all the values of the original type, except where
- // the source is a constant expression and the actual value after
+ // -- the source is a bit-field whose width w is less than that of its type
+ // (or, for an enumeration type, its underlying type) and the target type
+ // can represent all the values of a hypothetical extended integer type
+ // with width w and with the same signedness as the original type or
+ // -- the source is a constant expression and the actual value after
// conversion will fit into the target type and will produce the original
// value when converted back to the original type.
case ICK_Integral_Conversion:
@@ -441,53 +445,74 @@ NarrowingKind StandardConversionSequence::getNarrowingKind(
assert(FromType->isIntegralOrUnscopedEnumerationType());
assert(ToType->isIntegralOrUnscopedEnumerationType());
const bool FromSigned = FromType->isSignedIntegerOrEnumerationType();
- const unsigned FromWidth = Ctx.getIntWidth(FromType);
+ unsigned FromWidth = Ctx.getIntWidth(FromType);
const bool ToSigned = ToType->isSignedIntegerOrEnumerationType();
const unsigned ToWidth = Ctx.getIntWidth(ToType);
- if (FromWidth > ToWidth ||
- (FromWidth == ToWidth && FromSigned != ToSigned) ||
- (FromSigned && !ToSigned)) {
- // Not all values of FromType can be represented in ToType.
- const Expr *Initializer = IgnoreNarrowingConversion(Ctx, Converted);
+ constexpr auto CanRepresentAll = [](bool FromSigned, unsigned FromWidth,
+ bool ToSigned, unsigned ToWidth) {
+ return (FromWidth < ToWidth + (FromSigned == ToSigned)) &&
+ (FromSigned <= ToSigned);
+ };
- // If it's value-dependent, we can't tell whether it's narrowing.
- if (Initializer->isValueDependent())
- return NK_Dependent_Narrowing;
+ if (CanRepresentAll(FromSigned, FromWidth, ToSigned, ToWidth))
+ return NK_Not_Narrowing;
- std::optional<llvm::APSInt> OptInitializerValue;
- if (!(OptInitializerValue = Initializer->getIntegerConstantExpr(Ctx))) {
- // Such conversions on variables are always narrowing.
- return NK_Variable_Narrowing;
- }
- llvm::APSInt &InitializerValue = *OptInitializerValue;
- bool Narrowing = false;
- if (FromWidth < ToWidth) {
- // Negative -> unsigned is narrowing. Otherwise, more bits is never
- // narrowing.
- if (InitializerValue.isSigned() && InitializerValue.isNegative())
- Narrowing = true;
- } else {
- // Add a bit to the InitializerValue so we don't have to worry about
- // signed vs. unsigned comparisons.
- InitializerValue = InitializerValue.extend(
- InitializerValue.getBitWidth() + 1);
- // Convert the initializer to and from the target width and signed-ness.
- llvm::APSInt ConvertedValue = InitializerValue;
- ConvertedValue = ConvertedValue.trunc(ToWidth);
- ConvertedValue.setIsSigned(ToSigned);
- ConvertedValue = ConvertedValue.extend(InitializerValue.getBitWidth());
- ConvertedValue.setIsSigned(InitializerValue.isSigned());
- // If the result is different, this was a narrowing conversion.
- if (ConvertedValue != InitializerValue)
- Narrowing = true;
- }
- if (Narrowing) {
- ConstantType = Initializer->getType();
- ConstantValue = APValue(InitializerValue);
- return NK_Constant_Narrowing;
+ // Not all values of FromType can be represented in ToType.
+ const Expr *Initializer = IgnoreNarrowingConversion(Ctx, Converted);
+
+ bool DependentBitField = false;
+ if (auto *BF = Initializer->getSourceBitField()) {
+ auto *Width = BF->getBitWidth();
+ DependentBitField = Width->isValueDependent();
+ if (!DependentBitField) {
+ FromWidth = BF->getBitWidthValue(Ctx);
+ if (CanRepresentAll(FromSigned, FromWidth, ToSigned, ToWidth))
+ return NK_Not_Narrowing;
}
}
+
+ // If it's value-dependent, we can't tell whether it's narrowing.
+ if (Initializer->isValueDependent())
+ return NK_Dependent_Narrowing;
+
+ std::optional<llvm::APSInt> OptInitializerValue;
+ if (!(OptInitializerValue = Initializer->getIntegerConstantExpr(Ctx))) {
+ // Such would be narrowing only if the source width ends up being too
+ // large
+ if (DependentBitField)
+ return NK_Dependent_Narrowing;
+ // Otherwise it is narrowing
+ return NK_Variable_Narrowing;
+ }
+ llvm::APSInt &InitializerValue = *OptInitializerValue;
+ bool Narrowing = false;
+ if (FromWidth < ToWidth) {
+ // Negative -> unsigned is narrowing. Otherwise, more bits is never
+ // narrowing.
+ if (InitializerValue.isSigned() && InitializerValue.isNegative())
+ Narrowing = true;
+ } else {
+ // Add a bit to the InitializerValue so we don't have to worry about
+ // signed vs. unsigned comparisons.
+ InitializerValue =
+ InitializerValue.extend(InitializerValue.getBitWidth() + 1);
+ // Convert the initializer to and from the target width and signed-ness.
+ llvm::APSInt ConvertedValue = InitializerValue;
+ ConvertedValue = ConvertedValue.trunc(ToWidth);
+ ConvertedValue.setIsSigned(ToSigned);
+ ConvertedValue = ConvertedValue.extend(InitializerValue.getBitWidth());
+ ConvertedValue.setIsSigned(InitializerValue.isSigned());
+ // If the result is different, this was a narrowing conversion.
+ if (ConvertedValue != InitializerValue)
+ Narrowing = true;
+ }
+ if (Narrowing) {
+ ConstantType = Initializer->getType();
+ ConstantValue = APValue(InitializerValue);
+ return NK_Constant_Narrowing;
+ }
+
return NK_Not_Narrowing;
}
diff --git a/clang/test/CXX/dcl.decl/dcl.init/dcl.init.list/p7-0x.cpp b/clang/test/CXX/dcl.decl/dcl.init/dcl.init.list/p7-0x.cpp
index 2bceb3e267790d..a3049dc57d9fac 100644
--- a/clang/test/CXX/dcl.decl/dcl.init/dcl.init.list/p7-0x.cpp
+++ b/clang/test/CXX/dcl.decl/dcl.init/dcl.init.list/p7-0x.cpp
@@ -203,6 +203,30 @@ void shrink_int() {
unsigned short usc1 = { c }; // expected-error {{non-constant-expression cannot be narrowed from type 'signed char'}} expected-note {{silence}}
unsigned short usc2 = { (signed char)'x' }; // OK
unsigned short usc3 = { (signed char)-1 }; // expected-error {{ -1 which cannot be narrowed}} expected-note {{silence}}
+
+#if __BITINT_MAXWIDTH__ >= 3
+ _BitInt(2) S2 = 0;
+ unsigned _BitInt(2) U2 = 0;
+ _BitInt(3) S3 = 0;
+ unsigned _BitInt(3) U3 = 0;
+
+ _BitInt(2) bi0 = { S2 };
+ _BitInt(2) bi1 = { U2 }; // expected-error {{cannot be narrowed from type 'unsigned _BitInt(2)'}}
+ _BitInt(2) bi2 = { S3 }; // expected-error {{cannot be narrowed from type '_BitInt(3)'}}
+ _BitInt(2) bi3 = { U3 }; // expected-error {{cannot be narrowed from type 'unsigned _BitInt(3)'}}
+ unsigned _BitInt(2) bi4 = { S2 }; // expected-error {{cannot be narrowed from type '_BitInt(2)'}}
+ unsigned _BitInt(2) bi5 = { U2 };
+ unsigned _BitInt(2) bi6 = { S3 }; // expected-error {{cannot be narrowed from type '_BitInt(3)'}}
+ unsigned _BitInt(2) bi7 = { U3 }; // expected-error {{cannot be narrowed from type 'unsigned _BitInt(3)'}}
+ _BitInt(3) bi8 = { S2 };
+ _BitInt(3) bi9 = { U2 };
+ _BitInt(3) bia = { S3 };
+ _BitInt(3) bib = { U3 }; // expected-error {{cannot be narrowed from type 'unsigned _BitInt(3)'}}
+ unsigned _BitInt(3) bic = { S2 }; // expected-error {{cannot be narrowed from type '_BitInt(2)'}}
+ unsigned _BitInt(3) bid = { U2 };
+ unsigned _BitInt(3) bie = { S3 }; // expected-error {{cannot be narrowed from type '_BitInt(3)'}}
+ unsigned _BitInt(3) bif = { U3 };
+#endif
}
// Be sure that type- and value-dependent expressions in templates get the error
diff --git a/clang/test/CXX/drs/dr26xx.cpp b/clang/test/CXX/drs/dr26xx.cpp
index f151c9eea051a3..3b2b26c101d61b 100644
--- a/clang/test/CXX/drs/dr26xx.cpp
+++ b/clang/test/CXX/drs/dr26xx.cpp
@@ -6,6 +6,19 @@
// RUN: %clang_cc1 -std=c++23 -triple x86_64-unknown-unknown %s -verify=expected,since-cxx11,since-cxx20,since-cxx23
// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-unknown %s -verify=expected,since-cxx11,since-cxx20,since-cxx23
+namespace std {
+#if __cplusplus >= 202002L
+ struct strong_ordering {
+ int n;
+ constexpr operator int() const { return n; }
+ static const strong_ordering less, equal, greater;
+ };
+ constexpr strong_ordering strong_ordering::less{-1},
+ strong_ordering::equal{0}, strong_ordering::greater{1};
+#endif
+
+ typedef __INT64_TYPE__ int64_t;
+}
namespace dr2621 { // dr2621: 16
#if __cplusplus >= 202002L
@@ -24,6 +37,39 @@ using enum E;
#endif
}
+namespace dr2627 { // dr2627: 18
+#if __cplusplus >= 201103L
+struct C {
+ long long i : 8;
+};
+
+#if __cplusplus >= 202002L
+void f() {
+ C x{1}, y{2};
+ static_cast<void>(x.i <=> y.i);
+}
+#endif
+
+template<int N>
+struct D {
+ __int128 i : N;
+};
+
+template<int N>
+std::int64_t f(D<N> d) {
+ return std::int64_t{ d.i }; // #dr2627-f-narrowing
+}
+
+template std::int64_t f(D<63>);
+template std::int64_t f(D<64>);
+template std::int64_t f(D<65>);
+// since-cxx11-error-re@#dr2627-f-narrowing {{non-constant-expression cannot be narrowed from type '__int128' to 'std::int64_t' (aka '{{.+}}') in initializer list}}
+// since-cxx11-note@-2 {{in instantiation of function template specialization 'dr2627::f<65>' requested here}}
+// since-cxx11-note@#dr2627-f-narrowing {{insert an explicit cast to silence this issue}}
+
+#endif
+}
+
namespace dr2628 { // dr2628: no
// this was reverted for the 16.x release
// due to regressions, see the issue for more details:
diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html
index 397bf1357d3cb3..caecdfe27a9498 100755
--- a/clang/www/cxx_dr_status.html
+++ b/clang/www/cxx_dr_status.html
@@ -15570,7 +15570,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
<td><a href="https://cplusplus.github.io/CWG/issues/2627.html">2627</a></td>
<td>C++23</td>
<td>Bit-fields and narrowing conversions</td>
- <td class="unknown" align="center">Unknown</td>
+ <td class="unreleased" align="center">Clang 18</td>
</tr>
<tr id="2628">
<td><a href="https://cplusplus.github.io/CWG/issues/2628.html">2628</a></td>
|
I believe this should (in C++11 and later modes). |
|
New version gives a pedantic warning if this is used before C++23 (and it is a substitution failure before C++23) |
|
Given that this is a core issue resolution, i think it should be applied in all language modes without warning, even under pedantics. This is compounding by the fact that MSVC never warned (gcc warns in all language modes - they don't seem to implement the DR yet) (unless we have some reason to believe it would be disruptive) |
Endilll
left a comment
There was a problem hiding this comment.
Thank you for working on this!
AaronBallman
left a comment
There was a problem hiding this comment.
I'm pretty sure the C implementation of constexpr leans on this code to determine if the value is exactly representable without a change in value. But it looks like Clang currently doesn't get this quite right: https://godbolt.org/z/W3a8cPjnM Can you add some tests for C as well? (We can use the tests to document existing behavior, but there may be bugs here to be addressed. CC @Fznamznon)
|
@AaronBallman This shouldn't affect C at all since this is only about non-constant-expressions which come from a bit-field |
AaronBallman
left a comment
There was a problem hiding this comment.
Generally LGTM, just a few minor things. Thank you for this!
| // RUN: %clang_cc1 -triple i386-linux-pc -fsyntax-only -verify -std=c++11 %s | ||
| // RUN: %clang_cc1 -triple i386-windows-pc -fsyntax-only -verify -std=c++11 %s | ||
|
|
||
| #if __BITINT_MAXWIDTH__ >= 35 |
There was a problem hiding this comment.
No need for this predicate, it's required to be at least the same width as long long.
| // FIXME-expected-note@-2 {{insert an explicit cast to silence this issue}} | ||
| #endif | ||
|
|
||
| #if __BITINT_MAXWIDTH__ >= 3 |
| // RUN: %clang_cc1 -triple x86_64-linux-pc -fsyntax-only -verify -std=c++11 %s | ||
| // RUN: %clang_cc1 -triple x86_64-windows-pc -fsyntax-only -verify -std=c++11 %s | ||
| // RUN: %clang_cc1 -triple i386-linux-pc -fsyntax-only -verify -std=c++11 %s | ||
| // RUN: %clang_cc1 -triple i386-windows-pc -fsyntax-only -verify -std=c++11 %s |
There was a problem hiding this comment.
Could we get away with two RUN lines instead of four by doing -triple x86_64 and -triple i386 and leaving the rest of the triple to the test runner?
|
@AaronBallman I think I've addressed all your comments, thanks for the review! Could you please merge this for me if there is nothing else? |
Endilll
left a comment
There was a problem hiding this comment.
DR test part looks good.
* 'main' of https://github.com/llvm/llvm-project: (700 commits) [SandboxIR][NFC] SingleLLVMInstructionImpl class (llvm#102687) [ThinLTO]Clean up 'import-assume-unique-local' flag. (llvm#102424) [nsan] Make #include more conventional [SandboxIR][NFC] Use Tracker.emplaceIfTracking() [libc] Moved range_reduction_double ifdef statement (llvm#102659) [libc] Fix CFP long double and add tests (llvm#102660) [TargetLowering] Handle vector types in expandFixedPointMul (llvm#102635) [compiler-rt][NFC] Replace environment variable with %t (llvm#102197) [UnitTests] Convert a test to use opaque pointers (llvm#102668) [CodeGen][NFCI] Don't re-implement parts of ASTContext::getIntWidth (llvm#101765) [SandboxIR] Clean up tracking code with the help of emplaceIfTracking() (llvm#102406) [mlir][bazel] remove extra blanks in mlir-tblgen test [NVPTX][NFC] Update tests to use bfloat type (llvm#101493) [mlir] Add support for parsing nested PassPipelineOptions (llvm#101118) [mlir][bazel] add missing td dependency in mlir-tblgen test [flang][cuda] Fix lib dependency [libc] Clean up remaining use of *_WIDTH macros in printf (llvm#102679) [flang][cuda] Convert cuf.alloc for box to fir.alloca in device context (llvm#102662) [SandboxIR] Implement the InsertElementInst class (llvm#102404) [libc] Fix use of cpp::numeric_limits<...>::digits (llvm#102674) [mlir][ODS] Verify type constraints in Types and Attributes (llvm#102326) [LTO] enable `ObjCARCContractPass` only on optimized build (llvm#101114) [mlir][ODS] Consistent `cppType` / `cppClassName` usage (llvm#102657) [lldb] Move definition of SBSaveCoreOptions dtor out of header (llvm#102539) [libc] Use cpp::numeric_limits in preference to C23 <limits.h> macros (llvm#102665) [clang] Implement -fptrauth-auth-traps. (llvm#102417) [LLVM][rtsan] rtsan transform to preserve CFGAnalyses (llvm#102651) Revert "[AMDGPU] Move `AMDGPUAttributorPass` to full LTO post link stage (llvm#102086)" [RISCV][GISel] Add missing tests for G_CTLZ/CTTZ instruction selection. NFC Return available function types for BindingDecls. (llvm#102196) [clang] Wire -fptrauth-returns to "ptrauth-returns" fn attribute. (llvm#102416) [RISCV] Remove riscv-experimental-rv64-legal-i32. (llvm#102509) [RISCV] Move PseudoVSET(I)VLI expansion to use PseudoInstExpansion. (llvm#102496) [NVPTX] support switch statement with brx.idx (reland) (llvm#102550) [libc][newhdrgen]sorted function names in yaml (llvm#102544) [GlobalIsel] Combine G_ADD and G_SUB with constants (llvm#97771) Suppress spurious warnings due to R_RISCV_SET_ULEB128 [scudo] Separated committed and decommitted entries. (llvm#101409) [MIPS] Fix missing ANDI optimization (llvm#97689) [Clang] Add env var for nvptx-arch/amdgpu-arch timeout (llvm#102521) [asan] Switch allocator to dynamic base address (llvm#98511) [AMDGPU] Move `AMDGPUAttributorPass` to full LTO post link stage (llvm#102086) [libc][math][c23] Add fadd{l,f128} C23 math functions (llvm#102531) [mlir][bazel] revert bazel rule change for DLTITransformOps [msan] Support vst{2,3,4}_lane instructions (llvm#101215) Revert "[MLIR][DLTI][Transform] Introduce transform.dlti.query (llvm#101561)" [X86] pr57673.ll - generate MIR test checks [mlir][vector][test] Split tests from vector-transfer-flatten.mlir (llvm#102584) [mlir][bazel] add bazel rule for DLTITransformOps OpenMPOpt: Remove dead include [IR] Add method to GlobalVariable to change type of initializer. (llvm#102553) [flang][cuda] Force default allocator in device code (llvm#102238) [llvm] Construct SmallVector<SDValue> with ArrayRef (NFC) (llvm#102578) [MLIR][DLTI][Transform] Introduce transform.dlti.query (llvm#101561) [AMDGPU][AsmParser][NFC] Remove a misleading comment. (llvm#102604) [Arm][AArch64][Clang] Respect function's branch protection attributes. (llvm#101978) [mlir] Verifier: steal bit to track seen instead of set. (llvm#102626) [Clang] Fix Handling of Init Capture with Parameter Packs in LambdaScopeForCallOperatorInstantiationRAII (llvm#100766) [X86] Convert truncsat clamping patterns to use SDPatternMatch. NFC. [gn] Give two scripts argparse.RawDescriptionHelpFormatter [bazel] Add missing dep for the SPIRVToLLVM target [Clang] Simplify specifying passes via -Xoffload-linker (llvm#102483) [bazel] Port for d45de80 [SelectionDAG] Use unaligned store/load to move AVX registers onto stack for `insertelement` (llvm#82130) [Clang][OMPX] Add the code generation for multi-dim `num_teams` (llvm#101407) [ARM] Regenerate big-endian-vmov.ll. NFC [AMDGPU][AsmParser][NFCI] All NamedIntOperands to be of the i32 type. (llvm#102616) [libc][math][c23] Add totalorderl function. (llvm#102564) [mlir][spirv] Support `memref` in `convert-to-spirv` pass (llvm#102534) [MLIR][GPU-LLVM] Convert `gpu.func` to `llvm.func` (llvm#101664) Fix a unit test input file (llvm#102567) [llvm-readobj][COFF] Dump hybrid objects for ARM64X files. (llvm#102245) AMDGPU/NewPM: Port SIFixSGPRCopies to new pass manager (llvm#102614) [MemoryBuiltins] Simplify getCalledFunction() helper (NFC) [AArch64] Add invalid 1 x vscale costs for reductions and reduction-operations. (llvm#102105) [MemoryBuiltins] Handle allocator attributes on call-site LSV/test/AArch64: add missing lit.local.cfg; fix build (llvm#102607) Revert "Enable logf128 constant folding for hosts with 128bit floats (llvm#96287)" [RISCV] Add Syntacore SCR5 RV32/64 processors definition (llvm#102285) [InstCombine] Remove unnecessary RUN line from test (NFC) [flang][OpenMP] Handle multiple ranges in `num_teams` clause (llvm#102535) [mlir][vector] Add tests for scalable vectors in one-shot-bufferize.mlir (llvm#102361) [mlir][vector] Disable `vector.matrix_multiply` for scalable vectors (llvm#102573) [clang] Implement CWG2627 Bit-fields and narrowing conversions (llvm#78112) [NFC] Use references to avoid copying (llvm#99863) Revert "[mlir][ArmSME] Pattern to swap shape_cast(tranpose) with transpose(shape_cast) (llvm#100731)" (llvm#102457) [IRBuilder] Generate nuw GEPs for struct member accesses (llvm#99538) [bazel] Port for 9b06e25 [CodeGen][NewPM] Improve start/stop pass error message CodeGenPassBuilder (llvm#102591) [AArch64] Implement TRBMPAM_EL1 system register (llvm#102485) [InstCombine] Fixing wrong select folding in vectors with undef elements (llvm#102244) [AArch64] Sink operands to fmuladd. (llvm#102297) LSV: document hang reported in llvm#37865 (llvm#102479) Enable logf128 constant folding for hosts with 128bit floats (llvm#96287) [RISCV][clang] Remove bfloat base type in non-zvfbfmin vcreate (llvm#102146) [RISCV][clang] Add missing `zvfbfmin` to `vget_v` intrinsic (llvm#102149) [mlir][vector] Add mask elimination transform (llvm#99314) [Clang][Interp] Fix display of syntactically-invalid note for member function calls (llvm#102170) [bazel] Port for 3fffa6d [DebugInfo][RemoveDIs] Use iterator-inserters in clang (llvm#102006) ... Signed-off-by: Edwiin Kusuma Jaya <kutemeikito0905@gmail.com>
https://cplusplus.github.io/CWG/issues/2627.html
It is no longer a narrowing conversion when converting a bit-field to a type smaller than the field's declared type if the bit-field has a width small enough to fit in the target type. This includes integral promotions (
long long i : 8promoted tointis no longer narrowing, allowingc.i <=> c.i) and list-initialization (int n{ c.i };)Also applies back to C++11 as this is a defect report.