=?utf-8?q?Félix?= Cloutier <[email protected]>, =?utf-8?q?Félix?= Cloutier <[email protected]>, =?utf-8?q?Félix?= Cloutier <[email protected]>, =?utf-8?q?Félix?= Cloutier <[email protected]>, =?utf-8?q?Félix?= Cloutier <[email protected]>, =?utf-8?q?Félix?= Cloutier <[email protected]> Message-ID: In-Reply-To: <llvm.org/llvm/llvm-project/pull/[email protected]>
https://github.com/apple-fcloutier updated https://github.com/llvm/llvm-project/pull/160790 >From 93926f92102688806f550867ef0687006fbff6d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Cloutier?= <[email protected]> Date: Mon, 18 Nov 2024 18:23:48 -0800 Subject: [PATCH 1/7] [clang] Implement -fstrict-bool ``bool`` values are stored as i8 in memory, and it is undefined behavior for a ``bool`` value to be any value other than 0 or 1. Clang exploits this with range metadata: ``bool`` load instructions at any optimization level above -O0 are assumed to only have their lowest bit set. This can create memory safety problems when other bits are set, for instance through ``memcpy``. This change allows users to configure this behavior in three ways: * ``-fstrict-bool`` represents the status quo; range metadata is added at levels above -O0 and allows the compiler to assume in-memory ``bool`` values are always either 0 or 1. * ``-fno-strict-bool[={truncate|nonzero}]`` disables range metadata on ``bool`` loaded values and offers two ways to interpret the loaded values. ``truncate`` means the value is true is the least significant bit is 1 and false otherwise; ``nonzero`` means the value is true if any bit is 1 and false otherwise. The default is ``-fstrict-bool`` to not change the current behavior of Clang. The default behavior of ``-fno-strict-bool`` is ``truncate``. Radar-ID: 139397212 --- clang/docs/ReleaseNotes.rst | 1 + clang/docs/UsersManual.rst | 18 +++++++++++++++ clang/include/clang/Basic/CodeGenOptions.def | 1 + clang/include/clang/Basic/CodeGenOptions.h | 10 ++++++++ clang/include/clang/Options/Options.td | 21 +++++++++++++++++ clang/lib/CodeGen/CGExpr.cpp | 24 +++++++++++++++----- clang/lib/Driver/ToolChains/Clang.cpp | 22 ++++++++++++++++++ clang/test/CodeGen/strict-bool.c | 23 +++++++++++++++++++ clang/test/Driver/strict-bool.c | 22 ++++++++++++++++++ 9 files changed, 136 insertions(+), 6 deletions(-) create mode 100644 clang/test/CodeGen/strict-bool.c create mode 100644 clang/test/Driver/strict-bool.c diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index feaf92ad4415f..8ac6e5faf622e 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -342,6 +342,7 @@ New Compiler Flags - New option ``-fsanitize-debug-trap-reasons=`` added to control emitting trap reasons into the debug info when compiling with trapping UBSan (e.g. ``-fsanitize-trap=undefined``). - New options for enabling allocation token instrumentation: ``-fsanitize=alloc-token``, ``-falloc-token-max=``, ``-fsanitize-alloc-token-fast-abi``, ``-fsanitize-alloc-token-extended``. - The ``-resource-dir`` option is now displayed in the list of options shown by ``--help``. +- New option ``-f[no-]strict-bool`` added to control whether Clang can assume that ``bool`` values loaded from memory cannot have a bit pattern other than 0 or 1. - New option ``-fmatrix-memory-layout`` added to control the memory layout of Clang matrix types. (e.g. ``-fmatrix-memory-layout=column-major`` or ``-fmatrix-memory-layout=row-major``). Lanai Support diff --git a/clang/docs/UsersManual.rst b/clang/docs/UsersManual.rst index c624efb26f67d..b9d4e857d5159 100644 --- a/clang/docs/UsersManual.rst +++ b/clang/docs/UsersManual.rst @@ -2321,6 +2321,24 @@ are listed below. additional function arity information (for supported targets). See :doc:`ControlFlowIntegrity` for more details. +.. option:: -fstrict-bool + + ``bool`` values are stored to memory as 8-bit values on most targets. Under + ``-fstrict-bool``, it is undefined behavior for a ``bool`` value stored in + memory to have any other bit pattern than 0 or 1. This creates some + optimization opportunities for the compiler, but it enables memory + corruption if that assumption is violated, for instance if any other value + is ``memcpy``ed over a ``bool``. This is enabled by default. + +.. option:: -fno-strict-bool[={truncate|nonzero}] + + Disable optimizations based on the assumption that all ``bool`` values, + which are typically represented as 8-bit integers in memory, only ever + contain bit patterns 0 or 1. When =truncate is specified, a ``bool`` is + true if its least significant bit is set, and false otherwise. When =nonzero + is specified, a ``bool`` is true when any bit is set, and false otherwise. + The default is =truncate. + .. option:: -fstrict-vtable-pointers Enable optimizations based on the strict rules for overwriting polymorphic diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def index a059803c433e3..6f4cc374222f7 100644 --- a/clang/include/clang/Basic/CodeGenOptions.def +++ b/clang/include/clang/Basic/CodeGenOptions.def @@ -319,6 +319,7 @@ CODEGENOPT(SoftFloat , 1, 0, Benign) ///< -soft-float. CODEGENOPT(SpeculativeLoadHardening, 1, 0, Benign) ///< Enable speculative load hardening. CODEGENOPT(FineGrainedBitfieldAccesses, 1, 0, Benign) ///< Enable fine-grained bitfield accesses. CODEGENOPT(StrictEnums , 1, 0, Benign) ///< Optimize based on strict enum definition. +ENUM_CODEGENOPT(LoadBoolFromMem, BoolFromMem, 2, BoolFromMem::Strict, Benign) ///> Optimize based on in-memory bool values being 0 or 1. CODEGENOPT(StrictVTablePointers, 1, 0, Benign) ///< Optimize based on the strict vtable pointers CODEGENOPT(TimePasses , 1, 0, Benign) ///< Set when -ftime-report, -ftime-report=, -ftime-report-json, or -stats-file-timers is enabled. CODEGENOPT(TimePassesPerRun , 1, 0, Benign) ///< Set when -ftime-report=per-pass-run is enabled. diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h index c60ca507ff917..3d050df1847dc 100644 --- a/clang/include/clang/Basic/CodeGenOptions.h +++ b/clang/include/clang/Basic/CodeGenOptions.h @@ -214,6 +214,16 @@ class CodeGenOptions : public CodeGenOptionsBase { ///< larger debug info than `Basic`. }; + enum BoolFromMem { + Strict, ///< In-memory bool values are assumed to be 0 or 1, and any other + ///< value is UB. + Truncate, ///< Convert in-memory bools to i1 by checking if the least + ///< significant bit is 1. + NonZero, ///< Convert in-memory bools to i1 by checking if any bit is set + ///< to 1. + NonStrictDefault = Truncate + }; + /// The code model to use (-mcmodel). std::string CodeModel; diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index 45c5322351a17..dfd25be63ceac 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -4243,6 +4243,27 @@ def fno_debug_macro : Flag<["-"], "fno-debug-macro">, Group<f_Group>, def fstrict_aliasing : Flag<["-"], "fstrict-aliasing">, Group<f_Group>, Visibility<[ClangOption, CLOption, DXCOption]>, HelpText<"Enable optimizations based on strict aliasing rules">; +def fstrict_bool : Flag<["-"], "fstrict-bool">, Group<f_Group>, + Visibility<[ClangOption]>, + HelpText<"Enable optimizations based on bool bit patterns never being " + "anything other than 0 or 1">; +def fno_strict_bool : Flag<["-"], "fno-strict-bool">, Group<f_Group>, + Visibility<[ClangOption]>, + HelpText<"Disable optimizations based on bool bit patterns never being " + "anything other than 0 or 1">; +def fno_strict_bool_EQ : Joined<["-"], "fno-strict-bool=">, Group<f_Group>, + Visibility<[ClangOption]>, + HelpText<"Disable optimizations based on bool bit patterns never being " + "anything other than 0 or 1, specifying a conversion behavior.">, + Values<"truncate,nonzero">; +def load_bool_from_mem : Joined<["-"], "load-bool-from-mem=">, Group<f_Group>, + Visibility<[CC1Option]>, + HelpText<"Specify how to convert a multi-bit bool loaded from memory to a " + "1-bit value">, + NormalizedValuesScope<"CodeGenOptions::BoolFromMem">, + Values<"strict,nonstrict,truncate,nonzero">, + NormalizedValues<["Strict", "NonStrictDefault", "Truncate", "NonZero"]>, + MarshallingInfoEnum<CodeGenOpts<"LoadBoolFromMem">, "Strict">; def fstrict_enums : Flag<["-"], "fstrict-enums">, Group<f_Group>, Visibility<[ClangOption, CC1Option]>, HelpText<"Enable optimizations based on the strict definition of an enum's " diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp index e511c66835dd0..c385a86e72e82 100644 --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -2040,9 +2040,9 @@ llvm::Value *CodeGenFunction::EmitLoadOfScalar(LValue lvalue, lvalue.getTBAAInfo(), lvalue.isNontemporal()); } -static bool getRangeForType(CodeGenFunction &CGF, QualType Ty, - llvm::APInt &Min, llvm::APInt &End, - bool StrictEnums, bool IsBool) { +static bool getRangeForType(CodeGenFunction &CGF, QualType Ty, llvm::APInt &Min, + llvm::APInt &End, bool StrictEnums, bool StrictBool, + bool IsBool) { const auto *ED = Ty->getAsEnumDecl(); bool IsRegularCPlusPlusEnum = CGF.getLangOpts().CPlusPlus && StrictEnums && ED && !ED->isFixed(); @@ -2050,6 +2050,8 @@ static bool getRangeForType(CodeGenFunction &CGF, QualType Ty, return false; if (IsBool) { + if (!StrictBool) + return false; Min = llvm::APInt(CGF.getContext().getTypeSize(Ty), 0); End = llvm::APInt(CGF.getContext().getTypeSize(Ty), 2); } else { @@ -2060,7 +2062,10 @@ static bool getRangeForType(CodeGenFunction &CGF, QualType Ty, llvm::MDNode *CodeGenFunction::getRangeForLoadFromType(QualType Ty) { llvm::APInt Min, End; + bool IsStrictBool = + CGM.getCodeGenOpts().getLoadBoolFromMem() == CodeGenOptions::Strict; if (!getRangeForType(*this, Ty, Min, End, CGM.getCodeGenOpts().StrictEnums, + IsStrictBool, Ty->hasBooleanRepresentation() && !Ty->isVectorType())) return nullptr; @@ -2108,7 +2113,8 @@ bool CodeGenFunction::EmitScalarRangeCheck(llvm::Value *Value, QualType Ty, return false; llvm::APInt Min, End; - if (!getRangeForType(*this, Ty, Min, End, /*StrictEnums=*/true, IsBool)) + if (!getRangeForType(*this, Ty, Min, End, /*StrictEnums=*/true, + /*StrictBool=*/true, IsBool)) return true; SanitizerKind::SanitizerOrdinal Kind = @@ -2258,8 +2264,14 @@ llvm::Value *CodeGenFunction::EmitFromMemory(llvm::Value *Value, QualType Ty) { llvm::Type *ResTy = ConvertType(Ty); if (Ty->hasBooleanRepresentation() || Ty->isBitIntType() || - Ty->isExtVectorBoolType()) - return Builder.CreateTrunc(Value, ResTy, "loadedv"); + Ty->isExtVectorBoolType()) { + if (CGM.getCodeGenOpts().getLoadBoolFromMem() == CodeGenOptions::NonZero) { + auto *NonZero = Builder.CreateICmpNE( + Value, llvm::Constant::getNullValue(Value->getType()), "loadedv.nz"); + return Builder.CreateIntCast(NonZero, ResTy, false, "loadedv"); + } else + return Builder.CreateTrunc(Value, ResTy, "loadedv"); + } return Value; } diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index 310f3b58a211e..2673bd1af596a 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -5763,6 +5763,28 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, if (!Args.hasFlag(options::OPT_fstruct_path_tbaa, options::OPT_fno_struct_path_tbaa, true)) CmdArgs.push_back("-no-struct-path-tbaa"); + + if (Arg *A = Args.getLastArg(options::OPT_fstrict_bool, + options::OPT_fno_strict_bool, + options::OPT_fno_strict_bool_EQ)) { + StringRef BFM = ""; + if (A->getOption().matches(options::OPT_fstrict_bool)) + BFM = "strict"; + else if (A->getOption().matches(options::OPT_fno_strict_bool)) + BFM = "nonstrict"; + else if (A->getValue() == StringRef("truncate")) + BFM = "truncate"; + else if (A->getValue() == StringRef("nonzero")) + BFM = "nonzero"; + else + D.Diag(diag::err_drv_invalid_value) + << A->getAsString(Args) << A->getValue(); + CmdArgs.push_back(Args.MakeArgString("-load-bool-from-mem=" + BFM)); + } else if (KernelOrKext) { + // If unspecified, assume -fno-strict-bool=truncate in the Darwin kernel. + CmdArgs.push_back("-load-bool-from-mem=truncate"); + } + Args.addOptInFlag(CmdArgs, options::OPT_fstrict_enums, options::OPT_fno_strict_enums); Args.addOptOutFlag(CmdArgs, options::OPT_fstrict_return, diff --git a/clang/test/CodeGen/strict-bool.c b/clang/test/CodeGen/strict-bool.c new file mode 100644 index 0000000000000..f95cade48d7aa --- /dev/null +++ b/clang/test/CodeGen/strict-bool.c @@ -0,0 +1,23 @@ +// RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -emit-llvm -o - %s | FileCheck %s -check-prefix=CHECK-STRICT +// RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -load-bool-from-mem=strict -emit-llvm -o - %s | FileCheck %s -check-prefix=CHECK-STRICT +// RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -load-bool-from-mem=nonstrict -emit-llvm -o - %s | FileCheck %s -check-prefix=CHECK-TRUNCATE +// RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -load-bool-from-mem=truncate -emit-llvm -o - %s | FileCheck %s -check-prefix=CHECK-TRUNCATE +// RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -load-bool-from-mem=nonzero -emit-llvm -o - %s | FileCheck %s -check-prefix=CHECK-NONZERO + +struct has_bool { + _Bool b; +}; + +int foo(struct has_bool *b) { + // CHECK-STRICT: load i8, {{.*}}, !range ![[RANGE_BOOL:[0-9]+]] + // CHECK-STRICT-NOT: and i8 + + // CHECK-TRUNCATE: [[BOOL:%.+]] = load i8 + // CHECK-TRUNCATE: and i8 [[BOOL]], 1 + + // CHECK-NONZERO: [[BOOL:%.+]] = load i8 + // CHECK-NONZERO: cmp ne i8 [[BOOL]], 0 + return b->b; +} + +// CHECK_STRICT: ![[RANGE_BOOL]] = !{i8 0, i8 2} diff --git a/clang/test/Driver/strict-bool.c b/clang/test/Driver/strict-bool.c new file mode 100644 index 0000000000000..dc1a25872324b --- /dev/null +++ b/clang/test/Driver/strict-bool.c @@ -0,0 +1,22 @@ +// RUN: %clang -### %s 2>&1 | FileCheck %s --check-prefix=CHECK-NONE +// RUN: %clang -### -fstrict-bool %s 2>&1 | FileCheck %s --check-prefix=CHECK-STRICT +// RUN: %clang -### -fno-strict-bool %s 2>&1 | FileCheck %s --check-prefix=CHECK-NONSTRICT +// RUN: %clang -### -fno-strict-bool=truncate %s 2>&1 | FileCheck %s --check-prefix=CHECK-TRUNCATE +// RUN: %clang -### -fno-strict-bool=nonzero %s 2>&1 | FileCheck %s --check-prefix=CHECK-NONZERO +// RUN: %clang -### -fstrict-bool -fno-strict-bool %s 2>&1 | FileCheck %s --check-prefix=CHECK-NONSTRICT +// RUN: %clang -### -fno-strict-bool -fno-strict-bool=nonzero %s 2>&1 | FileCheck %s --check-prefix=CHECK-NONZERO +// RUN: %clang -### -fno-strict-bool=nonzero -fstrict-bool %s 2>&1 | FileCheck %s --check-prefix=CHECK-STRICT + +// RUN: %clang -### -mkernel %s 2>&1 | FileCheck %s --check-prefix=CHECK-TRUNCATE +// RUN: %clang -### -fapple-kext %s 2>&1 | FileCheck %s --check-prefix=CHECK-TRUNCATE +// RUN: %clang -### -mkernel -fstrict-bool %s 2>&1 | FileCheck %s --check-prefix=CHECK-STRICT +// RUN: %clang -### -fstrict-bool -mkernel %s 2>&1 | FileCheck %s --check-prefix=CHECK-STRICT + +// RUN: not %clang -### -fno-strict-bool=ow-ouch %s 2>&1 | FileCheck %s --check-prefix=CHECK-INVALID + +// CHECK-NONE-NOT: -load-bool-from-mem +// CHECK-STRICT: -load-bool-from-mem=strict +// CHECK-NONSTRICT: -load-bool-from-mem=nonstrict +// CHECK-TRUNCATE: -load-bool-from-mem=truncate +// CHECK-NONZERO: -load-bool-from-mem=nonzero +// CHECK-INVALID: invalid value 'ow-ouch' in '-fno-strict-bool=ow-ouch' >From ba50d4feafa5a3bc9094ac80c035026d5617bc92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Cloutier?= <[email protected]> Date: Fri, 26 Sep 2025 12:50:10 -0700 Subject: [PATCH 2/7] * CodeGenOptions::BoolFromMem is now an ``enum class``. * Fix ``CodeGenFunction::EmitFromMemory`` breaking BitInt with -fno-strict-bool=nonzero. Add a BitInt test invocation with -fno-strict-bool=nonzero. * Add a note above ``getRangeForType`` warning against extending its functionality without introducing -fstrict flags and sanitizer options. * Add UBSan variants of the strict-bool tests showing that UBSan wins. --- clang/include/clang/Basic/CodeGenOptions.h | 2 +- clang/lib/CodeGen/CGExpr.cpp | 31 ++++++++++++++-------- clang/test/CodeGen/ext-int.c | 1 + clang/test/CodeGen/strict-bool.c | 19 ++++++++++--- 4 files changed, 38 insertions(+), 15 deletions(-) diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h index 3d050df1847dc..4d89b0353f8bd 100644 --- a/clang/include/clang/Basic/CodeGenOptions.h +++ b/clang/include/clang/Basic/CodeGenOptions.h @@ -214,7 +214,7 @@ class CodeGenOptions : public CodeGenOptionsBase { ///< larger debug info than `Basic`. }; - enum BoolFromMem { + enum class BoolFromMem { Strict, ///< In-memory bool values are assumed to be 0 or 1, and any other ///< value is UB. Truncate, ///< Convert in-memory bools to i1 by checking if the least diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp index c385a86e72e82..e85e91e09e9c4 100644 --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -2040,6 +2040,13 @@ llvm::Value *CodeGenFunction::EmitLoadOfScalar(LValue lvalue, lvalue.getTBAAInfo(), lvalue.isNontemporal()); } +// XXX: safety first! This method SHOULD NOT be extended to support additional +// types, like BitInt types, without an opt-in bool controlled by a +// CodeGenOptions setting (like -fstrict-bool) and a new UBSan check (like +// SanitizerKind::Bool) as breaking that assumption would lead to memory +// corruption. See link for examples of how having a bool that has a value +// different from 0 or 1 in memory can lead to memory corruption. +// https://discourse.llvm.org/t/defining-what-happens-when-a-bool-isn-t-0-or-1/86778 static bool getRangeForType(CodeGenFunction &CGF, QualType Ty, llvm::APInt &Min, llvm::APInt &End, bool StrictEnums, bool StrictBool, bool IsBool) { @@ -2062,8 +2069,8 @@ static bool getRangeForType(CodeGenFunction &CGF, QualType Ty, llvm::APInt &Min, llvm::MDNode *CodeGenFunction::getRangeForLoadFromType(QualType Ty) { llvm::APInt Min, End; - bool IsStrictBool = - CGM.getCodeGenOpts().getLoadBoolFromMem() == CodeGenOptions::Strict; + bool IsStrictBool = CGM.getCodeGenOpts().getLoadBoolFromMem() == + CodeGenOptions::BoolFromMem::Strict; if (!getRangeForType(*this, Ty, Min, End, CGM.getCodeGenOpts().StrictEnums, IsStrictBool, Ty->hasBooleanRepresentation() && !Ty->isVectorType())) @@ -2263,15 +2270,17 @@ llvm::Value *CodeGenFunction::EmitFromMemory(llvm::Value *Value, QualType Ty) { } llvm::Type *ResTy = ConvertType(Ty); - if (Ty->hasBooleanRepresentation() || Ty->isBitIntType() || - Ty->isExtVectorBoolType()) { - if (CGM.getCodeGenOpts().getLoadBoolFromMem() == CodeGenOptions::NonZero) { - auto *NonZero = Builder.CreateICmpNE( - Value, llvm::Constant::getNullValue(Value->getType()), "loadedv.nz"); - return Builder.CreateIntCast(NonZero, ResTy, false, "loadedv"); - } else - return Builder.CreateTrunc(Value, ResTy, "loadedv"); - } + bool IsBitInt = Ty->isBitIntType(); + bool HasBoolRep = Ty->hasBooleanRepresentation(); + if (HasBoolRep && !IsBitInt && + CGM.getCodeGenOpts().getLoadBoolFromMem() == + CodeGenOptions::BoolFromMem::NonZero) { + auto *NonZero = Builder.CreateICmpNE( + Value, llvm::Constant::getNullValue(Value->getType()), "loadedv.nz"); + return Builder.CreateIntCast(NonZero, ResTy, false, "loadedv"); + } + if (HasBoolRep || IsBitInt || Ty->isExtVectorBoolType()) + return Builder.CreateTrunc(Value, ResTy, "loadedv"); return Value; } diff --git a/clang/test/CodeGen/ext-int.c b/clang/test/CodeGen/ext-int.c index aebacd6f22ffc..157d1990c060e 100644 --- a/clang/test/CodeGen/ext-int.c +++ b/clang/test/CodeGen/ext-int.c @@ -1,4 +1,5 @@ // RUN: %clang_cc1 -std=c23 -triple x86_64-gnu-linux -O3 -disable-llvm-passes -emit-llvm -o - %s | FileCheck %s --check-prefixes=CHECK,CHECK64,LIN64 +// RUN: %clang_cc1 -std=c23 -triple x86_64-gnu-linux -O3 -load-bool-from-mem=nonzero -disable-llvm-passes -emit-llvm -o - %s | FileCheck %s --check-prefixes=CHECK,CHECK64,LIN64 // RUN: %clang_cc1 -std=c23 -triple x86_64-windows-pc -O3 -disable-llvm-passes -emit-llvm -o - %s | FileCheck %s --check-prefixes=CHECK,CHECK64,WIN64 // RUN: %clang_cc1 -std=c23 -triple i386-gnu-linux -O3 -disable-llvm-passes -emit-llvm -o - %s | FileCheck %s --check-prefixes=CHECK,LIN32 // RUN: %clang_cc1 -std=c23 -triple i386-windows-pc -O3 -disable-llvm-passes -emit-llvm -o - %s | FileCheck %s --check-prefixes=CHECK,WIN32 diff --git a/clang/test/CodeGen/strict-bool.c b/clang/test/CodeGen/strict-bool.c index f95cade48d7aa..c5dbe2dd0d7df 100644 --- a/clang/test/CodeGen/strict-bool.c +++ b/clang/test/CodeGen/strict-bool.c @@ -3,20 +3,33 @@ // RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -load-bool-from-mem=nonstrict -emit-llvm -o - %s | FileCheck %s -check-prefix=CHECK-TRUNCATE // RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -load-bool-from-mem=truncate -emit-llvm -o - %s | FileCheck %s -check-prefix=CHECK-TRUNCATE // RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -load-bool-from-mem=nonzero -emit-llvm -o - %s | FileCheck %s -check-prefix=CHECK-NONZERO +// RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -fsanitize=bool -load-bool-from-mem=strict -emit-llvm -o - %s | FileCheck %s -check-prefix=CHECK-UBSAN-STRICT +// RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -fsanitize=bool -load-bool-from-mem=truncate -emit-llvm -o - %s | FileCheck %s -check-prefix=CHECK-UBSAN-TRUNCATE struct has_bool { _Bool b; }; int foo(struct has_bool *b) { - // CHECK-STRICT: load i8, {{.*}}, !range ![[RANGE_BOOL:[0-9]+]] - // CHECK-STRICT-NOT: and i8 + // CHECK-STRICT: [[BOOL:%.+]] = load i8, ptr {{.+}}, !range ![[RANGE_BOOL:[0-9]+]] + // CHECK-STRICT-NOT: and i8 [[BOOL]], 1 + // CHECK-STRICT-NOT: icmp ne i8 [[BOOL]], 0 + // CHECK-TRUNCATE-NOT: !range // CHECK-TRUNCATE: [[BOOL:%.+]] = load i8 // CHECK-TRUNCATE: and i8 [[BOOL]], 1 + // CHECK-NONZERO-NOT: !range // CHECK-NONZERO: [[BOOL:%.+]] = load i8 - // CHECK-NONZERO: cmp ne i8 [[BOOL]], 0 + // CHECK-NONZERO: icmp ne i8 [[BOOL]], 0 + + // CHECK-UBSAN-STRICT-NOT: !range + // CHECK-UBSAN-STRICT: [[BOOL:%.+]] = load i8, ptr {{.+}} + // CHECK-UBSAN-STRICT: icmp ult i8 [[BOOL]], 2 + + // CHECK-UBSAN-TRUNCATE-NOT: !range + // CHECK-UBSAN-TRUNCATE: [[BOOL:%.+]] = load i8, ptr {{.+}} + // CHECK-UBSAN-TRUNCATE: icmp ult i8 [[BOOL]], 2 return b->b; } >From 91fa717134d855e13ffb9dd2a7904324cb9b7f5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Cloutier?= <[email protected]> Date: Mon, 29 Sep 2025 18:38:30 -0700 Subject: [PATCH 3/7] Review feedback --- clang/docs/UsersManual.rst | 26 +++++++++++++-------- clang/lib/CodeGen/CGExpr.cpp | 11 ++++----- clang/test/CodeGen/ext-int.c | 1 - clang/test/CodeGen/strict-bool.c | 40 ++++++++++++++++++++++++++------ 4 files changed, 53 insertions(+), 25 deletions(-) diff --git a/clang/docs/UsersManual.rst b/clang/docs/UsersManual.rst index b9d4e857d5159..b65ce7a3c071a 100644 --- a/clang/docs/UsersManual.rst +++ b/clang/docs/UsersManual.rst @@ -2323,21 +2323,27 @@ are listed below. .. option:: -fstrict-bool - ``bool`` values are stored to memory as 8-bit values on most targets. Under - ``-fstrict-bool``, it is undefined behavior for a ``bool`` value stored in - memory to have any other bit pattern than 0 or 1. This creates some - optimization opportunities for the compiler, but it enables memory - corruption if that assumption is violated, for instance if any other value - is ``memcpy``ed over a ``bool``. This is enabled by default. + ``bool`` values are stored to memory as 8-bit values on most targets. C and + C++ specify that it is undefined behavior to put a value other than 0 or 1 + in the storage of a ``bool`` value, and with ``-fstrict-bool``, Clang + leverages this knowledge for optimization opportunities. When this + assumption is violated, for instance if invalid data is ``memcpy``ed over a + ``bool``, the optimized code can lead to memory corruption. + ``-fstrict-bool`` is enabled by default. .. option:: -fno-strict-bool[={truncate|nonzero}] Disable optimizations based on the assumption that all ``bool`` values, which are typically represented as 8-bit integers in memory, only ever - contain bit patterns 0 or 1. When =truncate is specified, a ``bool`` is - true if its least significant bit is set, and false otherwise. When =nonzero - is specified, a ``bool`` is true when any bit is set, and false otherwise. - The default is =truncate. + contain bit patterns 0 or 1. When ``=truncate`` is specified, a ``bool`` is + true if its least significant bit is set, and false otherwise. When + ``=nonzero`` is specified, a ``bool`` is true when any bit is set, and + false otherwise. The default is ``=truncate``, but this could change in + future releases. + + ``-fno-strict-bool`` does not permit Clang to store a value other than 0 or + 1 in a ``bool``: it is a safety net against programmer mistakes, such as + ``memcpy``ing invalid data over a ``bool``. .. option:: -fstrict-vtable-pointers diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp index e85e91e09e9c4..78a3c47362bdf 100644 --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -2270,16 +2270,13 @@ llvm::Value *CodeGenFunction::EmitFromMemory(llvm::Value *Value, QualType Ty) { } llvm::Type *ResTy = ConvertType(Ty); - bool IsBitInt = Ty->isBitIntType(); bool HasBoolRep = Ty->hasBooleanRepresentation(); - if (HasBoolRep && !IsBitInt && - CGM.getCodeGenOpts().getLoadBoolFromMem() == + if (HasBoolRep && CGM.getCodeGenOpts().getLoadBoolFromMem() == CodeGenOptions::BoolFromMem::NonZero) { - auto *NonZero = Builder.CreateICmpNE( - Value, llvm::Constant::getNullValue(Value->getType()), "loadedv.nz"); - return Builder.CreateIntCast(NonZero, ResTy, false, "loadedv"); + return Builder.CreateICmpNE( + Value, llvm::Constant::getNullValue(Value->getType()), "loadedv"); } - if (HasBoolRep || IsBitInt || Ty->isExtVectorBoolType()) + if (HasBoolRep || Ty->isBitIntType() || Ty->isExtVectorBoolType()) return Builder.CreateTrunc(Value, ResTy, "loadedv"); return Value; diff --git a/clang/test/CodeGen/ext-int.c b/clang/test/CodeGen/ext-int.c index 157d1990c060e..aebacd6f22ffc 100644 --- a/clang/test/CodeGen/ext-int.c +++ b/clang/test/CodeGen/ext-int.c @@ -1,5 +1,4 @@ // RUN: %clang_cc1 -std=c23 -triple x86_64-gnu-linux -O3 -disable-llvm-passes -emit-llvm -o - %s | FileCheck %s --check-prefixes=CHECK,CHECK64,LIN64 -// RUN: %clang_cc1 -std=c23 -triple x86_64-gnu-linux -O3 -load-bool-from-mem=nonzero -disable-llvm-passes -emit-llvm -o - %s | FileCheck %s --check-prefixes=CHECK,CHECK64,LIN64 // RUN: %clang_cc1 -std=c23 -triple x86_64-windows-pc -O3 -disable-llvm-passes -emit-llvm -o - %s | FileCheck %s --check-prefixes=CHECK,CHECK64,WIN64 // RUN: %clang_cc1 -std=c23 -triple i386-gnu-linux -O3 -disable-llvm-passes -emit-llvm -o - %s | FileCheck %s --check-prefixes=CHECK,LIN32 // RUN: %clang_cc1 -std=c23 -triple i386-windows-pc -O3 -disable-llvm-passes -emit-llvm -o - %s | FileCheck %s --check-prefixes=CHECK,WIN32 diff --git a/clang/test/CodeGen/strict-bool.c b/clang/test/CodeGen/strict-bool.c index c5dbe2dd0d7df..f8894345f46db 100644 --- a/clang/test/CodeGen/strict-bool.c +++ b/clang/test/CodeGen/strict-bool.c @@ -1,15 +1,17 @@ -// RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -emit-llvm -o - %s | FileCheck %s -check-prefix=CHECK-STRICT -// RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -load-bool-from-mem=strict -emit-llvm -o - %s | FileCheck %s -check-prefix=CHECK-STRICT -// RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -load-bool-from-mem=nonstrict -emit-llvm -o - %s | FileCheck %s -check-prefix=CHECK-TRUNCATE -// RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -load-bool-from-mem=truncate -emit-llvm -o - %s | FileCheck %s -check-prefix=CHECK-TRUNCATE -// RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -load-bool-from-mem=nonzero -emit-llvm -o - %s | FileCheck %s -check-prefix=CHECK-NONZERO -// RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -fsanitize=bool -load-bool-from-mem=strict -emit-llvm -o - %s | FileCheck %s -check-prefix=CHECK-UBSAN-STRICT -// RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -fsanitize=bool -load-bool-from-mem=truncate -emit-llvm -o - %s | FileCheck %s -check-prefix=CHECK-UBSAN-TRUNCATE +// RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -emit-llvm -o - %s | FileCheck %s -check-prefixes=CHECK,CHECK-STRICT +// RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -load-bool-from-mem=strict -emit-llvm -o - %s | FileCheck %s -check-prefixes=CHECK,CHECK-STRICT +// RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -load-bool-from-mem=nonstrict -emit-llvm -o - %s | FileCheck %s -check-prefixes=CHECK,CHECK-TRUNCATE +// RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -load-bool-from-mem=truncate -emit-llvm -o - %s | FileCheck %s -check-prefixes=CHECK,CHECK-TRUNCATE +// RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -load-bool-from-mem=nonzero -emit-llvm -o - %s | FileCheck %s -check-prefixes=CHECK,CHECK-NONZERO +// RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -fsanitize=bool -load-bool-from-mem=strict -emit-llvm -o - %s | FileCheck %s -check-prefixes=CHECK,CHECK-UBSAN-STRICT +// RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -fsanitize=bool -load-bool-from-mem=truncate -emit-llvm -o - %s | FileCheck %s -check-prefixes=CHECK,CHECK-UBSAN-TRUNCATE struct has_bool { _Bool b; + unsigned _BitInt(1) c; }; +// CHECK: @foo int foo(struct has_bool *b) { // CHECK-STRICT: [[BOOL:%.+]] = load i8, ptr {{.+}}, !range ![[RANGE_BOOL:[0-9]+]] // CHECK-STRICT-NOT: and i8 [[BOOL]], 1 @@ -33,4 +35,28 @@ int foo(struct has_bool *b) { return b->b; } +// CHECK: @bar +int bar(struct has_bool *c) { + // CHECK-STRICT: [[BITINT:%.+]] = load i8, ptr {{.+}}, !range ![[RANGE_BOOL:[0-9]+]] + // CHECK-STRICT-NOT: and i8 [[BITINT]], 1 + // CHECK-STRICT-NOT: icmp ne i8 [[BITINT]], 0 + + // CHECK-TRUNCATE-NOT: !range + // CHECK-TRUNCATE: [[BITINT:%.+]] = load i8 + // CHECK-TRUNCATE: and i8 [[BITINT]], 1 + + // CHECK-NONZERO-NOT: !range + // CHECK-NONZERO: [[BITINT:%.+]] = load i8 + // CHECK-NONZERO: icmp ne i8 [[BITINT]], 0 + + // CHECK-UBSAN-STRICT-NOT: !range + // CHECK-UBSAN-STRICT: [[BITINT:%.+]] = load i8, ptr {{.+}} + // CHECK-UBSAN-STRICT: icmp ult i8 [[BITINT]], 2 + + // CHECK-UBSAN-TRUNCATE-NOT: !range + // CHECK-UBSAN-TRUNCATE: [[BITINT:%.+]] = load i8, ptr {{.+}} + // CHECK-UBSAN-TRUNCATE: icmp ult i8 [[BITINT]], 2 + return c->c; +} + // CHECK_STRICT: ![[RANGE_BOOL]] = !{i8 0, i8 2} >From 2d6428b3500fbf42807b808ed0009155788e7837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Cloutier?= <[email protected]> Date: Mon, 29 Sep 2025 18:40:17 -0700 Subject: [PATCH 4/7] (clang-format) --- clang/lib/CodeGen/CGExpr.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp index 78a3c47362bdf..5dffce5c8229d 100644 --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -2272,7 +2272,7 @@ llvm::Value *CodeGenFunction::EmitFromMemory(llvm::Value *Value, QualType Ty) { llvm::Type *ResTy = ConvertType(Ty); bool HasBoolRep = Ty->hasBooleanRepresentation(); if (HasBoolRep && CGM.getCodeGenOpts().getLoadBoolFromMem() == - CodeGenOptions::BoolFromMem::NonZero) { + CodeGenOptions::BoolFromMem::NonZero) { return Builder.CreateICmpNE( Value, llvm::Constant::getNullValue(Value->getType()), "loadedv"); } >From c0a38538cb3db6ee918b4619eca06f47666bcc41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Cloutier?= <[email protected]> Date: Mon, 8 Dec 2025 10:55:41 +0100 Subject: [PATCH 5/7] Address review feedback: * Update release notes to clarify -fstrict-bool (the existing behavior) remains the default * Update user manual to clarify that -fno-strict-bool does not cancel -fsanitize=bool * Change default non-strict mode to be NonZero * Update tests and comments --- clang/docs/ReleaseNotes.rst | 3 ++- clang/docs/UsersManual.rst | 22 +++++++++++++-------- clang/include/clang/Basic/CodeGenOptions.h | 2 +- clang/lib/CodeGen/CGExpr.cpp | 23 +++++++++++----------- clang/test/CodeGen/strict-bool.c | 2 +- clang/test/Driver/strict-bool.c | 10 ++++++++-- 6 files changed, 38 insertions(+), 24 deletions(-) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 8ac6e5faf622e..4679b159f7a47 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -342,7 +342,8 @@ New Compiler Flags - New option ``-fsanitize-debug-trap-reasons=`` added to control emitting trap reasons into the debug info when compiling with trapping UBSan (e.g. ``-fsanitize-trap=undefined``). - New options for enabling allocation token instrumentation: ``-fsanitize=alloc-token``, ``-falloc-token-max=``, ``-fsanitize-alloc-token-fast-abi``, ``-fsanitize-alloc-token-extended``. - The ``-resource-dir`` option is now displayed in the list of options shown by ``--help``. -- New option ``-f[no-]strict-bool`` added to control whether Clang can assume that ``bool`` values loaded from memory cannot have a bit pattern other than 0 or 1. +- New option ``-f[no-]strict-bool`` added to control whether Clang can assume that ``bool`` values (stored as whole bytes in memory) can only contain the values 0 and 1. + Clang's previous behavior (making that assumption) is codified as ``-fstrict-bool`` and remains the default. - New option ``-fmatrix-memory-layout`` added to control the memory layout of Clang matrix types. (e.g. ``-fmatrix-memory-layout=column-major`` or ``-fmatrix-memory-layout=row-major``). Lanai Support diff --git a/clang/docs/UsersManual.rst b/clang/docs/UsersManual.rst index b65ce7a3c071a..f25a1f0585624 100644 --- a/clang/docs/UsersManual.rst +++ b/clang/docs/UsersManual.rst @@ -2335,15 +2335,21 @@ are listed below. Disable optimizations based on the assumption that all ``bool`` values, which are typically represented as 8-bit integers in memory, only ever - contain bit patterns 0 or 1. When ``=truncate`` is specified, a ``bool`` is - true if its least significant bit is set, and false otherwise. When + contain bit patterns 0 or 1. When ``=truncate`` is specified, a ``bool`` + is true if its least significant bit is set, and false otherwise. When ``=nonzero`` is specified, a ``bool`` is true when any bit is set, and - false otherwise. The default is ``=truncate``, but this could change in - future releases. - - ``-fno-strict-bool`` does not permit Clang to store a value other than 0 or - 1 in a ``bool``: it is a safety net against programmer mistakes, such as - ``memcpy``ing invalid data over a ``bool``. + false otherwise. The default is ``=nonzero``. + + ``-fno-strict-bool`` does not permit developers to store a value other + than 0 or 1 in a ``bool``: it is a safety net against mistakes, such as + ``memcpy``ing invalid data over a ``bool``. Using invalid ``bool`` bit + patterns is still undefined behavior, even as this option limits the + negative consequences. In particular, enabling the UBSan + ``-fsanitize=bool`` check will continue to trap for invalid ``bool`` + values when ``-fno-strict-bool`` is also specified, and program parts + that were compiled without ``-fno-strict-bool`` (or by different + compilers that have no equivalent option) will continue to behave + erratically. .. option:: -fstrict-vtable-pointers diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h index 4d89b0353f8bd..4d39048e7c803 100644 --- a/clang/include/clang/Basic/CodeGenOptions.h +++ b/clang/include/clang/Basic/CodeGenOptions.h @@ -221,7 +221,7 @@ class CodeGenOptions : public CodeGenOptionsBase { ///< significant bit is 1. NonZero, ///< Convert in-memory bools to i1 by checking if any bit is set ///< to 1. - NonStrictDefault = Truncate + NonStrictDefault = NonZero }; /// The code model to use (-mcmodel). diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp index 5dffce5c8229d..5ff3c74039438 100644 --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -2040,12 +2040,12 @@ llvm::Value *CodeGenFunction::EmitLoadOfScalar(LValue lvalue, lvalue.getTBAAInfo(), lvalue.isNontemporal()); } -// XXX: safety first! This method SHOULD NOT be extended to support additional -// types, like BitInt types, without an opt-in bool controlled by a -// CodeGenOptions setting (like -fstrict-bool) and a new UBSan check (like -// SanitizerKind::Bool) as breaking that assumption would lead to memory -// corruption. See link for examples of how having a bool that has a value -// different from 0 or 1 in memory can lead to memory corruption. +// This method SHOULD NOT be extended to support additional types, like BitInt +// types, without an opt-in bool controlled by a CodeGenOptions setting (like +// -fstrict-bool) and a new UBSan check (like SanitizerKind::Bool) as breaking +// that assumption would lead to memory corruption. See link for examples of how +// having a bool that has a value different from 0 or 1 in memory can lead to +// memory corruption. // https://discourse.llvm.org/t/defining-what-happens-when-a-bool-isn-t-0-or-1/86778 static bool getRangeForType(CodeGenFunction &CGF, QualType Ty, llvm::APInt &Min, llvm::APInt &End, bool StrictEnums, bool StrictBool, @@ -2069,11 +2069,12 @@ static bool getRangeForType(CodeGenFunction &CGF, QualType Ty, llvm::APInt &Min, llvm::MDNode *CodeGenFunction::getRangeForLoadFromType(QualType Ty) { llvm::APInt Min, End; - bool IsStrictBool = CGM.getCodeGenOpts().getLoadBoolFromMem() == - CodeGenOptions::BoolFromMem::Strict; - if (!getRangeForType(*this, Ty, Min, End, CGM.getCodeGenOpts().StrictEnums, - IsStrictBool, - Ty->hasBooleanRepresentation() && !Ty->isVectorType())) + bool IsBool = Ty->hasBooleanRepresentation() && !Ty->isVectorType(); + bool StrictBoolEnabled = CGM.getCodeGenOpts().getLoadBoolFromMem() == + CodeGenOptions::BoolFromMem::Strict; + if (!getRangeForType(*this, Ty, Min, End, + /*StrictEnums=*/CGM.getCodeGenOpts().StrictEnums, + /*StrictBool=*/StrictBoolEnabled, /*IsBool=*/IsBool)) return nullptr; llvm::MDBuilder MDHelper(getLLVMContext()); diff --git a/clang/test/CodeGen/strict-bool.c b/clang/test/CodeGen/strict-bool.c index f8894345f46db..b428a81a5cb13 100644 --- a/clang/test/CodeGen/strict-bool.c +++ b/clang/test/CodeGen/strict-bool.c @@ -1,6 +1,6 @@ // RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -emit-llvm -o - %s | FileCheck %s -check-prefixes=CHECK,CHECK-STRICT // RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -load-bool-from-mem=strict -emit-llvm -o - %s | FileCheck %s -check-prefixes=CHECK,CHECK-STRICT -// RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -load-bool-from-mem=nonstrict -emit-llvm -o - %s | FileCheck %s -check-prefixes=CHECK,CHECK-TRUNCATE +// RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -load-bool-from-mem=nonstrict -emit-llvm -o - %s | FileCheck %s -check-prefixes=CHECK,CHECK-NONZERO // RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -load-bool-from-mem=truncate -emit-llvm -o - %s | FileCheck %s -check-prefixes=CHECK,CHECK-TRUNCATE // RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -load-bool-from-mem=nonzero -emit-llvm -o - %s | FileCheck %s -check-prefixes=CHECK,CHECK-NONZERO // RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -fsanitize=bool -load-bool-from-mem=strict -emit-llvm -o - %s | FileCheck %s -check-prefixes=CHECK,CHECK-UBSAN-STRICT diff --git a/clang/test/Driver/strict-bool.c b/clang/test/Driver/strict-bool.c index dc1a25872324b..0106d35330d11 100644 --- a/clang/test/Driver/strict-bool.c +++ b/clang/test/Driver/strict-bool.c @@ -12,11 +12,17 @@ // RUN: %clang -### -mkernel -fstrict-bool %s 2>&1 | FileCheck %s --check-prefix=CHECK-STRICT // RUN: %clang -### -fstrict-bool -mkernel %s 2>&1 | FileCheck %s --check-prefix=CHECK-STRICT -// RUN: not %clang -### -fno-strict-bool=ow-ouch %s 2>&1 | FileCheck %s --check-prefix=CHECK-INVALID +// RUN: not %clang -### -fno-strict-bool= %s 2>&1 | FileCheck %s --check-prefix=CHECK-INVALID-FNO +// RUN: not %clang -### -fno-strict-bool=ow-ouch %s 2>&1 | FileCheck %s --check-prefix=CHECK-INVALID-FNO +// RUN: not %clang -### -fstrict-bool= %s 2>&1 | FileCheck %s --check-prefix=CHECK-INVALID +// RUN: not %clang -### -fstrict-bool=ow-ouch %s 2>&1 | FileCheck %s --check-prefix=CHECK-INVALID +// RUN: not %clang -### -fstrict-bool=truncate %s 2>&1 | FileCheck %s --check-prefix=CHECK-INVALID +// RUN: not %clang -### -fstrict-bool=nonzero %s 2>&1 | FileCheck %s --check-prefix=CHECK-INVALID // CHECK-NONE-NOT: -load-bool-from-mem // CHECK-STRICT: -load-bool-from-mem=strict // CHECK-NONSTRICT: -load-bool-from-mem=nonstrict // CHECK-TRUNCATE: -load-bool-from-mem=truncate // CHECK-NONZERO: -load-bool-from-mem=nonzero -// CHECK-INVALID: invalid value 'ow-ouch' in '-fno-strict-bool=ow-ouch' +// CHECK-INVALID: unknown argument{{:?}} '-fstrict-bool={{.*}}' +// CHECK-INVALID-FNO: invalid value '{{.*}}' in '-f{{(no-)?}}strict-bool={{.*}}' >From c93f0b5fdc0fc687298f272c1d5c8b52dae01cfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Cloutier?= <[email protected]> Date: Mon, 15 Dec 2025 19:55:36 +0000 Subject: [PATCH 6/7] Fix strict bool effect on unpacked vector loads (HLSL only), reorganize logic to decide if converting with nonzero method --- clang/lib/CodeGen/CGExpr.cpp | 28 +++++++++++++++++-------- clang/test/CodeGen/strict-bool.c | 14 +++++++++++-- clang/test/CodeGenHLSL/strict-bool.hlsl | 14 +++++++++++++ 3 files changed, 45 insertions(+), 11 deletions(-) create mode 100644 clang/test/CodeGenHLSL/strict-bool.hlsl diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp index 5ff3c74039438..4eb1ed6e8f32c 100644 --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -2067,6 +2067,17 @@ static bool getRangeForType(CodeGenFunction &CGF, QualType Ty, llvm::APInt &Min, return true; } +static bool IsOptimizedBuild(const CodeGenOptions &CGOpt) { + return CGOpt.OptimizationLevel > 0; +} + +static bool ConvertBoolByCmpZero(const CodeGenOptions &CGOpt) { + if (CGOpt.getLoadBoolFromMem() == CodeGenOptions::BoolFromMem::NonZero) + return true; + + return false; +} + llvm::MDNode *CodeGenFunction::getRangeForLoadFromType(QualType Ty) { llvm::APInt Min, End; bool IsBool = Ty->hasBooleanRepresentation() && !Ty->isVectorType(); @@ -2086,7 +2097,7 @@ void CodeGenFunction::maybeAttachRangeForLoad(llvm::LoadInst *Load, QualType Ty, if (EmitScalarRangeCheck(Load, Ty, Loc)) { // In order to prevent the optimizer from throwing away the check, don't // attach range metadata to the load. - } else if (CGM.getCodeGenOpts().OptimizationLevel > 0) { + } else if (IsOptimizedBuild(CGM.getCodeGenOpts())) { if (llvm::MDNode *RangeInfo = getRangeForLoadFromType(Ty)) { Load->setMetadata(llvm::LLVMContext::MD_range, RangeInfo); Load->setMetadata(llvm::LLVMContext::MD_noundef, @@ -2271,13 +2282,12 @@ llvm::Value *CodeGenFunction::EmitFromMemory(llvm::Value *Value, QualType Ty) { } llvm::Type *ResTy = ConvertType(Ty); - bool HasBoolRep = Ty->hasBooleanRepresentation(); - if (HasBoolRep && CGM.getCodeGenOpts().getLoadBoolFromMem() == - CodeGenOptions::BoolFromMem::NonZero) { + bool HasBoolRep = Ty->hasBooleanRepresentation() || Ty->isExtVectorBoolType(); + if (HasBoolRep && ConvertBoolByCmpZero(CGM.getCodeGenOpts())) { return Builder.CreateICmpNE( Value, llvm::Constant::getNullValue(Value->getType()), "loadedv"); } - if (HasBoolRep || Ty->isBitIntType() || Ty->isExtVectorBoolType()) + if (HasBoolRep || Ty->isBitIntType()) return Builder.CreateTrunc(Value, ResTy, "loadedv"); return Value; @@ -2472,7 +2482,7 @@ RValue CodeGenFunction::EmitLoadOfLValue(LValue LV, SourceLocation Loc) { if (LV.isMatrixElt()) { llvm::Value *Idx = LV.getMatrixIdx(); - if (CGM.getCodeGenOpts().OptimizationLevel > 0) { + if (IsOptimizedBuild(CGM.getCodeGenOpts())) { const auto *const MatTy = LV.getType()->castAs<ConstantMatrixType>(); llvm::MatrixBuilder MB(Builder); MB.CreateIndexAssumption(Idx, MatTy->getNumElementsFlattened()); @@ -2690,7 +2700,7 @@ void CodeGenFunction::EmitStoreThroughLValue(RValue Src, LValue Dst, if (Dst.isMatrixElt()) { llvm::Value *Idx = Dst.getMatrixIdx(); - if (CGM.getCodeGenOpts().OptimizationLevel > 0) { + if (IsOptimizedBuild(CGM.getCodeGenOpts())) { const auto *const MatTy = Dst.getType()->castAs<ConstantMatrixType>(); llvm::MatrixBuilder MB(Builder); MB.CreateIndexAssumption(Idx, MatTy->getNumElementsFlattened()); @@ -3920,7 +3930,7 @@ static void emitCheckHandlerCall(CodeGenFunction &CGF, llvm::AttributeList::FunctionIndex, B), /*Local=*/true); llvm::CallInst *HandlerCall = CGF.EmitNounwindRuntimeCall(Fn, FnArgs); - NoMerge = NoMerge || !CGF.CGM.getCodeGenOpts().OptimizationLevel || + NoMerge = NoMerge || !IsOptimizedBuild(CGF.CGM.getCodeGenOpts()) || (CGF.CurCodeDecl && CGF.CurCodeDecl->hasAttr<OptimizeNoneAttr>()); if (NoMerge) HandlerCall->addFnAttr(llvm::Attribute::NoMerge); @@ -4312,7 +4322,7 @@ void CodeGenFunction::EmitTrapCheck(llvm::Value *Checked, TrapLocation, TrapCategory, TrapMessage); } - NoMerge = NoMerge || !CGM.getCodeGenOpts().OptimizationLevel || + NoMerge = NoMerge || !IsOptimizedBuild(CGM.getCodeGenOpts()) || (CurCodeDecl && CurCodeDecl->hasAttr<OptimizeNoneAttr>()); llvm::MDBuilder MDHelper(getLLVMContext()); diff --git a/clang/test/CodeGen/strict-bool.c b/clang/test/CodeGen/strict-bool.c index b428a81a5cb13..b2a0f36b39ae1 100644 --- a/clang/test/CodeGen/strict-bool.c +++ b/clang/test/CodeGen/strict-bool.c @@ -6,12 +6,15 @@ // RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -fsanitize=bool -load-bool-from-mem=strict -emit-llvm -o - %s | FileCheck %s -check-prefixes=CHECK,CHECK-UBSAN-STRICT // RUN: %clang_cc1 -triple armv7-apple-darwin -O1 -fsanitize=bool -load-bool-from-mem=truncate -emit-llvm -o - %s | FileCheck %s -check-prefixes=CHECK,CHECK-UBSAN-TRUNCATE +typedef _Bool bool4_t __attribute__((ext_vector_type(4))); + struct has_bool { _Bool b; unsigned _BitInt(1) c; + bool4_t v; }; -// CHECK: @foo +// CHECK-LABEL: @foo int foo(struct has_bool *b) { // CHECK-STRICT: [[BOOL:%.+]] = load i8, ptr {{.+}}, !range ![[RANGE_BOOL:[0-9]+]] // CHECK-STRICT-NOT: and i8 [[BOOL]], 1 @@ -35,7 +38,7 @@ int foo(struct has_bool *b) { return b->b; } -// CHECK: @bar +// CHECK-LABEL: @bar int bar(struct has_bool *c) { // CHECK-STRICT: [[BITINT:%.+]] = load i8, ptr {{.+}}, !range ![[RANGE_BOOL:[0-9]+]] // CHECK-STRICT-NOT: and i8 [[BITINT]], 1 @@ -59,4 +62,11 @@ int bar(struct has_bool *c) { return c->c; } +// CHECK-LABEL: @vec +bool4_t vec(struct has_bool *c) { + // CHECK: [[BITS:%.+]] = load <{{[0-9]+}} x i1> + return c->v; +} + + // CHECK_STRICT: ![[RANGE_BOOL]] = !{i8 0, i8 2} diff --git a/clang/test/CodeGenHLSL/strict-bool.hlsl b/clang/test/CodeGenHLSL/strict-bool.hlsl new file mode 100644 index 0000000000000..deccf6ccf0786 --- /dev/null +++ b/clang/test/CodeGenHLSL/strict-bool.hlsl @@ -0,0 +1,14 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -O1 -load-bool-from-mem=truncate -emit-llvm -o - %s | FileCheck %s -check-prefixes=CHECK-TRUNCATE +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -O1 -load-bool-from-mem=nonzero -emit-llvm -o - %s | FileCheck %s -check-prefixes=CHECK-NONZERO + +typedef bool bool8_t __attribute__((ext_vector_type(8))); +extern bool8_t vec; + +bool8_t getvec(void) { + // CHECK-TRUNCATE: [[BOOL:%.+]] = load <8 x i32> + // CHECK-TRUNCATE: trunc <8 x i32> [[BOOL]] to <8 x i1> + + // CHECK-NONZERO: [[BOOL:%.+]] = load <8 x i32> + // CHECK-NONZERO: icmp ne <8 x i32> [[BOOL]], zeroinitializer + return vec; +} >From 91aeaad6de6a9d963cc503fff3b146ec1c660b0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Cloutier?= <[email protected]> Date: Thu, 18 Dec 2025 15:01:15 +0000 Subject: [PATCH 7/7] Trace down HLSL and ObjC cases loading booleans (without range metadata) that would not check the -fno-strict-bool mode --- clang/include/clang/Basic/CodeGenOptions.h | 18 ++ clang/lib/CodeGen/CGExpr.cpp | 39 ++-- clang/lib/CodeGen/CGObjC.cpp | 5 +- .../CodeGenHLSL/builtins/BoolSwizzles.hlsl | 176 ++++++++++++++++++ .../CodeGenHLSL/builtins/ScalarSwizzles.hlsl | 160 ---------------- 5 files changed, 216 insertions(+), 182 deletions(-) create mode 100644 clang/test/CodeGenHLSL/builtins/BoolSwizzles.hlsl diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h index 4d39048e7c803..3dab0f9585c66 100644 --- a/clang/include/clang/Basic/CodeGenOptions.h +++ b/clang/include/clang/Basic/CodeGenOptions.h @@ -670,6 +670,24 @@ class CodeGenOptions : public CodeGenOptionsBase { bool isLoaderReplaceableFunctionName(StringRef FuncName) const { return llvm::is_contained(LoaderReplaceableFunctionNames, FuncName); } + + /// Are we building at -O1 or higher? + bool isOptimizedBuild() const { + return OptimizationLevel > 0; + } + + /// When loading a bool from a storage unit larger than i1, should it + /// be converted to i1 by comparing to 0 or by truncating to i1? + bool isConvertingBoolWithCmp0() const { + switch (getLoadBoolFromMem()) { + case BoolFromMem::Strict: + case BoolFromMem::Truncate: + return false; + + case BoolFromMem::NonZero: + return true; + } + } }; } // end namespace clang diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp index 4eb1ed6e8f32c..7c63a56126dcc 100644 --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -2067,17 +2067,6 @@ static bool getRangeForType(CodeGenFunction &CGF, QualType Ty, llvm::APInt &Min, return true; } -static bool IsOptimizedBuild(const CodeGenOptions &CGOpt) { - return CGOpt.OptimizationLevel > 0; -} - -static bool ConvertBoolByCmpZero(const CodeGenOptions &CGOpt) { - if (CGOpt.getLoadBoolFromMem() == CodeGenOptions::BoolFromMem::NonZero) - return true; - - return false; -} - llvm::MDNode *CodeGenFunction::getRangeForLoadFromType(QualType Ty) { llvm::APInt Min, End; bool IsBool = Ty->hasBooleanRepresentation() && !Ty->isVectorType(); @@ -2097,7 +2086,7 @@ void CodeGenFunction::maybeAttachRangeForLoad(llvm::LoadInst *Load, QualType Ty, if (EmitScalarRangeCheck(Load, Ty, Loc)) { // In order to prevent the optimizer from throwing away the check, don't // attach range metadata to the load. - } else if (IsOptimizedBuild(CGM.getCodeGenOpts())) { + } else if (CGM.getCodeGenOpts().isOptimizedBuild()) { if (llvm::MDNode *RangeInfo = getRangeForLoadFromType(Ty)) { Load->setMetadata(llvm::LLVMContext::MD_range, RangeInfo); Load->setMetadata(llvm::LLVMContext::MD_noundef, @@ -2283,7 +2272,7 @@ llvm::Value *CodeGenFunction::EmitFromMemory(llvm::Value *Value, QualType Ty) { llvm::Type *ResTy = ConvertType(Ty); bool HasBoolRep = Ty->hasBooleanRepresentation() || Ty->isExtVectorBoolType(); - if (HasBoolRep && ConvertBoolByCmpZero(CGM.getCodeGenOpts())) { + if (HasBoolRep && CGM.getCodeGenOpts().isConvertingBoolWithCmp0()) { return Builder.CreateICmpNE( Value, llvm::Constant::getNullValue(Value->getType()), "loadedv"); } @@ -2482,7 +2471,7 @@ RValue CodeGenFunction::EmitLoadOfLValue(LValue LV, SourceLocation Loc) { if (LV.isMatrixElt()) { llvm::Value *Idx = LV.getMatrixIdx(); - if (IsOptimizedBuild(CGM.getCodeGenOpts())) { + if (CGM.getCodeGenOpts().isOptimizedBuild()) { const auto *const MatTy = LV.getType()->castAs<ConstantMatrixType>(); llvm::MatrixBuilder MB(Builder); MB.CreateIndexAssumption(Idx, MatTy->getNumElementsFlattened()); @@ -2558,8 +2547,12 @@ RValue CodeGenFunction::EmitLoadOfExtVectorElementLValue(LValue LV) { llvm::Type *LVTy = ConvertType(LV.getType()); if (Element->getType()->getPrimitiveSizeInBits() > - LVTy->getPrimitiveSizeInBits()) - Element = Builder.CreateTrunc(Element, LVTy); + LVTy->getPrimitiveSizeInBits()) { + if (LV.getType()->hasBooleanRepresentation() && CGM.getCodeGenOpts().isConvertingBoolWithCmp0()) + Element = Builder.CreateICmpNE(Element, llvm::Constant::getNullValue(Element->getType())); + else + Element = Builder.CreateTrunc(Element, LVTy); + } return RValue::get(Element); } @@ -2573,8 +2566,12 @@ RValue CodeGenFunction::EmitLoadOfExtVectorElementLValue(LValue LV) { Vec = Builder.CreateShuffleVector(Vec, Mask); - if (LV.getType()->isExtVectorBoolType()) - Vec = Builder.CreateTrunc(Vec, ConvertType(LV.getType()), "truncv"); + if (LV.getType()->isExtVectorBoolType()) { + if (CGM.getCodeGenOpts().isConvertingBoolWithCmp0()) + Vec = Builder.CreateICmpNE(Vec, llvm::Constant::getNullValue(Vec->getType())); + else + Vec = Builder.CreateTrunc(Vec, ConvertType(LV.getType()), "truncv"); + } return RValue::get(Vec); } @@ -2700,7 +2697,7 @@ void CodeGenFunction::EmitStoreThroughLValue(RValue Src, LValue Dst, if (Dst.isMatrixElt()) { llvm::Value *Idx = Dst.getMatrixIdx(); - if (IsOptimizedBuild(CGM.getCodeGenOpts())) { + if (CGM.getCodeGenOpts().isOptimizedBuild()) { const auto *const MatTy = Dst.getType()->castAs<ConstantMatrixType>(); llvm::MatrixBuilder MB(Builder); MB.CreateIndexAssumption(Idx, MatTy->getNumElementsFlattened()); @@ -3930,7 +3927,7 @@ static void emitCheckHandlerCall(CodeGenFunction &CGF, llvm::AttributeList::FunctionIndex, B), /*Local=*/true); llvm::CallInst *HandlerCall = CGF.EmitNounwindRuntimeCall(Fn, FnArgs); - NoMerge = NoMerge || !IsOptimizedBuild(CGF.CGM.getCodeGenOpts()) || + NoMerge = NoMerge || !CGF.CGM.getCodeGenOpts().isOptimizedBuild() || (CGF.CurCodeDecl && CGF.CurCodeDecl->hasAttr<OptimizeNoneAttr>()); if (NoMerge) HandlerCall->addFnAttr(llvm::Attribute::NoMerge); @@ -4322,7 +4319,7 @@ void CodeGenFunction::EmitTrapCheck(llvm::Value *Checked, TrapLocation, TrapCategory, TrapMessage); } - NoMerge = NoMerge || !IsOptimizedBuild(CGM.getCodeGenOpts()) || + NoMerge = NoMerge || !CGM.getCodeGenOpts().isOptimizedBuild() || (CurCodeDecl && CurCodeDecl->hasAttr<OptimizeNoneAttr>()); llvm::MDBuilder MDHelper(getLLVMContext()); diff --git a/clang/lib/CodeGen/CGObjC.cpp b/clang/lib/CodeGen/CGObjC.cpp index 10aad2e26938d..a6decad4aa9c7 100644 --- a/clang/lib/CodeGen/CGObjC.cpp +++ b/clang/lib/CodeGen/CGObjC.cpp @@ -1209,7 +1209,10 @@ CodeGenFunction::generateObjCGetterBody(const ObjCImplementationDecl *classImpl, uint64_t retTySize = CGM.getDataLayout().getTypeSizeInBits(retTy); if (ivarSize > retTySize) { bitcastType = llvm::Type::getIntNTy(getLLVMContext(), retTySize); - ivarVal = Builder.CreateTrunc(ivarVal, bitcastType); + if (getterMethod->getReturnType()->hasBooleanRepresentation() && CGM.getCodeGenOpts().isConvertingBoolWithCmp0()) + ivarVal = Builder.CreateICmpNE(ivarVal, llvm::Constant::getNullValue(ivarVal->getType())); + else + ivarVal = Builder.CreateTrunc(ivarVal, bitcastType); } Builder.CreateStore(ivarVal, ReturnValue.withElementType(bitcastType)); diff --git a/clang/test/CodeGenHLSL/builtins/BoolSwizzles.hlsl b/clang/test/CodeGenHLSL/builtins/BoolSwizzles.hlsl new file mode 100644 index 0000000000000..9cdad86ee587c --- /dev/null +++ b/clang/test/CodeGenHLSL/builtins/BoolSwizzles.hlsl @@ -0,0 +1,176 @@ +// RUN: %clang_cc1 -std=hlsl2021 -finclude-default-header -x hlsl -load-bool-from-mem=truncate -triple dxil-pc-shadermodel6.3-library %s -emit-llvm -disable-llvm-passes -o - | FileCheck -check-prefixes=CHECK,CHECK-TR %s +// RUN: %clang_cc1 -std=hlsl2021 -finclude-default-header -x hlsl -load-bool-from-mem=nonzero -triple dxil-pc-shadermodel6.3-library %s -emit-llvm -disable-llvm-passes -o - | FileCheck -check-prefixes=CHECK,CHECK-NZ %s + +// CHECK-LABEL: ToFourBools +// CHECK: {{%.*}} = zext i1 {{.*}} to i32 +// CHECK: [[splat:%.*]] = insertelement <1 x i32> poison, i32 {{.*}}, i64 0 +// CHECK-NEXT: [[vec4:%.*]] = shufflevector <1 x i32> [[splat]], <1 x i32> poison, <4 x i32> zeroinitializer +// CHECK-NZ-NEXT: [[vec2Ret:%.*]] = icmp ne <4 x i32> [[vec4]], zeroinitializer +// CHECK-TR-NEXT: [[vec2Ret:%.*]] = trunc <4 x i32> [[vec4]] to <4 x i1> +// CHECK-NEXT: ret <4 x i1> [[vec2Ret]] +bool4 ToFourBools(bool V) { + return V.rrrr; +} + +// CHECK-LABEL: FillTrue +// CHECK: [[Tmp:%.*]] = alloca <1 x i32>, align 4 +// CHECK-NEXT: store <1 x i32> splat (i32 1), ptr [[Tmp]], align 4 +// CHECK-NEXT: [[Vec1:%.*]] = load <1 x i32>, ptr [[Tmp]], align 4 +// CHECK-NEXT: [[Vec2:%.*]] = shufflevector <1 x i32> [[Vec1]], <1 x i32> poison, <2 x i32> zeroinitializer +// CHECK-NZ-NEXT: [[Vec2Ret:%.*]] = icmp ne <2 x i32> [[Vec2]], zeroinitializer +// CHECK-TR-NEXT: [[Vec2Ret:%.*]] = trunc <2 x i32> [[Vec2]] to <2 x i1> +// CHECK-NEXT: ret <2 x i1> [[Vec2Ret]] +bool2 FillTrue() { + return true.xx; +} + +// CHECK-LABEL: HowManyBools +// CHECK: [[VAddr:%.*]] = alloca i32, align 4 +// CHECK-NEXT: [[Vec2Ptr:%.*]] = alloca <2 x i32>, align 8 +// CHECK-NEXT: [[Tmp:%.*]] = zext i1 {{.*}} to i32 +// CHECK-NEXT: store i32 [[Tmp]], ptr [[VAddr]], align 4 +// CHECK-NEXT: [[VVal:%.*]] = load i32, ptr [[VAddr]], align 4 +// CHECK-NEXT: [[Splat:%.*]] = insertelement <1 x i32> poison, i32 [[VVal]], i64 0 +// CHECK-NEXT: [[Vec2:%.*]] = shufflevector <1 x i32> [[Splat]], <1 x i32> poison, <2 x i32> zeroinitializer +// CHECK-NZ-NEXT: [[Trunc:%.*]] = icmp ne <2 x i32> [[Vec2]], zeroinitializer +// CHECK-TR-NEXT: [[Trunc:%.*]] = trunc <2 x i32> [[Vec2]] to <2 x i1> +// CHECK-NEXT: [[Ext:%.*]] = zext <2 x i1> [[Trunc]] to <2 x i32> +// CHECK-NEXT: store <2 x i32> [[Ext]], ptr [[Vec2Ptr]], align 8 +// CHECK-NEXT: [[V2:%.*]] = load <2 x i32>, ptr [[Vec2Ptr]], align 8 +// CHECK-NEXT: [[V3:%.*]] = shufflevector <2 x i32> [[V2]], <2 x i32> poison, <2 x i32> zeroinitializer +// CHECK-NZ-NEXT: [[LV1:%.*]] = icmp ne <2 x i32> [[V3]], zeroinitializer +// CHECK-TR-NEXT: [[LV1:%.*]] = trunc <2 x i32> [[V3]] to <2 x i1> +// CHECK-NEXT: ret <2 x i1> [[LV1]] +bool2 HowManyBools(bool V) { + return V.rr.rr; +} + +// CHECK-LABEL: AssignBool +// CHECK: [[VAddr:%.*]] = alloca i32, align 4 +// CHECK-NEXT: [[XAddr:%.*]] = alloca i32, align 4 +// CHECK-NEXT: [[Zext:%.*]] = zext i1 %V to i32 +// CHECK-NEXT: store i32 [[Zext]], ptr [[VAddr]], align 4 +// CHECK-NEXT: [[X:%.*]] = load i32, ptr [[VAddr]], align 4 +// CHECK-NEXT: [[Splat:%.*]] = insertelement <1 x i32> poison, i32 [[X]], i64 0 +// CHECK-NEXT: [[Y:%.*]] = extractelement <1 x i32> [[Splat]], i32 0 +// CHECK-NZ-NEXT: [[Z:%.*]] = icmp ne i32 [[Y]], 0 +// CHECK-TR-NEXT: [[Z:%.*]] = trunc i32 [[Y]] to i1 +// CHECK-NEXT: [[A:%.*]] = zext i1 [[Z]] to i32 +// CHECK-NEXT: store i32 [[A]], ptr [[XAddr]], align 4 +// CHECK-NEXT: [[B:%.*]] = load i32, ptr [[VAddr]], align 4 +// CHECK-NEXT: [[Splat2:%.*]] = insertelement <1 x i32> poison, i32 [[B]], i64 0 +// CHECK-NEXT: [[C:%.*]] = extractelement <1 x i32> [[Splat2]], i32 0 +// CHECK-NZ-NEXT: [[D:%.*]] = icmp ne i32 [[C]], 0 +// CHECK-TR-NEXT: [[D:%.*]] = trunc i32 [[C]] to i1 +// CHECK-NEXT: br i1 [[D]], label %lor.end, label %lor.rhs + +// CHECK: lor.rhs: +// CHECK-NEXT: [[E:%.*]] = load i32, ptr [[VAddr]], align 4 +// CHECK-NEXT: [[Splat3:%.*]] = insertelement <1 x i32> poison, i32 [[E]], i64 0 +// CHECK-NEXT: [[F:%.*]] = extractelement <1 x i32> [[Splat3]], i32 0 +// CHECK-NZ-NEXT: [[G:%.*]] = icmp ne i32 [[F]], 0 +// CHECK-TR-NEXT: [[G:%.*]] = trunc i32 [[F]] to i1 +// CHECK-NEXT: br label %lor.end + +// CHECK: lor.end: +// CHECK-NEXT: [[H:%.*]] = phi i1 [ true, %entry ], [ [[G]], %lor.rhs ] +// CHECK-NEXT: [[J:%.*]] = zext i1 %9 to i32 +// CHECK-NEXT: store i32 [[J]], ptr [[XAddr]], align 4 +// CHECK-NEXT: [[I:%.*]] = load i32, ptr [[XAddr]], align 4 +// CHECK-NZ-NEXT: [[LoadV:%.*]] = icmp ne i32 [[I]], 0 +// CHECK-TR-NEXT: [[LoadV:%.*]] = trunc i32 [[I]] to i1 +// CHECK-NEXT: ret i1 [[LoadV]] +bool AssignBool(bool V) { + bool X = V.x; + X.x = V.x || V.x; + return X; +} + +// CHECK-LABEL: AssignBool2 +// CHECK: [[VAdddr:%.*]] = alloca i32, align 4 +// CHECK-NEXT: [[X:%.*]] = alloca <2 x i32>, align 8 +// CHECK-NEXT: [[Tmp:%.*]] = alloca <1 x i32>, align 4 +// CHECK-NEXT: [[SV:%.*]] = zext i1 %V to i32 +// CHECK-NEXT: store i32 [[SV]], ptr [[VAddr]], align 4 +// CHECK-NEXT: store <1 x i32> splat (i32 1), ptr [[Tmp]], align 4 +// CHECK-NEXT: [[Y:%.*]] = load <1 x i32>, ptr [[Tmp]], align 4 +// CHECK-NEXT: [[Z:%.*]] = shufflevector <1 x i32> [[Y]], <1 x i32> poison, <2 x i32> zeroinitializer +// CHECK-NZ-NEXT: [[LV:%.*]] = icmp ne <2 x i32> [[Z]], zeroinitializer +// CHECK-TR-NEXT: [[LV:%.*]] = trunc <2 x i32> [[Z]] to <2 x i1> +// CHECK-NEXT: [[A:%.*]] = zext <2 x i1> [[LV]] to <2 x i32> +// CHECK-NEXT: store <2 x i32> [[A]], ptr [[X]], align 8 +// CHECK-NEXT: [[B:%.*]] = load i32, ptr [[VAddr]], align 4 +// CHECK-NZ-NEXT: [[LV1:%.*]] = icmp ne i32 [[B]], 0 +// CHECK-TR-NEXT: [[LV1:%.*]] = trunc i32 [[B]] to i1 +// CHECK-NEXT: [[D:%.*]] = zext i1 [[LV1]] to i32 +// CHECK-NEXT: [[C:%.*]] = getelementptr <2 x i32>, ptr [[X]], i32 0, i32 1 +// CHECK-NEXT: store i32 [[D]], ptr [[C]], align 4 +// CHECK-NEXT: ret void +void AssignBool2(bool V) { + bool2 X = true.xx; + X.y = V; +} + +// CHECK-LABEL: AssignBool3 +// CHECK: [[VAddr:%.*]] = alloca <2 x i32>, align 8 +// CHECK-NEXT: [[X:%.*]] = alloca <2 x i32>, align 8 +// CHECK-NEXT: [[Y:%.*]] = zext <2 x i1> %V to <2 x i32> +// CHECK-NEXT: store <2 x i32> [[Y]], ptr [[VAddr]], align 8 +// CHECK-NEXT: store <2 x i32> splat (i32 1), ptr [[X]], align 8 +// CHECK-NEXT: [[Z:%.*]] = load <2 x i32>, ptr [[VAddr]], align 8 +// CHECK-NZ-NEXT: [[LV:%.*]] = icmp ne <2 x i32> [[Z]], zeroinitializer +// CHECK-TR-NEXT: [[LV:%.*]] = trunc <2 x i32> [[Z]] to <2 x i1> +// CHECK-NEXT: [[B:%.*]] = zext <2 x i1> [[LV]] to <2 x i32> +// CHECK-NEXT: [[V1:%.*]] = extractelement <2 x i32> [[B]], i32 0 +// CHECK-NEXT: store i32 [[V1]], ptr [[X]], align 4 +// CHECK-NEXT: [[V2:%.*]] = extractelement <2 x i32> [[B]], i32 1 +// CHECK-NEXT: [[X2:%.*]] = getelementptr <2 x i32>, ptr [[X]], i32 0, i32 1 +// CHECK-NEXT: store i32 [[V2]], ptr [[X2]], align 4 +// CHECK-NEXT: ret void + +void AssignBool3(bool2 V) { + bool2 X = {true,true}; + X.xy = V; +} + +// CHECK-LABEL: AccessBools +// CHECK: [[X:%.*]] = alloca <4 x i32>, align 16 +// CHECK-NEXT: [[Tmp:%.*]] = alloca <1 x i32>, align 4 +// CHECK-NEXT: store <1 x i32> splat (i32 1), ptr [[Tmp]], align 4 +// CHECK-NEXT: [[Y:%.*]] = load <1 x i32>, ptr [[Tmp]], align 4 +// CHECK-NEXT: [[Z:%.*]] = shufflevector <1 x i32> [[Y]], <1 x i32> poison, <4 x i32> zeroinitializer +// CHECK-NZ-NEXT: [[LV:%.*]] = icmp ne <4 x i32> [[Z]], zeroinitializer +// CHECK-TR-NEXT: [[LV:%.*]] = trunc <4 x i32> [[Vec2]] to <4 x i1> +// CHECK-NEXT: [[A:%.*]] = zext <4 x i1> [[LV]] to <4 x i32> +// CHECK-NEXT: store <4 x i32> [[A]], ptr [[X]], align 16 +// CHECK-NEXT: [[B:%.*]] = load <4 x i32>, ptr [[X]], align 16 +// CHECK-NEXT: [[C:%.*]] = shufflevector <4 x i32> [[B]], <4 x i32> poison, <2 x i32> <i32 2, i32 3> +// CHECK-NZ-NEXT: [[LV1:%.*]] = icmp ne <2 x i32> [[C]], zeroinitializer +// CHECK-TR-NEXT: [[LV1:%.*]] = trunc <2 x i32> [[C]] to <2 x i1> +// CHECK-NEXT: ret <2 x i1> [[LV1]] +bool2 AccessBools() { + bool4 X = true.xxxx; + return X.zw; +} + +// CHECK-LABEL: define hidden void {{.*}}BoolSizeMismatch{{.*}} +// CHECK: [[B:%.*]] = alloca <4 x i32>, align 16 +// CHECK-NEXT: [[Tmp:%.*]] = alloca <1 x i32>, align 4 +// CHECK-NEXT: store <4 x i32> splat (i32 1), ptr [[B]], align 16 +// CHECK-NEXT: store <1 x i32> zeroinitializer, ptr [[Tmp]], align 4 +// CHECK-NEXT: [[L0:%.*]] = load <1 x i32>, ptr [[Tmp]], align 4 +// CHECK-NEXT: [[L1:%.*]] = shufflevector <1 x i32> [[L0]], <1 x i32> poison, <3 x i32> zeroinitializer +// CHECK-NZ-NEXT: [[TruncV:%.*]] = icmp ne <3 x i32> [[L1]], zeroinitializer +// CHECK-TR-NEXT: [[TruncV:%.*]] = trunc <3 x i32> [[Vec2]] to <3 x i1> +// CHECK-NEXT: [[L2:%.*]] = zext <3 x i1> [[TruncV]] to <3 x i32> +// CHECK-NEXT: [[V1:%.*]] = extractelement <3 x i32> [[L2]], i32 0 +// CHECK-NEXT: store i32 [[V1]], ptr %B, align 4 +// CHECK-NEXT: [[V2:%.*]] = extractelement <3 x i32> [[L2]], i32 1 +// CHECK-NEXT: [[B2:%.*]] = getelementptr <4 x i32>, ptr %B, i32 0, i32 1 +// CHECK-NEXT: store i32 [[V2]], ptr [[B2]], align 4 +// CHECK-NEXT: [[V3:%.*]] = extractelement <3 x i32> [[L2]], i32 2 +// CHECK-NEXT: [[B3:%.*]] = getelementptr <4 x i32>, ptr %B, i32 0, i32 2 +void BoolSizeMismatch() { + bool4 B = {true,true,true,true}; + B.xyz = false.xxx; +} diff --git a/clang/test/CodeGenHLSL/builtins/ScalarSwizzles.hlsl b/clang/test/CodeGenHLSL/builtins/ScalarSwizzles.hlsl index 270598265c660..e7ef500fcafae 100644 --- a/clang/test/CodeGenHLSL/builtins/ScalarSwizzles.hlsl +++ b/clang/test/CodeGenHLSL/builtins/ScalarSwizzles.hlsl @@ -18,16 +18,6 @@ float4 ToFourFloats(float V){ return V.rrrr; } -// CHECK-LABEL: ToFourBools -// CHECK: {{%.*}} = zext i1 {{.*}} to i32 -// CHECK: [[splat:%.*]] = insertelement <1 x i32> poison, i32 {{.*}}, i64 0 -// CHECK-NEXT: [[vec4:%.*]] = shufflevector <1 x i32> [[splat]], <1 x i32> poison, <4 x i32> zeroinitializer -// CHECK-NEXT: [[vec2Ret:%.*]] = trunc <4 x i32> [[vec4]] to <4 x i1> -// CHECK-NEXT: ret <4 x i1> [[vec2Ret]] -bool4 ToFourBools(bool V) { - return V.rrrr; -} - // CHECK-LABEL: FillOne // CHECK: [[vec1Ptr:%.*]] = alloca <1 x i32>, align 4 // CHECK: store <1 x i32> splat (i32 1), ptr [[vec1Ptr]], align 4 @@ -103,17 +93,6 @@ vector<float, 1> FillOneHalfFloat(){ return .5f.r; } -// CHECK-LABEL: FillTrue -// CHECK: [[Tmp:%.*]] = alloca <1 x i32>, align 4 -// CHECK-NEXT: store <1 x i32> splat (i32 1), ptr [[Tmp]], align 4 -// CHECK-NEXT: [[Vec1:%.*]] = load <1 x i32>, ptr [[Tmp]], align 4 -// CHECK-NEXT: [[Vec2:%.*]] = shufflevector <1 x i32> [[Vec1]], <1 x i32> poison, <2 x i32> zeroinitializer -// CHECK-NEXT: [[Vec2Ret:%.*]] = trunc <2 x i32> [[Vec2]] to <2 x i1> -// CHECK-NEXT: ret <2 x i1> [[Vec2Ret]] -bool2 FillTrue() { - return true.xx; -} - // The initial codegen for this case is correct but a bit odd. The IR optimizer // cleans this up very nicely. @@ -131,25 +110,6 @@ float2 HowManyFloats(float V) { return V.rr.rr; } -// CHECK-LABEL: HowManyBools -// CHECK: [[VAddr:%.*]] = alloca i32, align 4 -// CHECK-NEXT: [[Vec2Ptr:%.*]] = alloca <2 x i32>, align 8 -// CHECK-NEXT: [[Tmp:%.*]] = zext i1 {{.*}} to i32 -// CHECK-NEXT: store i32 [[Tmp]], ptr [[VAddr]], align 4 -// CHECK-NEXT: [[VVal:%.*]] = load i32, ptr [[VAddr]], align 4 -// CHECK-NEXT: [[Splat:%.*]] = insertelement <1 x i32> poison, i32 [[VVal]], i64 0 -// CHECK-NEXT: [[Vec2:%.*]] = shufflevector <1 x i32> [[Splat]], <1 x i32> poison, <2 x i32> zeroinitializer -// CHECK-NEXT: [[Trunc:%.*]] = trunc <2 x i32> [[Vec2]] to <2 x i1> -// CHECK-NEXT: [[Ext:%.*]] = zext <2 x i1> [[Trunc]] to <2 x i32> -// CHECK-NEXT: store <2 x i32> [[Ext]], ptr [[Vec2Ptr]], align 8 -// CHECK-NEXT: [[V2:%.*]] = load <2 x i32>, ptr [[Vec2Ptr]], align 8 -// CHECK-NEXT: [[V3:%.*]] = shufflevector <2 x i32> [[V2]], <2 x i32> poison, <2 x i32> zeroinitializer -// CHECK-NEXT: [[LV1:%.*]] = trunc <2 x i32> [[V3]] to <2 x i1> -// CHECK-NEXT: ret <2 x i1> [[LV1]] -bool2 HowManyBools(bool V) { - return V.rr.rr; -} - // This codegen is gnarly because `1.l` is a double, so this creates double // vectors that need to be truncated down to floats. The optimizer cleans this // up nicely too. @@ -206,123 +166,3 @@ int AssignInt(int V){ X.x = V.x + V.x; return X; } - -// CHECK-LABEL: AssignBool -// CHECK: [[VAddr:%.*]] = alloca i32, align 4 -// CHECK-NEXT: [[XAddr:%.*]] = alloca i32, align 4 -// CHECK-NEXT: [[Zext:%.*]] = zext i1 %V to i32 -// CHECK-NEXT: store i32 [[Zext]], ptr [[VAddr]], align 4 -// CHECK-NEXT: [[X:%.*]] = load i32, ptr [[VAddr]], align 4 -// CHECK-NEXT: [[Splat:%.*]] = insertelement <1 x i32> poison, i32 [[X]], i64 0 -// CHECK-NEXT: [[Y:%.*]] = extractelement <1 x i32> [[Splat]], i32 0 -// CHECK-NEXT: [[Z:%.*]] = trunc i32 [[Y]] to i1 -// CHECK-NEXT: [[A:%.*]] = zext i1 [[Z]] to i32 -// CHECK-NEXT: store i32 [[A]], ptr [[XAddr]], align 4 -// CHECK-NEXT: [[B:%.*]] = load i32, ptr [[VAddr]], align 4 -// CHECK-NEXT: [[Splat2:%.*]] = insertelement <1 x i32> poison, i32 [[B]], i64 0 -// CHECK-NEXT: [[C:%.*]] = extractelement <1 x i32> [[Splat2]], i32 0 -// CHECK-NEXT: [[D:%.*]] = trunc i32 [[C]] to i1 -// CHECK-NEXT: br i1 [[D]], label %lor.end, label %lor.rhs - -// CHECK: lor.rhs: -// CHECK-NEXT: [[E:%.*]] = load i32, ptr [[VAddr]], align 4 -// CHECK-NEXT: [[Splat3:%.*]] = insertelement <1 x i32> poison, i32 [[E]], i64 0 -// CHECK-NEXT: [[F:%.*]] = extractelement <1 x i32> [[Splat3]], i32 0 -// CHECK-NEXT: [[G:%.*]] = trunc i32 [[F]] to i1 -// CHECK-NEXT: br label %lor.end - -// CHECK: lor.end: -// CHECK-NEXT: [[H:%.*]] = phi i1 [ true, %entry ], [ [[G]], %lor.rhs ] -// CHECK-NEXT: [[J:%.*]] = zext i1 %9 to i32 -// CHECK-NEXT: store i32 [[J]], ptr [[XAddr]], align 4 -// CHECK-NEXT: [[I:%.*]] = load i32, ptr [[XAddr]], align 4 -// CHECK-NEXT: [[LoadV:%.*]] = trunc i32 [[I]] to i1 -// CHECK-NEXT: ret i1 [[LoadV]] -bool AssignBool(bool V) { - bool X = V.x; - X.x = V.x || V.x; - return X; -} - -// CHECK-LABEL: AssignBool2 -// CHECK: [[VAdddr:%.*]] = alloca i32, align 4 -// CHECK-NEXT: [[X:%.*]] = alloca <2 x i32>, align 8 -// CHECK-NEXT: [[Tmp:%.*]] = alloca <1 x i32>, align 4 -// CHECK-NEXT: [[SV:%.*]] = zext i1 %V to i32 -// CHECK-NEXT: store i32 [[SV]], ptr [[VAddr]], align 4 -// CHECK-NEXT: store <1 x i32> splat (i32 1), ptr [[Tmp]], align 4 -// CHECK-NEXT: [[Y:%.*]] = load <1 x i32>, ptr [[Tmp]], align 4 -// CHECK-NEXT: [[Z:%.*]] = shufflevector <1 x i32> [[Y]], <1 x i32> poison, <2 x i32> zeroinitializer -// CHECK-NEXT: [[LV:%.*]] = trunc <2 x i32> [[Z]] to <2 x i1> -// CHECK-NEXT: [[A:%.*]] = zext <2 x i1> [[LV]] to <2 x i32> -// CHECK-NEXT: store <2 x i32> [[A]], ptr [[X]], align 8 -// CHECK-NEXT: [[B:%.*]] = load i32, ptr [[VAddr]], align 4 -// CHECK-NEXT: [[LV1:%.*]] = trunc i32 [[B]] to i1 -// CHECK-NEXT: [[D:%.*]] = zext i1 [[LV1]] to i32 -// CHECK-NEXT: [[C:%.*]] = getelementptr <2 x i32>, ptr [[X]], i32 0, i32 1 -// CHECK-NEXT: store i32 [[D]], ptr [[C]], align 4 -// CHECK-NEXT: ret void -void AssignBool2(bool V) { - bool2 X = true.xx; - X.y = V; -} - -// CHECK-LABEL: AssignBool3 -// CHECK: [[VAddr:%.*]] = alloca <2 x i32>, align 8 -// CHECK-NEXT: [[X:%.*]] = alloca <2 x i32>, align 8 -// CHECK-NEXT: [[Y:%.*]] = zext <2 x i1> %V to <2 x i32> -// CHECK-NEXT: store <2 x i32> [[Y]], ptr [[VAddr]], align 8 -// CHECK-NEXT: store <2 x i32> splat (i32 1), ptr [[X]], align 8 -// CHECK-NEXT: [[Z:%.*]] = load <2 x i32>, ptr [[VAddr]], align 8 -// CHECK-NEXT: [[LV:%.*]] = trunc <2 x i32> [[Z]] to <2 x i1> -// CHECK-NEXT: [[B:%.*]] = zext <2 x i1> [[LV]] to <2 x i32> -// CHECK-NEXT: [[V1:%.*]] = extractelement <2 x i32> [[B]], i32 0 -// CHECK-NEXT: store i32 [[V1]], ptr [[X]], align 4 -// CHECK-NEXT: [[V2:%.*]] = extractelement <2 x i32> [[B]], i32 1 -// CHECK-NEXT: [[X2:%.*]] = getelementptr <2 x i32>, ptr [[X]], i32 0, i32 1 -// CHECK-NEXT: store i32 [[V2]], ptr [[X2]], align 4 -// CHECK-NEXT: ret void - -void AssignBool3(bool2 V) { - bool2 X = {true,true}; - X.xy = V; -} - -// CHECK-LABEL: AccessBools -// CHECK: [[X:%.*]] = alloca <4 x i32>, align 16 -// CHECK-NEXT: [[Tmp:%.*]] = alloca <1 x i32>, align 4 -// CHECK-NEXT: store <1 x i32> splat (i32 1), ptr [[Tmp]], align 4 -// CHECK-NEXT: [[Y:%.*]] = load <1 x i32>, ptr [[Tmp]], align 4 -// CHECK-NEXT: [[Z:%.*]] = shufflevector <1 x i32> [[Y]], <1 x i32> poison, <4 x i32> zeroinitializer -// CHECK-NEXT: [[LV:%.*]] = trunc <4 x i32> [[Z]] to <4 x i1> -// CHECK-NEXT: [[A:%.*]] = zext <4 x i1> [[LV]] to <4 x i32> -// CHECK-NEXT: store <4 x i32> [[A]], ptr [[X]], align 16 -// CHECK-NEXT: [[B:%.*]] = load <4 x i32>, ptr [[X]], align 16 -// CHECK-NEXT: [[C:%.*]] = shufflevector <4 x i32> [[B]], <4 x i32> poison, <2 x i32> <i32 2, i32 3> -// CHECK-NEXT: [[LV1:%.*]] = trunc <2 x i32> [[C]] to <2 x i1> -// CHECK-NEXT: ret <2 x i1> [[LV1]] -bool2 AccessBools() { - bool4 X = true.xxxx; - return X.zw; -} - -// CHECK-LABEL: define hidden void {{.*}}BoolSizeMismatch{{.*}} -// CHECK: [[B:%.*]] = alloca <4 x i32>, align 16 -// CHECK-NEXT: [[Tmp:%.*]] = alloca <1 x i32>, align 4 -// CHECK-NEXT: store <4 x i32> splat (i32 1), ptr [[B]], align 16 -// CHECK-NEXT: store <1 x i32> zeroinitializer, ptr [[Tmp]], align 4 -// CHECK-NEXT: [[L0:%.*]] = load <1 x i32>, ptr [[Tmp]], align 4 -// CHECK-NEXT: [[L1:%.*]] = shufflevector <1 x i32> [[L0]], <1 x i32> poison, <3 x i32> zeroinitializer -// CHECK-NEXT: [[TruncV:%.*]] = trunc <3 x i32> [[L1]] to <3 x i1> -// CHECK-NEXT: [[L2:%.*]] = zext <3 x i1> [[TruncV]] to <3 x i32> -// CHECK-NEXT: [[V1:%.*]] = extractelement <3 x i32> [[L2]], i32 0 -// CHECK-NEXT: store i32 [[V1]], ptr %B, align 4 -// CHECK-NEXT: [[V2:%.*]] = extractelement <3 x i32> [[L2]], i32 1 -// CHECK-NEXT: [[B2:%.*]] = getelementptr <4 x i32>, ptr %B, i32 0, i32 1 -// CHECK-NEXT: store i32 [[V2]], ptr [[B2]], align 4 -// CHECK-NEXT: [[V3:%.*]] = extractelement <3 x i32> [[L2]], i32 2 -// CHECK-NEXT: [[B3:%.*]] = getelementptr <4 x i32>, ptr %B, i32 0, i32 2 -void BoolSizeMismatch() { - bool4 B = {true,true,true,true}; - B.xyz = false.xxx; -} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
