Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.. title:: clang-tidy - readability-use-std-min-max

readability-use-std-min-max
===========================

Replaces certain conditional statements with equivalent calls to
``std::min`` or ``std::max``.
Note: This may impact performance in critical code due to potential
additional stores compared to the original if statement.

Before:

.. code-block:: c++

void foo() {
int a = 2, b = 3;
if (a < b)
a = b;
}


After:

.. code-block:: c++

void foo() {
int a = 2, b = 3;
a = std::max(a, b);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
// RUN: %check_clang_tidy -std=c++11-or-later %s readability-use-std-min-max %t -- -- -fno-delayed-template-parsing
#define MY_MACRO_MIN(a, b) ((a) < (b) ? (a) : (b))

constexpr int myConstexprMin(int a, int b) {
return a < b ? a : b;
}

constexpr int myConstexprMax(int a, int b) {
return a > b ? a : b;
}

#define MY_IF_MACRO(condition, statement) \
if (condition) { \
statement \
}

class MyClass {
public:
int member1;
int member2;
};

template<typename T>

void foo(T value7) {
int value1,value2,value3;

// CHECK-MESSAGES: :[[@LINE+2]]:3: warning: use `std::max` instead of `<` [readability-use-std-min-max]
// CHECK-FIXES: value1 = std::max(value1, value2);
if (value1 < value2)
value1 = value2;

// CHECK-MESSAGES: :[[@LINE+2]]:3: warning: use `std::min` instead of `<` [readability-use-std-min-max]
// CHECK-FIXES: value2 = std::min(value1, value2);
if (value1 < value2)
value2 = value1;

// CHECK-MESSAGES: :[[@LINE+2]]:3: warning: use `std::min` instead of `>` [readability-use-std-min-max]
// CHECK-FIXES: value2 = std::min(value2, value1);
if (value2 > value1)
value2 = value1;

// CHECK-MESSAGES: :[[@LINE+2]]:3: warning: use `std::max` instead of `>` [readability-use-std-min-max]
// CHECK-FIXES: value1 = std::max(value2, value1);
if (value2 > value1)
value1 = value2;

// No suggestion needed here
if (value1 == value2)
value1 = value2;

// CHECK-MESSAGES: :[[@LINE+3]]:3: warning: use `std::max` instead of `<` [readability-use-std-min-max]
// CHECK-FIXES: value1 = std::max<int>(value1, value4);
short value4;
if(value1<value4)
value1=value4;

// CHECK-MESSAGES: :[[@LINE+2]]:3: warning: use `std::min` instead of `<` [readability-use-std-min-max]
// CHECK-FIXES: value3 = std::min(value1+value2, value3);
if(value1+value2<value3)
value3 = value1+value2;

// CHECK-MESSAGES: :[[@LINE+2]]:3: warning: use `std::max` instead of `<` [readability-use-std-min-max]
// CHECK-FIXES: value1 = std::max(value1, myConstexprMin(value2, value3));
if (value1 < myConstexprMin(value2, value3))
value1 = myConstexprMin(value2, value3);

// CHECK-MESSAGES: :[[@LINE+2]]:3: warning: use `std::min` instead of `>` [readability-use-std-min-max]
// CHECK-FIXES: value1 = std::min(value1, myConstexprMax(value2, value3));
if (value1 > myConstexprMax(value2, value3))
value1 = myConstexprMax(value2, value3);

// CHECK-MESSAGES: :[[@LINE+2]]:3: warning: use `std::min` instead of `<=` [readability-use-std-min-max]
// CHECK-FIXES: value2 = std::min(value1, value2);
if (value1 <= value2)
value2 = value1;

// CHECK-MESSAGES: :[[@LINE+2]]:3: warning: use `std::max` instead of `<=` [readability-use-std-min-max]
// CHECK-FIXES: value1 = std::max(value1, value2);
if (value1 <= value2)
value1 = value2;

// CHECK-MESSAGES: :[[@LINE+2]]:3: warning: use `std::max` instead of `>=` [readability-use-std-min-max]
// CHECK-FIXES: value1 = std::max(value2, value1);
if (value2 >= value1)
value1 = value2;

// CHECK-MESSAGES: :[[@LINE+2]]:3: warning: use `std::min` instead of `>=` [readability-use-std-min-max]
// CHECK-FIXES: value2 = std::min(value2, value1);
if (value2 >= value1)
value2 = value1;

// CHECK-MESSAGES: :[[@LINE+3]]:3: warning: use `std::max` instead of `<` [readability-use-std-min-max]
// CHECK-FIXES: obj.member1 = std::max(obj.member1, obj.member2);
MyClass obj;
if (obj.member1 < obj.member2)
obj.member1 = obj.member2;

// CHECK-MESSAGES: :[[@LINE+2]]:3: warning: use `std::min` instead of `<` [readability-use-std-min-max]
// CHECK-FIXES: obj.member2 = std::min(obj.member1, obj.member2);
if (obj.member1 < obj.member2)
obj.member2 = obj.member1;

// CHECK-MESSAGES: :[[@LINE+2]]:3: warning: use `std::min` instead of `>` [readability-use-std-min-max]
// CHECK-FIXES: obj.member2 = std::min(obj.member2, obj.member1);
if (obj.member2 > obj.member1)
obj.member2 = obj.member1;

// CHECK-MESSAGES: :[[@LINE+2]]:3: warning: use `std::max` instead of `>` [readability-use-std-min-max]
// CHECK-FIXES: obj.member1 = std::max(obj.member2, obj.member1);
if (obj.member2 > obj.member1)
obj.member1 = obj.member2;

// CHECK-MESSAGES: :[[@LINE+2]]:3: warning: use `std::max` instead of `<` [readability-use-std-min-max]
// CHECK-FIXES: obj.member1 = std::max<int>(obj.member1, value4);
if (obj.member1 < value4)
obj.member1 = value4;

// CHECK-MESSAGES: :[[@LINE+2]]:3: warning: use `std::min` instead of `<` [readability-use-std-min-max]
// CHECK-FIXES: value3 = std::min(obj.member1 + value2, value3);
if (obj.member1 + value2 < value3)
value3 = obj.member1 + value2;

// CHECK-MESSAGES: :[[@LINE+2]]:3: warning: use `std::min` instead of `<=` [readability-use-std-min-max]
// CHECK-FIXES: obj.member2 = std::min(value1, obj.member2);
if (value1 <= obj.member2)
obj.member2 = value1;

// CHECK-MESSAGES: :[[@LINE+2]]:3: warning: use `std::max` instead of `<=` [readability-use-std-min-max]
// CHECK-FIXES: value1 = std::max(value1, obj.member2);
if (value1 <= obj.member2)
value1 = obj.member2;

// CHECK-MESSAGES: :[[@LINE+2]]:3: warning: use `std::max` instead of `>=` [readability-use-std-min-max]
// CHECK-FIXES: value1 = std::max(obj.member2, value1);
if (obj.member2 >= value1)
value1 = obj.member2;

// CHECK-MESSAGES: :[[@LINE+2]]:3: warning: use `std::min` instead of `>=` [readability-use-std-min-max]
// CHECK-FIXES: obj.member2 = std::min(obj.member2, value1);
if (obj.member2 >= value1)
obj.member2 = value1;

// No suggestion needed here
if (MY_MACRO_MIN(value1, value2) < value3)
value3 = MY_MACRO_MIN(value1, value2);

// CHECK-MESSAGES: :[[@LINE+2]]:3: warning: use `std::max` instead of `<` [readability-use-std-min-max]
// CHECK-FIXES: value4 = std::max<int>(value4, value2);
if (value4 < value2){
value4 = value2;
}

// No suggestion needed here
if(value1 < value2)
value2 = value1;
else
value2 = value3;

// No suggestion needed here
if(value1<value2){
value2 = value1;
}
else{
value2 = value3;
}

// No suggestion needed here
if(value1<value2){
value2 = value1;
int res = value1 + value2;
}

// No suggestion needed here
MY_IF_MACRO(value1 < value2, value1 = value2;)

// No suggestion needed here
if(value1<value2){
value1 = value2;
}
else if(value1>value2){
value2 = value1;
}

// CHECK-MESSAGES: :[[@LINE+3]]:5: warning: use `std::max` instead of `<` [readability-use-std-min-max]
// CHECK-FIXES: value1 = std::max(value1, value3);
if(value1 == value2){
if(value1<value3)
value1 = value3;
}

// CHECK-MESSAGES: :[[@LINE+5]]:7: warning: use `std::max` instead of `<` [readability-use-std-min-max]
// CHECK-FIXES: value1 = std::max<int>(value1, value4);
if(value1 == value2){
if(value2 == value3){
value3+=1;
if(value1<value4){
value1 = value4;
}
}
else if(value3>value2){
value2 = value3;
}
}

// CHECK-MESSAGES: :[[@LINE+4]]:3: warning: use `std::min` instead of `<` [readability-use-std-min-max]
// CHECK-FIXES: value6 = std::min<unsigned int>(value5, value6);
unsigned int value5;
unsigned char value6;
if(value5<value6){
value6 = value5;
}

//No suggestion needed here
if(value7<value6){
value6 = value7;
}

//CHECK-MESSAGES: :[[@LINE+3]]:3: warning: use `std::min` instead of `<` [readability-use-std-min-max]
//CHECK-FIXES: value1 = std::min(value8, value1);
const int value8 = 5;
if(value8<value1)
value1 = value8;

//CHECK-MESSAGES: :[[@LINE+3]]:3: warning: use `std::min` instead of `<` [readability-use-std-min-max]
//CHECK-FIXES: value1 = std::min(value9, value1);
volatile int value9 = 6;
if(value9<value1)
value1 = value9;
}

using my_size = unsigned long long;

template<typename T>
struct MyVector
{
using size_type = my_size;
size_type size() const;
};

void testVectorSizeType() {
MyVector<int> v;
unsigned int value;

// CHECK-MESSAGES: :[[@LINE+2]]:3: warning: use `std::max` instead of `>` [readability-use-std-min-max]
// CHECK-FIXES: value = std::max<my_size>(v.size(), value);
if (v.size() > value)
value = v.size();

// CHECK-MESSAGES: :[[@LINE+2]]:3: warning: use `std::max` instead of `<` [readability-use-std-min-max]
// CHECK-FIXES: value = std::max<my_size>(value, v.size());
if (value < v.size())
value = v.size();
}
7 changes: 7 additions & 0 deletions clang/include/clang/AST/Type.h
Original file line number Diff line number Diff line change
Expand Up @@ -2613,6 +2613,9 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase {
/// Return true if this is a fixed point or integer type.
bool isFixedPointOrIntegerType() const;

/// Return true if this can be converted to (or from) a fixed point type.
bool isConvertibleToFixedPointType() const;

/// Return true if this is a saturated fixed point type according to
/// ISO/IEC JTC1 SC22 WG14 N1169. This type can be signed or unsigned.
bool isSaturatedFixedPointType() const;
Expand Down Expand Up @@ -7493,6 +7496,10 @@ inline bool Type::isFixedPointOrIntegerType() const {
return isFixedPointType() || isIntegerType();
}

inline bool Type::isConvertibleToFixedPointType() const {
return isRealFloatingType() || isFixedPointOrIntegerType();
}

inline bool Type::isSaturatedFixedPointType() const {
if (const auto *BT = dyn_cast<BuiltinType>(CanonicalType)) {
return BT->getKind() >= BuiltinType::SatShortAccum &&
Expand Down
26 changes: 24 additions & 2 deletions clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
#include "clang/AST/DeclBase.h"
#include "clang/AST/Expr.h"
#include "clang/AST/Type.h"
#include "clang/Analysis/FlowSensitive/ControlFlowContext.h"
#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
#include "clang/Analysis/FlowSensitive/DataflowLattice.h"
#include "clang/Analysis/FlowSensitive/Formula.h"
Expand All @@ -31,7 +30,6 @@
#include "llvm/ADT/MapVector.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/ErrorHandling.h"
#include <memory>
#include <type_traits>
#include <utility>

Expand Down Expand Up @@ -81,6 +79,8 @@ class Environment {
return ComparisonResult::Unknown;
}

/// DEPRECATED. Override `join` and/or `widen`, instead.
///
/// Modifies `MergedVal` to approximate both `Val1` and `Val2`. This could
/// be a strict lattice join or a more general widening operation.
///
Expand All @@ -105,6 +105,28 @@ class Environment {
return true;
}

/// Modifies `JoinedVal` to approximate both `Val1` and `Val2`. This should
/// obey the properties of a lattice join.
///
/// `Env1` and `Env2` can be used to query child values and path condition
/// implications of `Val1` and `Val2` respectively.
///
/// Requirements:
///
/// `Val1` and `Val2` must be distinct.
///
/// `Val1`, `Val2`, and `JoinedVal` must model values of type `Type`.
///
/// `Val1` and `Val2` must be assigned to the same storage location in
/// `Env1` and `Env2` respectively.
virtual void join(QualType Type, const Value &Val1, const Environment &Env1,
const Value &Val2, const Environment &Env2,
Value &JoinedVal, Environment &JoinedEnv) {
[[maybe_unused]] bool ShouldKeep =
merge(Type, Val1, Env1, Val2, Env2, JoinedVal, JoinedEnv);
assert(ShouldKeep && "dropping merged value is unsupported");
}

/// This function may widen the current value -- replace it with an
/// approximation that can reach a fixed point more quickly than iterated
/// application of the transfer function alone. The previous value is
Expand Down
2 changes: 1 addition & 1 deletion clang/include/clang/Basic/DiagnosticCommonKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def warn_pragma_debug_unexpected_argument : Warning<
def warn_fp_nan_inf_when_disabled : Warning<
"use of %select{infinity|NaN}0%select{| via a macro}1 is undefined behavior "
"due to the currently enabled floating-point options">,
InGroup<DiagGroup<"nan-infinity-disabled">>;
InGroup<DiagGroup<"nan-infinity-disabled", [], NanInfDisabledDocs>>;
}

// Parse && Sema
Expand Down
9 changes: 9 additions & 0 deletions clang/include/clang/Basic/DiagnosticDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,12 @@ program by treating all string literals as having type ``const char *``
instead of ``char *``. This can cause unexpected behaviors with type-sensitive
constructs like ``_Generic``.
}];

defvar NanInfDisabledDocs = [{
This warning is enabled when source code using the macros ``INFINITY`` or ``NAN``
is compiled with floating-point options preventing these two values. This can
lead to undefined behavior. Check the order of command line arguments that modify
this behavior, such as ``-ffast-math``, ``-fhonor-infinities``, and
``-fhonor-nans`` (etc), as well as ``#pragma`` directives if this diagnostic is
generated unexpectedly.
}];
3 changes: 3 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -2982,6 +2982,9 @@ def err_trailing_requires_clause_on_deduction_guide : Error<
"deduction guide cannot have a requires clause">;
def err_constrained_non_templated_function
: Error<"non-templated function cannot have a requires clause">;
def err_non_temp_spec_requires_clause : Error<
"%select{explicit|friend}0 specialization cannot have a trailing requires clause "
"unless it declares a function template">;
def err_reference_to_function_with_unsatisfied_constraints : Error<
"invalid reference to function %0: constraints not satisfied">;
def err_requires_expr_local_parameter_default_argument : Error<
Expand Down
4 changes: 2 additions & 2 deletions clang/include/clang/Basic/arm_neon.td
Original file line number Diff line number Diff line change
Expand Up @@ -1354,9 +1354,9 @@ let isScalarNarrowShift = 1 in {
// Signed/Unsigned Saturating Rounded Shift Right Narrow (Immediate)
def SCALAR_SQRSHRN_N: SInst<"vqrshrn_n", "(1<)1I", "SsSiSlSUsSUiSUl">;
// Signed Saturating Shift Right Unsigned Narrow (Immediate)
def SCALAR_SQSHRUN_N: SInst<"vqshrun_n", "(1<)1I", "SsSiSl">;
def SCALAR_SQSHRUN_N: SInst<"vqshrun_n", "(1<U)1I", "SsSiSl">;
// Signed Saturating Rounded Shift Right Unsigned Narrow (Immediate)
def SCALAR_SQRSHRUN_N: SInst<"vqrshrun_n", "(1<)1I", "SsSiSl">;
def SCALAR_SQRSHRUN_N: SInst<"vqrshrun_n", "(1<U)1I", "SsSiSl">;
}

////////////////////////////////////////////////////////////////////////////////
Expand Down
11 changes: 6 additions & 5 deletions clang/include/clang/Lex/Preprocessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -2838,7 +2838,8 @@ class Preprocessor {
return AnnotationInfos.find(II)->second;
}

void emitMacroExpansionWarnings(const Token &Identifier) const {
void emitMacroExpansionWarnings(const Token &Identifier,
bool IsIfnDef = false) const {
IdentifierInfo *Info = Identifier.getIdentifierInfo();
if (Info->isDeprecatedMacro())
emitMacroDeprecationWarning(Identifier);
Expand All @@ -2847,12 +2848,12 @@ class Preprocessor {
!SourceMgr.isInMainFile(Identifier.getLocation()))
emitRestrictExpansionWarning(Identifier);

if (Info->getName() == "INFINITY")
if (getLangOpts().NoHonorInfs)
if (!IsIfnDef) {
if (Info->getName() == "INFINITY" && getLangOpts().NoHonorInfs)
emitRestrictInfNaNWarning(Identifier, 0);
if (Info->getName() == "NAN")
if (getLangOpts().NoHonorNaNs)
if (Info->getName() == "NAN" && getLangOpts().NoHonorNaNs)
emitRestrictInfNaNWarning(Identifier, 1);
}
}

static void processPathForFileMacro(SmallVectorImpl<char> &Path,
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11427,6 +11427,10 @@ class FixedPointExprEvaluator
return true;
}

bool ZeroInitialization(const Expr *E) {
return Success(0, E);
}

//===--------------------------------------------------------------------===//
// Visitor Methods
//===--------------------------------------------------------------------===//
Expand Down
4 changes: 3 additions & 1 deletion clang/lib/AST/Interp/ByteCodeEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "Opcode.h"
#include "Program.h"
#include "clang/AST/ASTLambda.h"
#include "clang/AST/Attr.h"
#include "clang/AST/DeclCXX.h"
#include "clang/Basic/Builtins.h"
#include <type_traits>
Expand Down Expand Up @@ -116,7 +117,8 @@ Function *ByteCodeEmitter::compileFunc(const FunctionDecl *FuncDecl) {
if (const auto *MD = dyn_cast<CXXMethodDecl>(FuncDecl))
IsEligibleForCompilation = MD->isLambdaStaticInvoker();
if (!IsEligibleForCompilation)
IsEligibleForCompilation = FuncDecl->isConstexpr();
IsEligibleForCompilation =
FuncDecl->isConstexpr() || FuncDecl->hasAttr<MSConstexprAttr>();

// Compile the function body.
if (!IsEligibleForCompilation || !visitFunc(FuncDecl)) {
Expand Down
47 changes: 46 additions & 1 deletion clang/lib/AST/Interp/ByteCodeExprGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ bool ByteCodeExprGen<Emitter>::VisitBinaryOperator(const BinaryOperator *BO) {
// Special case for C++'s three-way/spaceship operator <=>, which
// returns a std::{strong,weak,partial}_ordering (which is a class, so doesn't
// have a PrimType).
if (!T) {
if (!T && Ctx.getLangOpts().CPlusPlus) {
if (DiscardResult)
return true;
const ComparisonCategoryInfo *CmpInfo =
Expand Down Expand Up @@ -820,6 +820,19 @@ bool ByteCodeExprGen<Emitter>::VisitImplicitValueInitExpr(const ImplicitValueIni
return true;
}

if (QT->isAnyComplexType()) {
assert(Initializing);
QualType ElemQT = QT->getAs<ComplexType>()->getElementType();
PrimType ElemT = classifyPrim(ElemQT);
for (unsigned I = 0; I < 2; ++I) {
if (!this->visitZeroInitializer(ElemT, ElemQT, E))
return false;
if (!this->emitInitElem(ElemT, I, E))
return false;
}
return true;
}

return false;
}

Expand Down Expand Up @@ -3009,21 +3022,53 @@ bool ByteCodeExprGen<Emitter>::VisitDeclRefExpr(const DeclRefExpr *E) {
// pointer to the actual value) instead of a pointer to the pointer to the
// value.
bool IsReference = D->getType()->isReferenceType();
// Complex values are copied in the AST via a simply assignment or
// ltor cast. But we represent them as two-element arrays, which means
// we pass them around as pointers. So, to assignm from them, we will
// have to copy both (primitive) elements instead.
bool IsComplex = D->getType()->isAnyComplexType();

// Check for local/global variables and parameters.
if (auto It = Locals.find(D); It != Locals.end()) {
const unsigned Offset = It->second.Offset;
// FIXME: Fix the code duplication here with the code in the global case.
if (Initializing && IsComplex) {
PrimType ElemT = classifyComplexElementType(D->getType());
for (unsigned I = 0; I != 2; ++I) {
if (!this->emitGetPtrLocal(Offset, E))
return false;
if (!this->emitArrayElemPop(ElemT, I, E))
return false;
if (!this->emitInitElem(ElemT, I, E))
return false;
}
return true;
}

if (IsReference)
return this->emitGetLocal(PT_Ptr, Offset, E);
return this->emitGetPtrLocal(Offset, E);
} else if (auto GlobalIndex = P.getGlobal(D)) {
if (Initializing && IsComplex) {
PrimType ElemT = classifyComplexElementType(D->getType());
for (unsigned I = 0; I != 2; ++I) {
if (!this->emitGetPtrGlobal(*GlobalIndex, E))
return false;
if (!this->emitArrayElemPop(ElemT, I, E))
return false;
if (!this->emitInitElem(ElemT, I, E))
return false;
}
return true;
}

if (IsReference)
return this->emitGetGlobalPtr(*GlobalIndex, E);

return this->emitGetPtrGlobal(*GlobalIndex, E);
} else if (const auto *PVD = dyn_cast<ParmVarDecl>(D)) {
if (auto It = this->Params.find(PVD); It != this->Params.end()) {
// FIXME: _Complex initializing case?
if (IsReference || !It->second.IsPtr)
return this->emitGetParamPtr(It->second.Offset, E);

Expand Down
8 changes: 8 additions & 0 deletions clang/lib/AST/Interp/Interp.h
Original file line number Diff line number Diff line change
Expand Up @@ -1878,6 +1878,14 @@ inline bool ArrayElemPtrPop(InterpState &S, CodePtr OpPC) {
return NarrowPtr(S, OpPC);
}

template <PrimType Name, class T = typename PrimConv<Name>::T>
inline bool ArrayElemPop(InterpState &S, CodePtr OpPC, uint32_t Index) {
const Pointer &Ptr = S.Stk.pop<Pointer>();

S.Stk.push<T>(Ptr.atIndex(Index).deref<T>());
return true;
}

/// Just takes a pointer and checks if it's an incomplete
/// array type.
inline bool ArrayDecay(InterpState &S, CodePtr OpPC) {
Expand Down
6 changes: 6 additions & 0 deletions clang/lib/AST/Interp/Opcodes.td
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,12 @@ def ExpandPtr : Opcode;
def ArrayElemPtr : AluOpcode;
def ArrayElemPtrPop : AluOpcode;

def ArrayElemPop : Opcode {
let Args = [ArgUint32];
let Types = [AllTypeClass];
let HasGroup = 1;
}

//===----------------------------------------------------------------------===//
// Direct field accessors
//===----------------------------------------------------------------------===//
Expand Down
59 changes: 28 additions & 31 deletions clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,15 @@ static bool compareDistinctValues(QualType Type, Value &Val1,
llvm_unreachable("All cases covered in switch");
}

/// Attempts to merge distinct values `Val1` and `Val2` in `Env1` and `Env2`,
/// respectively, of the same type `Type`. Merging generally produces a single
/// Attempts to join distinct values `Val1` and `Val2` in `Env1` and `Env2`,
/// respectively, of the same type `Type`. Joining generally produces a single
/// value that (soundly) approximates the two inputs, although the actual
/// meaning depends on `Model`.
static Value *mergeDistinctValues(QualType Type, Value &Val1,
const Environment &Env1, Value &Val2,
const Environment &Env2,
Environment &MergedEnv,
Environment::ValueModel &Model) {
static Value *joinDistinctValues(QualType Type, Value &Val1,
const Environment &Env1, Value &Val2,
const Environment &Env2,
Environment &JoinedEnv,
Environment::ValueModel &Model) {
// Join distinct boolean values preserving information about the constraints
// in the respective path conditions.
if (isa<BoolValue>(&Val1) && isa<BoolValue>(&Val2)) {
Expand All @@ -113,42 +113,39 @@ static Value *mergeDistinctValues(QualType Type, Value &Val1,
// ```
auto &Expr1 = cast<BoolValue>(Val1).formula();
auto &Expr2 = cast<BoolValue>(Val2).formula();
auto &A = MergedEnv.arena();
auto &MergedVal = A.makeAtomRef(A.makeAtom());
MergedEnv.assume(
auto &A = JoinedEnv.arena();
auto &JoinedVal = A.makeAtomRef(A.makeAtom());
JoinedEnv.assume(
A.makeOr(A.makeAnd(A.makeAtomRef(Env1.getFlowConditionToken()),
A.makeEquals(MergedVal, Expr1)),
A.makeEquals(JoinedVal, Expr1)),
A.makeAnd(A.makeAtomRef(Env2.getFlowConditionToken()),
A.makeEquals(MergedVal, Expr2))));
return &A.makeBoolValue(MergedVal);
A.makeEquals(JoinedVal, Expr2))));
return &A.makeBoolValue(JoinedVal);
}

Value *MergedVal = nullptr;
Value *JoinedVal = nullptr;
if (auto *RecordVal1 = dyn_cast<RecordValue>(&Val1)) {
auto *RecordVal2 = cast<RecordValue>(&Val2);

if (&RecordVal1->getLoc() == &RecordVal2->getLoc())
// `RecordVal1` and `RecordVal2` may have different properties associated
// with them. Create a new `RecordValue` with the same location but
// without any properties so that we soundly approximate both values. If a
// particular analysis needs to merge properties, it should do so in
// `DataflowAnalysis::merge()`.
MergedVal = &MergedEnv.create<RecordValue>(RecordVal1->getLoc());
// particular analysis needs to join properties, it should do so in
// `DataflowAnalysis::join()`.
JoinedVal = &JoinedEnv.create<RecordValue>(RecordVal1->getLoc());
else
// If the locations for the two records are different, need to create a
// completely new value.
MergedVal = MergedEnv.createValue(Type);
JoinedVal = JoinedEnv.createValue(Type);
} else {
MergedVal = MergedEnv.createValue(Type);
JoinedVal = JoinedEnv.createValue(Type);
}

// FIXME: Consider destroying `MergedValue` immediately if `ValueModel::merge`
// returns false to avoid storing unneeded values in `DACtx`.
if (MergedVal)
if (Model.merge(Type, Val1, Env1, Val2, Env2, *MergedVal, MergedEnv))
return MergedVal;
if (JoinedVal)
Model.join(Type, Val1, Env1, Val2, Env2, *JoinedVal, JoinedEnv);

return nullptr;
return JoinedVal;
}

// When widening does not change `Current`, return value will equal `&Prev`.
Expand Down Expand Up @@ -240,9 +237,9 @@ joinLocToVal(const llvm::MapVector<const StorageLocation *, Value *> &LocToVal,
continue;
}

if (Value *MergedVal = mergeDistinctValues(
if (Value *JoinedVal = joinDistinctValues(
Loc->getType(), *Val, Env1, *It->second, Env2, JoinedEnv, Model)) {
Result.insert({Loc, MergedVal});
Result.insert({Loc, JoinedVal});
}
}

Expand Down Expand Up @@ -657,10 +654,10 @@ Environment Environment::join(const Environment &EnvA, const Environment &EnvB,
// cast.
auto *Func = dyn_cast<FunctionDecl>(EnvA.CallStack.back());
assert(Func != nullptr);
if (Value *MergedVal =
mergeDistinctValues(Func->getReturnType(), *EnvA.ReturnVal, EnvA,
*EnvB.ReturnVal, EnvB, JoinedEnv, Model))
JoinedEnv.ReturnVal = MergedVal;
if (Value *JoinedVal =
joinDistinctValues(Func->getReturnType(), *EnvA.ReturnVal, EnvA,
*EnvB.ReturnVal, EnvB, JoinedEnv, Model))
JoinedEnv.ReturnVal = JoinedVal;
}

if (EnvA.ReturnLoc == EnvB.ReturnLoc)
Expand Down
37 changes: 22 additions & 15 deletions clang/lib/Analysis/UnsafeBufferUsage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2870,19 +2870,6 @@ void clang::checkUnsafeBufferUsage(const Decl *D,
#endif
it = FixablesForAllVars.byVar.erase(it);
} else if (Tracker.hasUnclaimedUses(it->first)) {
#ifndef NDEBUG
auto AllUnclaimed = Tracker.getUnclaimedUses(it->first);
for (auto UnclaimedDRE : AllUnclaimed) {
std::string UnclaimedUseTrace =
getDREAncestorString(UnclaimedDRE, D->getASTContext());

Handler.addDebugNoteForVar(
it->first, UnclaimedDRE->getBeginLoc(),
("failed to produce fixit for '" + it->first->getNameAsString() +
"' : has an unclaimed use\nThe unclaimed DRE trace: " +
UnclaimedUseTrace));
}
#endif
it = FixablesForAllVars.byVar.erase(it);
} else if (it->first->isInitCapture()) {
#ifndef NDEBUG
Expand All @@ -2892,10 +2879,30 @@ void clang::checkUnsafeBufferUsage(const Decl *D,
"' : init capture"));
#endif
it = FixablesForAllVars.byVar.erase(it);
}else {
++it;
} else {
++it;
}
}

#ifndef NDEBUG
for (const auto &it : UnsafeOps.byVar) {
const VarDecl *const UnsafeVD = it.first;
auto UnclaimedDREs = Tracker.getUnclaimedUses(UnsafeVD);
if (UnclaimedDREs.empty())
continue;
const auto UnfixedVDName = UnsafeVD->getNameAsString();
for (const clang::DeclRefExpr *UnclaimedDRE : UnclaimedDREs) {
std::string UnclaimedUseTrace =
getDREAncestorString(UnclaimedDRE, D->getASTContext());

Handler.addDebugNoteForVar(
UnsafeVD, UnclaimedDRE->getBeginLoc(),
("failed to produce fixit for '" + UnfixedVDName +
"' : has an unclaimed use\nThe unclaimed DRE trace: " +
UnclaimedUseTrace));
}
}
#endif

// Fixpoint iteration for pointer assignments
using DepMapTy = DenseMap<const VarDecl *, llvm::SetVector<const VarDecl *>>;
Expand Down
22 changes: 15 additions & 7 deletions clang/lib/CodeGen/CGExprScalar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -774,7 +774,7 @@ class ScalarExprEmitter
void EmitUndefinedBehaviorIntegerDivAndRemCheck(const BinOpInfo &Ops,
llvm::Value *Zero,bool isDiv);
// Common helper for getting how wide LHS of shift is.
static Value *GetWidthMinusOneValue(Value* LHS,Value* RHS);
static Value *GetMaximumShiftAmount(Value *LHS, Value *RHS);

// Used for shifting constraints for OpenCL, do mask for powers of 2, URem for
// non powers of two.
Expand Down Expand Up @@ -4115,13 +4115,21 @@ Value *ScalarExprEmitter::EmitSub(const BinOpInfo &op) {
return Builder.CreateExactSDiv(diffInChars, divisor, "sub.ptr.div");
}

Value *ScalarExprEmitter::GetWidthMinusOneValue(Value* LHS,Value* RHS) {
Value *ScalarExprEmitter::GetMaximumShiftAmount(Value *LHS, Value *RHS) {
llvm::IntegerType *Ty;
if (llvm::VectorType *VT = dyn_cast<llvm::VectorType>(LHS->getType()))
Ty = cast<llvm::IntegerType>(VT->getElementType());
else
Ty = cast<llvm::IntegerType>(LHS->getType());
return llvm::ConstantInt::get(RHS->getType(), Ty->getBitWidth() - 1);
// For a given type of LHS the maximum shift amount is width(LHS)-1, however
// it can occur that width(LHS)-1 > range(RHS). Since there is no check for
// this in ConstantInt::get, this results in the value getting truncated.
// Constrain the return value to be max(RHS) in this case.
llvm::Type *RHSTy = RHS->getType();
llvm::APInt RHSMax = llvm::APInt::getMaxValue(RHSTy->getScalarSizeInBits());
if (RHSMax.ult(Ty->getBitWidth()))
return llvm::ConstantInt::get(RHSTy, RHSMax);
return llvm::ConstantInt::get(RHSTy, Ty->getBitWidth() - 1);
}

Value *ScalarExprEmitter::ConstrainShiftValue(Value *LHS, Value *RHS,
Expand All @@ -4133,7 +4141,7 @@ Value *ScalarExprEmitter::ConstrainShiftValue(Value *LHS, Value *RHS,
Ty = cast<llvm::IntegerType>(LHS->getType());

if (llvm::isPowerOf2_64(Ty->getBitWidth()))
return Builder.CreateAnd(RHS, GetWidthMinusOneValue(LHS, RHS), Name);
return Builder.CreateAnd(RHS, GetMaximumShiftAmount(LHS, RHS), Name);

return Builder.CreateURem(
RHS, llvm::ConstantInt::get(RHS->getType(), Ty->getBitWidth()), Name);
Expand Down Expand Up @@ -4166,7 +4174,7 @@ Value *ScalarExprEmitter::EmitShl(const BinOpInfo &Ops) {
isa<llvm::IntegerType>(Ops.LHS->getType())) {
CodeGenFunction::SanitizerScope SanScope(&CGF);
SmallVector<std::pair<Value *, SanitizerMask>, 2> Checks;
llvm::Value *WidthMinusOne = GetWidthMinusOneValue(Ops.LHS, Ops.RHS);
llvm::Value *WidthMinusOne = GetMaximumShiftAmount(Ops.LHS, Ops.RHS);
llvm::Value *ValidExponent = Builder.CreateICmpULE(Ops.RHS, WidthMinusOne);

if (SanitizeExponent) {
Expand All @@ -4184,7 +4192,7 @@ Value *ScalarExprEmitter::EmitShl(const BinOpInfo &Ops) {
Builder.CreateCondBr(ValidExponent, CheckShiftBase, Cont);
llvm::Value *PromotedWidthMinusOne =
(RHS == Ops.RHS) ? WidthMinusOne
: GetWidthMinusOneValue(Ops.LHS, RHS);
: GetMaximumShiftAmount(Ops.LHS, RHS);
CGF.EmitBlock(CheckShiftBase);
llvm::Value *BitsShiftedOff = Builder.CreateLShr(
Ops.LHS, Builder.CreateSub(PromotedWidthMinusOne, RHS, "shl.zeros",
Expand Down Expand Up @@ -4235,7 +4243,7 @@ Value *ScalarExprEmitter::EmitShr(const BinOpInfo &Ops) {
isa<llvm::IntegerType>(Ops.LHS->getType())) {
CodeGenFunction::SanitizerScope SanScope(&CGF);
llvm::Value *Valid =
Builder.CreateICmpULE(RHS, GetWidthMinusOneValue(Ops.LHS, RHS));
Builder.CreateICmpULE(Ops.RHS, GetMaximumShiftAmount(Ops.LHS, Ops.RHS));
EmitBinOpCheck(std::make_pair(Valid, SanitizerKind::ShiftExponent), Ops);
}

Expand Down
20 changes: 11 additions & 9 deletions clang/lib/Driver/Driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1443,16 +1443,18 @@ Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
const ToolChain &TC = getToolChain(
*UArgs, computeTargetTriple(*this, TargetTriple, *UArgs));

// Check if the environment version is valid.
// Check if the environment version is valid except wasm case.
llvm::Triple Triple = TC.getTriple();
StringRef TripleVersionName = Triple.getEnvironmentVersionString();
StringRef TripleObjectFormat =
Triple.getObjectFormatTypeName(Triple.getObjectFormat());
if (Triple.getEnvironmentVersion().empty() && TripleVersionName != "" &&
TripleVersionName != TripleObjectFormat) {
Diags.Report(diag::err_drv_triple_version_invalid)
<< TripleVersionName << TC.getTripleString();
ContainsError = true;
if (!Triple.isWasm()) {
StringRef TripleVersionName = Triple.getEnvironmentVersionString();
StringRef TripleObjectFormat =
Triple.getObjectFormatTypeName(Triple.getObjectFormat());
if (Triple.getEnvironmentVersion().empty() && TripleVersionName != "" &&
TripleVersionName != TripleObjectFormat) {
Diags.Report(diag::err_drv_triple_version_invalid)
<< TripleVersionName << TC.getTripleString();
ContainsError = true;
}
}

// Report warning when arm64EC option is overridden by specified target
Expand Down
4 changes: 2 additions & 2 deletions clang/lib/Format/BreakableToken.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -449,11 +449,11 @@ const FormatToken &BreakableComment::tokenAt(unsigned LineIndex) const {

static bool mayReflowContent(StringRef Content) {
Content = Content.trim(Blanks);
// Lines starting with '@' commonly have special meaning.
// Lines starting with '@' or '\' commonly have special meaning.
// Lines starting with '-', '-#', '+' or '*' are bulleted/numbered lists.
bool hasSpecialMeaningPrefix = false;
for (StringRef Prefix :
{"@", "TODO", "FIXME", "XXX", "-# ", "- ", "+ ", "* "}) {
{"@", "\\", "TODO", "FIXME", "XXX", "-# ", "- ", "+ ", "* "}) {
if (Content.starts_with(Prefix)) {
hasSpecialMeaningPrefix = true;
break;
Expand Down
10 changes: 10 additions & 0 deletions clang/lib/Headers/cpuid.h
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@
#define bit_AMXINT8 0x02000000

/* Features in %eax for leaf 7 sub-leaf 1 */
#define bit_SHA512 0x00000001
#define bit_SM3 0x00000002
#define bit_SM4 0x00000004
#define bit_RAOINT 0x00000008
#define bit_AVXVNNI 0x00000010
#define bit_AVX512BF16 0x00000020
Expand All @@ -211,7 +214,11 @@
/* Features in %edx for leaf 7 sub-leaf 1 */
#define bit_AVXVNNIINT8 0x00000010
#define bit_AVXNECONVERT 0x00000020
#define bit_AMXCOMPLEX 0x00000100
#define bit_AVXVNNIINT16 0x00000400
#define bit_PREFETCHI 0x00004000
#define bit_USERMSR 0x00008000
#define bit_AVX10 0x00080000

/* Features in %eax for leaf 13 sub-leaf 1 */
#define bit_XSAVEOPT 0x00000001
Expand Down Expand Up @@ -244,6 +251,9 @@
#define bit_RDPRU 0x00000010
#define bit_WBNOINVD 0x00000200

/* Features in %ebx for leaf 0x24 */
#define bit_AVX10_256 0x00020000
#define bit_AVX10_512 0x00040000

#if __i386__
#define __cpuid(__leaf, __eax, __ebx, __ecx, __edx) \
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/Lex/PPDirectives.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3288,7 +3288,7 @@ void Preprocessor::HandleIfdefDirective(Token &Result,
return;
}

emitMacroExpansionWarnings(MacroNameTok);
emitMacroExpansionWarnings(MacroNameTok, /*IsIfnDef=*/true);

// Check to see if this is the last token on the #if[n]def line.
CheckEndOfDirective(isIfndef ? "ifndef" : "ifdef");
Expand Down
4 changes: 3 additions & 1 deletion clang/lib/Lex/PPExpressions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,9 @@ static bool EvaluateDefined(PPValue &Result, Token &PeekTok, DefinedTracker &DT,
Result.Val.setIsUnsigned(false); // Result is signed intmax_t.
DT.IncludedUndefinedIds = !Macro;

PP.emitMacroExpansionWarnings(PeekTok);
PP.emitMacroExpansionWarnings(
PeekTok,
(II->getName() == "INFINITY" || II->getName() == "NAN") ? true : false);

// If there is a macro, mark it used.
if (Result.Val != 0 && ValueLive)
Expand Down
103 changes: 54 additions & 49 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10440,6 +10440,60 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
diag::ext_operator_new_delete_declared_inline)
<< NewFD->getDeclName();

if (Expr *TRC = NewFD->getTrailingRequiresClause()) {
// C++20 [dcl.decl.general]p4:
// The optional requires-clause in an init-declarator or
// member-declarator shall be present only if the declarator declares a
// templated function.
//
// C++20 [temp.pre]p8:
// An entity is templated if it is
// - a template,
// - an entity defined or created in a templated entity,
// - a member of a templated entity,
// - an enumerator for an enumeration that is a templated entity, or
// - the closure type of a lambda-expression appearing in the
// declaration of a templated entity.
//
// [Note 6: A local class, a local or block variable, or a friend
// function defined in a templated entity is a templated entity.
// — end note]
//
// A templated function is a function template or a function that is
// templated. A templated class is a class template or a class that is
// templated. A templated variable is a variable template or a variable
// that is templated.
if (!FunctionTemplate) {
if (isFunctionTemplateSpecialization || isMemberSpecialization) {
// C++ [temp.expl.spec]p8 (proposed resolution for CWG2847):
// An explicit specialization shall not have a trailing
// requires-clause unless it declares a function template.
//
// Since a friend function template specialization cannot be
// definition, and since a non-template friend declaration with a
// trailing requires-clause must be a definition, we diagnose
// friend function template specializations with trailing
// requires-clauses on the same path as explicit specializations
// even though they aren't necessarily prohibited by the same
// language rule.
Diag(TRC->getBeginLoc(), diag::err_non_temp_spec_requires_clause)
<< isFriend;
} else if (isFriend && NewFD->isTemplated() &&
!D.isFunctionDefinition()) {
// C++ [temp.friend]p9:
// A non-template friend declaration with a requires-clause shall be
// a definition.
Diag(NewFD->getBeginLoc(),
diag::err_non_temp_friend_decl_with_requires_clause_must_be_def);
NewFD->setInvalidDecl();
} else if (!NewFD->isTemplated() ||
!(isa<CXXMethodDecl>(NewFD) || D.isFunctionDefinition())) {
Diag(TRC->getBeginLoc(),
diag::err_constrained_non_templated_function);
}
}
}

// We do not add HD attributes to specializations here because
// they may have different constexpr-ness compared to their
// templates and, after maybeAddCUDAHostDeviceAttrs() is applied,
Expand Down Expand Up @@ -12063,55 +12117,6 @@ bool Sema::CheckFunctionDeclaration(Scope *S, FunctionDecl *NewFD,
checkThisInStaticMemberFunctionType(Method);
}

if (Expr *TRC = NewFD->getTrailingRequiresClause()) {
// C++20: dcl.decl.general p4:
// The optional requires-clause ([temp.pre]) in an init-declarator or
// member-declarator shall be present only if the declarator declares a
// templated function ([dcl.fct]).
//
// [temp.pre]/8:
// An entity is templated if it is
// - a template,
// - an entity defined ([basic.def]) or created ([class.temporary]) in a
// templated entity,
// - a member of a templated entity,
// - an enumerator for an enumeration that is a templated entity, or
// - the closure type of a lambda-expression ([expr.prim.lambda.closure])
// appearing in the declaration of a templated entity. [Note 6: A local
// class, a local or block variable, or a friend function defined in a
// templated entity is a templated entity. — end note]
//
// A templated function is a function template or a function that is
// templated. A templated class is a class template or a class that is
// templated. A templated variable is a variable template or a variable
// that is templated.

bool IsTemplate = NewFD->getDescribedFunctionTemplate();
bool IsFriend = NewFD->getFriendObjectKind();
if (!IsTemplate && // -a template
// defined... in a templated entity
!(DeclIsDefn && NewFD->isTemplated()) &&
// a member of a templated entity
!(isa<CXXMethodDecl>(NewFD) && NewFD->isTemplated()) &&
// Don't complain about instantiations, they've already had these
// rules + others enforced.
!NewFD->isTemplateInstantiation() &&
// If the function violates [temp.friend]p9 because it is missing
// a definition, and adding a definition would make it templated,
// then let that error take precedence.
!(!DeclIsDefn && IsFriend && NewFD->isTemplated())) {
Diag(TRC->getBeginLoc(), diag::err_constrained_non_templated_function);
} else if (!DeclIsDefn && !IsTemplate && IsFriend &&
!NewFD->isTemplateInstantiation()) {
// C++ [temp.friend]p9:
// A non-template friend declaration with a requires-clause shall be a
// definition.
Diag(NewFD->getBeginLoc(),
diag::err_non_temp_friend_decl_with_requires_clause_must_be_def);
NewFD->setInvalidDecl();
}
}

if (CXXConversionDecl *Conversion = dyn_cast<CXXConversionDecl>(NewFD))
ActOnConversionDeclarator(Conversion);

Expand Down
5 changes: 4 additions & 1 deletion clang/lib/Sema/SemaOverload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2177,7 +2177,10 @@ static bool IsStandardConversion(Sema &S, Expr* From, QualType ToType,
From->isIntegerConstantExpr(S.getASTContext())) {
SCS.Second = ICK_Compatible_Conversion;
FromType = ToType;
} else if (ToType->isFixedPointType() || FromType->isFixedPointType()) {
} else if ((ToType->isFixedPointType() &&
FromType->isConvertibleToFixedPointType()) ||
(FromType->isFixedPointType() &&
ToType->isConvertibleToFixedPointType())) {
SCS.Second = ICK_Fixed_Point_Conversion;
FromType = ToType;
} else {
Expand Down
15 changes: 9 additions & 6 deletions clang/lib/StaticAnalyzer/Checkers/WebKit/ASTUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,16 @@ tryToFindPtrOrigin(const Expr *E, bool StopAtFirstRefCountedObj) {
}
if (auto *call = dyn_cast<CallExpr>(E)) {
if (auto *memberCall = dyn_cast<CXXMemberCallExpr>(call)) {
std::optional<bool> IsGetterOfRefCt = isGetterOfRefCounted(memberCall->getMethodDecl());
if (IsGetterOfRefCt && *IsGetterOfRefCt) {
E = memberCall->getImplicitObjectArgument();
if (StopAtFirstRefCountedObj) {
return {E, true};
if (auto *decl = memberCall->getMethodDecl()) {
std::optional<bool> IsGetterOfRefCt =
isGetterOfRefCounted(memberCall->getMethodDecl());
if (IsGetterOfRefCt && *IsGetterOfRefCt) {
E = memberCall->getImplicitObjectArgument();
if (StopAtFirstRefCountedObj) {
return {E, true};
}
continue;
}
continue;
}
}

Expand Down
28 changes: 28 additions & 0 deletions clang/test/AST/Interp/complex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,16 @@ static_assert(__imag(I3) == 0, "");
// constexpr _Complex _BitInt(8) A = 0;// = {4};


constexpr _Complex double Doubles[4] = {{1.0, 2.0}};
static_assert(__real(Doubles[0]) == 1.0, "");
static_assert(__imag(Doubles[0]) == 2.0, "");
static_assert(__real(Doubles[1]) == 0.0, "");
static_assert(__imag(Doubles[1]) == 0.0, "");
static_assert(__real(Doubles[2]) == 0.0, "");
static_assert(__imag(Doubles[2]) == 0.0, "");
static_assert(__real(Doubles[3]) == 0.0, "");
static_assert(__imag(Doubles[3]) == 0.0, "");

void func(void) {
__complex__ int arr;
_Complex int result;
Expand Down Expand Up @@ -194,3 +204,21 @@ namespace ZeroInit {

constexpr int ignored = (fcomplex(), 0);
}

namespace DeclRefCopy {
constexpr _Complex int ComplexInt = 42 + 24i;

constexpr _Complex int B = ComplexInt;
constexpr _Complex int ArrayOfComplexInt[4] = {ComplexInt, ComplexInt, ComplexInt, ComplexInt};
static_assert(__real(ArrayOfComplexInt[0]) == 42, "");
static_assert(__imag(ArrayOfComplexInt[0]) == 24, "");
static_assert(__real(ArrayOfComplexInt[3]) == 42, "");
static_assert(__imag(ArrayOfComplexInt[3]) == 24, "");

constexpr int localComplexArray() {
_Complex int A = 42 + 24i;
_Complex int ArrayOfComplexInt[4] = {A, A, A, A};
return __real(ArrayOfComplexInt[0]) + __imag(ArrayOfComplexInt[3]);
}
static_assert(localComplexArray() == (24 + 42), "");
}
5 changes: 5 additions & 0 deletions clang/test/AST/fixed-point-zero-init.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// RUN: %clang_cc1 -ffixed-point %s -verify
// expected-no-diagnostics

constexpr _Accum a[2] = {};
static_assert(a[0] == 0 && a[0] != 1);
1 change: 1 addition & 0 deletions clang/test/AST/ms-constexpr.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// RUN: %clang_cc1 -fms-compatibility -fms-compatibility-version=19.33 -std=c++20 -ast-dump -verify %s | FileCheck %s
// RUN: %clang_cc1 -fms-compatibility -fms-compatibility-version=19.33 -std=c++20 -ast-dump -verify %s -fexperimental-new-constant-interpreter | FileCheck %s
// expected-no-diagnostics

// CHECK: used f1 'bool ()'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UncountedLocalVarsChecker -verify %s

#include "mock-types.h"

class RenderStyle;

class FillLayer {
public:
void ref() const;
void deref() const;
};

class FillLayersPropertyWrapper {
public:
typedef const FillLayer& (RenderStyle::*LayersGetter)() const;

private:
bool canInterpolate(const RenderStyle& from) const
{
auto* fromLayer = &(from.*m_layersGetter)();
// expected-warning@-1{{Local variable 'fromLayer' is uncounted and unsafe}}
return true;
}

LayersGetter m_layersGetter;
};
60 changes: 60 additions & 0 deletions clang/test/CXX/drs/dr28xx.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// RUN: %clang_cc1 -std=c++98 -verify=expected %s
// RUN: %clang_cc1 -std=c++11 -verify=expected %s
// RUN: %clang_cc1 -std=c++14 -verify=expected %s
// RUN: %clang_cc1 -std=c++17 -verify=expected %s
// RUN: %clang_cc1 -std=c++20 -verify=expected,since-cxx20 %s
// RUN: %clang_cc1 -std=c++23 -verify=expected,since-cxx20,since-cxx23 %s
// RUN: %clang_cc1 -std=c++2c -verify=expected,since-cxx20,since-cxx23,since-cxx26 %s

#if __cplusplus < 202002L
// expected-no-diagnostics
#endif

namespace dr2847 { // dr2847: 19

#if __cplusplus >= 202002L

template<typename>
void i();

struct A {
template<typename>
void f() requires true;

template<>
void f<int>() requires true;
// since-cxx20-error@-1 {{explicit specialization cannot have a trailing requires clause unless it declares a function template}}

friend void i<int>() requires true;
// since-cxx20-error@-1 {{friend specialization cannot have a trailing requires clause unless it declares a function template}}
};

template<typename>
struct B {
void f() requires true;

template<typename>
void g() requires true;

template<typename>
void h() requires true;

template<>
void h<int>() requires true;
// since-cxx20-error@-1 {{explicit specialization cannot have a trailing requires clause unless it declares a function template}}

friend void i<int>() requires true;
// since-cxx20-error@-1 {{friend specialization cannot have a trailing requires clause unless it declares a function template}}
};

template<>
void B<int>::f() requires true;
// since-cxx20-error@-1 {{explicit specialization cannot have a trailing requires clause unless it declares a function template}}

template<>
template<typename T>
void B<int>::g() requires true;

#endif

} // namespace dr2847
12 changes: 6 additions & 6 deletions clang/test/CodeGen/aarch64-neon-intrinsics.c
Original file line number Diff line number Diff line change
Expand Up @@ -14132,24 +14132,24 @@ int32_t test_vqshrund_n_s64(int64_t a) {
// CHECK: [[VQRSHRUNH_N_S16:%.*]] = call <8 x i8> @llvm.aarch64.neon.sqrshrun.v8i8(<8 x i16> [[TMP0]], i32 8)
// CHECK: [[TMP1:%.*]] = extractelement <8 x i8> [[VQRSHRUNH_N_S16]], i64 0
// CHECK: ret i8 [[TMP1]]
int8_t test_vqrshrunh_n_s16(int16_t a) {
return (int8_t)vqrshrunh_n_s16(a, 8);
uint8_t test_vqrshrunh_n_s16(int16_t a) {
return (uint8_t)vqrshrunh_n_s16(a, 8);
}

// CHECK-LABEL: @test_vqrshruns_n_s32(
// CHECK: [[TMP0:%.*]] = insertelement <4 x i32> poison, i32 %a, i64 0
// CHECK: [[VQRSHRUNS_N_S32:%.*]] = call <4 x i16> @llvm.aarch64.neon.sqrshrun.v4i16(<4 x i32> [[TMP0]], i32 16)
// CHECK: [[TMP1:%.*]] = extractelement <4 x i16> [[VQRSHRUNS_N_S32]], i64 0
// CHECK: ret i16 [[TMP1]]
int16_t test_vqrshruns_n_s32(int32_t a) {
return (int16_t)vqrshruns_n_s32(a, 16);
uint16_t test_vqrshruns_n_s32(int32_t a) {
return (uint16_t)vqrshruns_n_s32(a, 16);
}

// CHECK-LABEL: @test_vqrshrund_n_s64(
// CHECK: [[VQRSHRUND_N_S64:%.*]] = call i32 @llvm.aarch64.neon.sqrshrun.i32(i64 %a, i32 32)
// CHECK: ret i32 [[VQRSHRUND_N_S64]]
int32_t test_vqrshrund_n_s64(int64_t a) {
return (int32_t)vqrshrund_n_s64(a, 32);
uint32_t test_vqrshrund_n_s64(int64_t a) {
return (uint32_t)vqrshrund_n_s64(a, 32);
}

// CHECK-LABEL: @test_vcvts_n_f32_s32(
Expand Down
36 changes: 36 additions & 0 deletions clang/test/CodeGen/ubsan-shift-bitint.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// RUN: %clang_cc1 %s -O0 -fsanitize=shift-exponent -emit-llvm -std=c2x -triple=x86_64-unknown-linux -o - | FileCheck %s

// Checking that the code generation is using the unextended/untruncated
// exponent values and capping the values accordingly

// CHECK-LABEL: define{{.*}} i32 @test_left_variable
int test_left_variable(unsigned _BitInt(5) b, unsigned _BitInt(2) e) {
// CHECK: [[E_REG:%.+]] = load [[E_SIZE:i2]]
// CHECK: icmp ule [[E_SIZE]] [[E_REG]], -1
return b << e;
}

// CHECK-LABEL: define{{.*}} i32 @test_right_variable
int test_right_variable(unsigned _BitInt(2) b, unsigned _BitInt(3) e) {
// CHECK: [[E_REG:%.+]] = load [[E_SIZE:i3]]
// CHECK: icmp ule [[E_SIZE]] [[E_REG]], 1
return b >> e;
}

// Old code generation would give false positives on left shifts when:
// value(e) > (width(b) - 1 % 2 ** width(e))
// CHECK-LABEL: define{{.*}} i32 @test_left_literal
int test_left_literal(unsigned _BitInt(5) b) {
// CHECK-NOT: br i1 false, label %cont, label %handler.shift_out_of_bounds
// CHECK: br i1 true, label %cont, label %handler.shift_out_of_bounds
return b << 3uwb;
}

// Old code generation would give false positives on right shifts when:
// (value(e) % 2 ** width(b)) < width(b)
// CHECK-LABEL: define{{.*}} i32 @test_right_literal
int test_right_literal(unsigned _BitInt(2) b) {
// CHECK-NOT: br i1 true, label %cont, label %handler.shift_out_of_bounds
// CHECK: br i1 false, label %cont, label %handler.shift_out_of_bounds
return b >> 4uwb;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,18 @@
// RUN: FileCheck --check-prefix=CHECK-TARGET %s

// CHECK-TARGET: "aarch64-unknown-linux-android31"

// RUN: not %clang --target=armv7-linux-gnuS -c %s -### 2>&1 | \
// RUN: FileCheck --check-prefix=CHECK-ERROR2 %s

// CHECK-ERROR2: error: version 'S' in target triple 'armv7-unknown-linux-gnuS' is invalid

// RUN: %clang --target=wasm32-unknown-wasi-preview2 -c %s -### 2>&1 | \
// RUN: FileCheck --check-prefix=CHECK-WASM %s

// CHECK-WASM: "-triple" "wasm32-unknown-wasi-preview2"

// RUN: %clang --target=wasm32-wasi-pthread -c %s -### 2>&1 | \
// RUN: FileCheck --check-prefix=CHECK-WASM1 %s

// CHECK-WASM1: "-triple" "wasm32-unknown-wasi-pthread"
70 changes: 54 additions & 16 deletions clang/test/Driver/wasm-features.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,51 +2,89 @@

// CHECK: "-fvisibility=hidden"

// RUN: %clang --target=wasm32-unknown-unknown -### %s 2>&1 | FileCheck %s -check-prefix=DEFAULT
// RUN: %clang --target=wasm32-unknown-unknown -### %s -mcpu=mvp 2>&1 | FileCheck %s -check-prefix=MVP
// RUN: %clang --target=wasm32-unknown-unknown -### %s 2>&1 | FileCheck %s -check-prefix=GENERIC
// RUN: %clang --target=wasm32-unknown-unknown -### %s -mcpu=generic 2>&1 | FileCheck %s -check-prefix=GENERIC
// RUN: %clang --target=wasm32-unknown-unknown -### %s -mcpu=bleeding-edge 2>&1 | FileCheck %s -check-prefix=BLEEDING-EDGE

// MVP: "-target-cpu" "mvp"
// GENERIC: "-target-cpu" "generic"
// BLEEDING-EDGE: "-target-cpu" "bleeding-edge"

// RUN: %clang --target=wasm32-unknown-unknown -### %s -mbulk-memory 2>&1 | FileCheck %s -check-prefix=BULK-MEMORY
// RUN: %clang --target=wasm32-unknown-unknown -### %s -mno-bulk-memory 2>&1 | FileCheck %s -check-prefix=NO-BULK-MEMORY

// BULK-MEMORY: "-target-feature" "+bulk-memory"
// NO-BULK-MEMORY: "-target-feature" "-bulk-memory"
// DEFAULT-NOT: "-target-feature" "-bulk-memory"
// MVP-NOT: "-target-feature" "+bulk-memory"
// BLEEDING-EDGE-NOT: "-target-feature" "-bulk-memory"

// RUN: %clang --target=wasm32-unknown-unknown -### %s -mmutable-globals 2>&1 | FileCheck %s -check-prefix=MUTABLE-GLOBALS
// RUN: %clang --target=wasm32-unknown-unknown -### %s -mno-mutable-globals 2>&1 | FileCheck %s -check-prefix=NO-MUTABLE-GLOBALS

// MUTABLE-GLOBALS: "-target-feature" "+mutable-globals"
// NO-MUTABLE-GLOBALS: "-target-feature" "-mutable-globals"
// DEFAULT-NOT: "-target-feature" "-mutable-globals"
// MVP-NOT: "-target-feature" "+mutable-globals"
// BLEEDING-EDGE-NOT: "-target-feature" "-mutable-globals"

// RUN: %clang --target=wasm32-unknown-unknown -### %s -msign-ext 2>&1 | FileCheck %s -check-prefix=SIGN-EXT
// RUN: %clang --target=wasm32-unknown-unknown -### %s -mno-sign-ext 2>&1 | FileCheck %s -check-prefix=NO-SIGN-EXT

// SIGN-EXT: "-target-feature" "+sign-ext"
// NO-SIGN-EXT: "-target-feature" "-sign-ext"
// DEFAULT-NOT: "-target-feature" "-sign-ext"
// MVP-NOT: "-target-feature" "+sign-ext"
// BLEEDING-EDGE-NOT: "-target-feature" "-sign-ext"

// RUN: %clang --target=wasm32-unknown-unknown -### %s -mnontrapping-fptoint 2>&1 | FileCheck %s -check-prefix=NONTRAPPING-FPTOINT
// RUN: %clang --target=wasm32-unknown-unknown -### %s -mno-nontrapping-fptoint 2>&1 | FileCheck %s -check-prefix=NO-NONTRAPPING-FPTOINT

// NONTRAPPING-FPTOINT: "-target-feature" "+nontrapping-fptoint"
// NO-NONTRAPPING-FPTOINT: "-target-feature" "-nontrapping-fptoint"
// DEFAULT-NOT: "-target-feature" "-nontrapping-fptoint"
// MVP-NOT: "-target-feature" "+nontrapping-fptoint"
// BLEEDING-EDGE-NOT: "-target-feature" "-nontrapping-fptoint"

// RUN: %clang --target=wasm32-unknown-unknown -### %s -mmultivalue 2>&1 | FileCheck %s -check-prefix=MULTIVALUE
// RUN: %clang --target=wasm32-unknown-unknown -### %s -mno-multivalue 2>&1 | FileCheck %s -check-prefix=NO-MULTIVALUE

// MULTIVALUE: "-target-feature" "+multivalue"
// NO-MULTIVALUE: "-target-feature" "-multivalue"

// RUN: %clang --target=wasm32-unknown-unknown -### %s -mmultimemory 2>&1 | FileCheck %s -check-prefix=MULTIMEMORY
// RUN: %clang --target=wasm32-unknown-unknown -### %s -mno-multimemory 2>&1 | FileCheck %s -check-prefix=NO-MULTIMEMORY

// MULTIMEMORY: "-target-feature" "+multimemory"
// NO-MULTIMEMORY: "-target-feature" "-multimemory"
// DEFAULT-NOT: "-target-feature" "-multimemory"
// MVP-NOT: "-target-feature" "+multimemory"
// BLEEDING-EDGE-NOT: "-target-feature" "-multimemory"

// RUN: %clang --target=wasm32-unknown-unknown -### %s -matomics 2>&1 | FileCheck %s -check-prefix=ATOMICS
// RUN: %clang --target=wasm32-unknown-unknown -### %s -mno-atomics 2>&1 | FileCheck %s -check-prefix=NO-ATOMICS

// ATOMICS: "-target-feature" "+atomics"
// NO-ATOMICS: "-target-feature" "-atomics"

// RUN: %clang --target=wasm32-unknown-unknown -### %s -mtail-call 2>&1 | FileCheck %s -check-prefix=TAIL-CALL
// RUN: %clang --target=wasm32-unknown-unknown -### %s -mno-tail-call 2>&1 | FileCheck %s -check-prefix=NO-TAIL-CALL

// TAIL-CALL: "-target-feature" "+tail-call"
// NO-TAIL-CALL: "-target-feature" "-tail-call"

// RUN: %clang --target=wasm32-unknown-unknown -### %s -mreference-types 2>&1 | FileCheck %s -check-prefix=REFERENCE-TYPES
// RUN: %clang --target=wasm32-unknown-unknown -### %s -mno-reference-types 2>&1 | FileCheck %s -check-prefix=NO-REFERENCE-TYPES

// REFERENCE-TYPES: "-target-feature" "+reference-types"
// NO-REFERENCE-TYPES: "-target-feature" "-reference-types"

// RUN: %clang --target=wasm32-unknown-unknown -### %s -msimd128 2>&1 | FileCheck %s -check-prefix=SIMD128
// RUN: %clang --target=wasm32-unknown-unknown -### %s -mno-simd128 2>&1 | FileCheck %s -check-prefix=NO-SIMD128

// SIMD128: "-target-feature" "+simd128"
// NO-SIMD128: "-target-feature" "-simd128"

// RUN: %clang --target=wasm32-unknown-unknown -### %s -mrelaxed-simd 2>&1 | FileCheck %s -check-prefix=RELAXED-SIMD
// RUN: %clang --target=wasm32-unknown-unknown -### %s -mno-relaxed-simd 2>&1 | FileCheck %s -check-prefix=NO-RELAXED-SIMD

// RELAXED-SIMD: "-target-feature" "+relaxed-simd"
// NO-RELAXED-SIMD: "-target-feature" "-relaxed-simd"

// RUN: %clang --target=wasm32-unknown-unknown -### %s -mexception-handling 2>&1 | FileCheck %s -check-prefix=EXCEPTION-HANDLING
// RUN: %clang --target=wasm32-unknown-unknown -### %s -mno-exception-handling 2>&1 | FileCheck %s -check-prefix=NO-EXCEPTION-HANDLING

// EXCEPTION-HANDLING: "-target-feature" "+exception-handling"
// NO-EXCEPTION-HANDLING: "-target-feature" "-exception-handling"

// RUN: %clang --target=wasm32-unknown-unknown -### %s -mextended-const 2>&1 | FileCheck %s -check-prefix=EXTENDED-CONST
// RUN: %clang --target=wasm32-unknown-unknown -### %s -mno-extended-const 2>&1 | FileCheck %s -check-prefix=NO-EXTENDED-CONST

// EXTENDED-CONST: "-target-feature" "+extended-const"
// NO-EXTENDED-CONST: "-target-feature" "-extended-const"
1 change: 0 additions & 1 deletion clang/test/Format/clang-format-ignore.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// UNSUPPORTED: system-windows
// RUN: rm -rf %t.dir
// RUN: mkdir -p %t.dir/level1/level2

Expand Down
4 changes: 4 additions & 0 deletions clang/test/Frontend/fixed_point_errors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,7 @@ int fract_int = 10r; // expected-error{{invalid suffix 'r' on integer consta
float accum_flt = 0.0k; // expected-error{{invalid suffix 'k' on floating constant}}
float fract_flt = 0.0r; // expected-error{{invalid suffix 'r' on floating constant}}
#endif

#ifndef WITHOUT_FIXED_POINT
const char *c = 10.0k; // expected-error{{cannot initialize a variable of type 'const char *' with an rvalue of type '_Accum'}}
#endif
17 changes: 13 additions & 4 deletions clang/test/Preprocessor/riscv-target-features.c
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@
// CHECK-NOT: __riscv_smnpm{{.*$}}
// CHECK-NOT: __riscv_ssnpm{{.*$}}
// CHECK-NOT: __riscv_sspm{{.*$}}
// CHECK-NOT: __riscv_ssqosid{{.*$}}
// CHECK-NOT: __riscv_supm{{.*$}}
// CHECK-NOT: __riscv_zaamo {{.*$}}
// CHECK-NOT: __riscv_zacas {{.*$}}
Expand Down Expand Up @@ -1599,19 +1600,27 @@
// RUN: %clang --target=riscv32 -menable-experimental-extensions \
// RUN: -march=rv32i_sspm0p8 -E -dM %s \
// RUN: -o - | FileCheck --check-prefix=CHECK-SSPM-EXT %s
// RUN: %clang --target=riscv64 \
// RUN: -march=rv64i_sspm0p8 -E -dM %s -menable-experimental-extensions \
// RUN: %clang --target=riscv64 -menable-experimental-extensions \
// RUN: -march=rv64i_sspm0p8 -E -dM %s \
// RUN: -o - | FileCheck --check-prefix=CHECK-SSPM-EXT %s
// CHECK-SSPM-EXT: __riscv_sspm 8000{{$}}

// RUN: %clang --target=riscv32 -menable-experimental-extensions \
// RUN: -march=rv32i_supm0p8 -E -dM %s \
// RUN: -o - | FileCheck --check-prefix=CHECK-SUPM-EXT %s
// RUN: %clang --target=riscv64 \
// RUN: -march=rv64i_supm0p8 -E -dM %s -menable-experimental-extensions \
// RUN: %clang --target=riscv64 -menable-experimental-extensions \
// RUN: -march=rv64i_supm0p8 -E -dM %s \
// RUN: -o - | FileCheck --check-prefix=CHECK-SUPM-EXT %s
// CHECK-SUPM-EXT: __riscv_supm 8000{{$}}

// RUN: %clang --target=riscv32 -menable-experimental-extensions \
// RUN: -march=rv32i_ssqosid1p0 -E -dM %s \
// RUN: -o - | FileCheck --check-prefix=CHECK-SSQOSID-EXT %s
// RUN: %clang --target=riscv64 -menable-experimental-extensions \
// RUN: -march=rv64i_ssqosid1p0 -E -dM %s \
// RUN: -o - | FileCheck --check-prefix=CHECK-SSQOSID-EXT %s
// CHECK-SSQOSID-EXT: __riscv_ssqosid 1000000{{$}}

// Misaligned

// RUN: %clang --target=riscv32-unknown-linux-gnu -march=rv32i -E -dM %s \
Expand Down
23 changes: 22 additions & 1 deletion clang/test/Preprocessor/wasm-target-features.c
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,27 @@
// MVP-NOT:#define __wasm_reference_types__
// MVP-NOT:#define __wasm_extended_const__
// MVP-NOT:#define __wasm_multimemory__
// MVP-NOT:#define __wasm_relaxed_simd__

// RUN: %clang -E -dM %s -o - 2>&1 \
// RUN: -target wasm32-unknown-unknown -mcpu=generic \
// RUN: | FileCheck %s -check-prefix=GENERIC
// RUN: %clang -E -dM %s -o - 2>&1 \
// RUN: -target wasm64-unknown-unknown -mcpu=generic \
// RUN: | FileCheck %s -check-prefix=GENERIC
//
// GENERIC-DAG:#define __wasm_sign_ext__ 1{{$}}
// GENERIC-DAG:#define __wasm_mutable_globals__ 1{{$}}
// GENERIC-NOT:#define __wasm_nontrapping_fptoint__ 1{{$}}
// GENERIC-NOT:#define __wasm_bulk_memory__ 1{{$}}
// GENERIC-NOT:#define __wasm_simd128__ 1{{$}}
// GENERIC-NOT:#define __wasm_atomics__ 1{{$}}
// GENERIC-NOT:#define __wasm_tail_call__ 1{{$}}
// GENERIC-NOT:#define __wasm_multimemory__ 1{{$}}
// GENERIC-NOT:#define __wasm_exception_handling__ 1{{$}}
// GENERIC-NOT:#define __wasm_multivalue__ 1{{$}}
// GENERIC-NOT:#define __wasm_reference_types__ 1{{$}}
// GENERIC-NOT:#define __wasm_extended_const__ 1{{$}}

// RUN: %clang -E -dM %s -o - 2>&1 \
// RUN: -target wasm32-unknown-unknown -mcpu=bleeding-edge \
Expand All @@ -160,11 +181,11 @@
// BLEEDING-EDGE-DAG:#define __wasm_mutable_globals__ 1{{$}}
// BLEEDING-EDGE-DAG:#define __wasm_tail_call__ 1{{$}}
// BLEEDING-EDGE-DAG:#define __wasm_multimemory__ 1{{$}}
// BLEEDING-EDGE-NOT:#define __wasm_unimplemented_simd128__ 1{{$}}
// BLEEDING-EDGE-NOT:#define __wasm_exception_handling__ 1{{$}}
// BLEEDING-EDGE-NOT:#define __wasm_multivalue__ 1{{$}}
// BLEEDING-EDGE-NOT:#define __wasm_reference_types__ 1{{$}}
// BLEEDING-EDGE-NOT:#define __wasm_extended_const__ 1{{$}}
// BLEEDING-EDGE-NOT:#define __wasm_relaxed_simd__ 1{{$}}

// RUN: %clang -E -dM %s -o - 2>&1 \
// RUN: -target wasm32-unknown-unknown -mcpu=bleeding-edge -mno-simd128 \
Expand Down
1 change: 1 addition & 0 deletions clang/test/Sema/struct-cast.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// RUN: %clang_cc1 -fsyntax-only %s -verify
// RUN: %clang_cc1 -fsyntax-only %s -fexperimental-new-constant-interpreter -verify
// expected-no-diagnostics

struct S {
Expand Down
60 changes: 53 additions & 7 deletions clang/test/Sema/warn-infinity-nan-disabled-lnx.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
// RUN: %clang_cc1 -x c++ -verify=no-inf-no-nan -triple powerpc64le-unknown-unknown %s \
// RUN: -menable-no-infs -menable-no-nans
// RUN: %clang_cc1 -x c++ -verify=no-inf-no-nan \
// RUN: -triple powerpc64le-unknown-unknown %s -menable-no-infs \
// RUN: -menable-no-nans -std=c++23

// RUN: %clang_cc1 -x c++ -verify=no-fast -triple powerpc64le-unknown-unknown %s
// RUN: %clang_cc1 -x c++ -verify=no-inf-no-nan \
// RUN: -triple powerpc64le-unknown-unknown %s -menable-no-infs \
// RUN: -menable-no-nans -funsafe-math-optimizations -std=c++23

// RUN: %clang_cc1 -x c++ -verify=no-fast -triple powerpc64le-unknown-unknown \
// RUN: %s -std=c++23

// RUN: %clang_cc1 -x c++ -verify=no-inf -triple powerpc64le-unknown-unknown %s \
// RUN: -menable-no-infs -std=c++23

// RUN: %clang_cc1 -x c++ -verify=no-inf -triple powerpc64le-unknown-unknown %s \
// RUN: -menable-no-infs
// RUN: -menable-no-infs -funsafe-math-optimizations -std=c++23

// RUN: %clang_cc1 -x c++ -verify=no-nan -triple powerpc64le-unknown-unknown %s \
// RUN: -menable-no-nans -std=c++23

// RUN: %clang_cc1 -x c++ -verify=no-nan -triple powerpc64le-unknown-unknown %s \
// RUN: -menable-no-nans
// RUN: -funsafe-math-optimizations -menable-no-nans -std=c++23

// RUN: %clang_cc1 -x c++ -verify=no-fast -triple powerpc64le-unknown-unknown \
// RUN: %s -Wno-nan-infinity-disabled -menable-no-infs -std=c++23

// RUN: %clang_cc1 -x c++ -verify=no-fast -triple powerpc64le-unknown-unknown \
// RUN: %s -Wno-nan-infinity-disabled -menable-no-nans -std=c++23

// no-fast-no-diagnostics

Expand Down Expand Up @@ -133,13 +151,41 @@ int compareit(float a, float b) {
// no-inf-warning@+1 {{use of infinity is undefined behavior due to the currently enabled floating-point options}}
p = __builtin_isfinite(a);

// These should NOT warn, since they are not using NaN or infinity.
// These should NOT warn, since they are not using NaN or infinity.
j = a > 1.1;
j = b < 1.1;
j = a >= 1.1;
j = b <= 1.1;
j = isunorderedf(a, b);

#ifndef INFINITY
j = a;
#endif
#ifndef NAN
j = b;
#endif
#ifdef INFINITY
j = a;
#endif
#ifdef NAN
j = b;
#endif
#if defined(INFINITY)
j = a;
#elifndef(INFINITY)
j = b;
#endif
#if defined(INFINITY)
j = a;
#elifndef(NAN)
j = b;
#endif
#if defined(NAN)
j = a;
#elifndef(INFINITY)
j = b;
#endif

// no-inf-no-nan-warning@+4 {{use of NaN via a macro is undefined behavior due to the currently enabled floating-point options}}
// no-inf-no-nan-warning@+3 {{use of NaN is undefined behavior due to the currently enabled floating-point options}}
// no-nan-warning@+2 {{use of NaN via a macro is undefined behavior due to the currently enabled floating-point options}}
Expand Down Expand Up @@ -173,4 +219,4 @@ int compareit(float a, float b) {
j = numeric_limits<float>::infinity();
return 0;

}
}
60 changes: 53 additions & 7 deletions clang/test/Sema/warn-infinity-nan-disabled-win.cpp
Original file line number Diff line number Diff line change
@@ -1,16 +1,34 @@
// Use of NAN macro will trigger a warning "infinity defined in macro" because
// on Windows the NAN macro is defined using INFINITY. See below.

// RUN: %clang_cc1 -x c++ -verify=no-inf-no-nan -triple powerpc64le-unknown-unknown %s \
// RUN: -menable-no-infs -menable-no-nans
// RUN: %clang_cc1 -x c++ -verify=no-inf-no-nan \
// RUN: -triple powerpc64le-unknown-unknown %s -menable-no-infs \
// RUN: -menable-no-nans -std=c++23

// RUN: %clang_cc1 -x c++ -verify=no-fast -triple powerpc64le-unknown-unknown %s
// RUN: %clang_cc1 -x c++ -verify=no-inf-no-nan \
// RUN: -triple powerpc64le-unknown-unknown %s -menable-no-infs \
// RUN: -menable-no-nans -funsafe-math-optimizations -std=c++23

// RUN: %clang_cc1 -x c++ -verify=no-fast -triple powerpc64le-unknown-unknown \
// RUN: %s -std=c++23

// RUN: %clang_cc1 -x c++ -verify=no-inf -triple powerpc64le-unknown-unknown %s \
// RUN: -menable-no-infs -std=c++23

// RUN: %clang_cc1 -x c++ -verify=no-inf -triple powerpc64le-unknown-unknown %s \
// RUN: -menable-no-infs
// RUN: -menable-no-infs -funsafe-math-optimizations -std=c++23

// RUN: %clang_cc1 -x c++ -verify=no-nan -triple powerpc64le-unknown-unknown %s \
// RUN: -menable-no-nans -std=c++23

// RUN: %clang_cc1 -x c++ -verify=no-nan -triple powerpc64le-unknown-unknown %s \
// RUN: -menable-no-nans
// RUN: -funsafe-math-optimizations -menable-no-nans -std=c++23

// RUN: %clang_cc1 -x c++ -verify=no-fast -triple powerpc64le-unknown-unknown \
// RUN: %s -Wno-nan-infinity-disabled -menable-no-infs -std=c++23

// RUN: %clang_cc1 -x c++ -verify=no-fast -triple powerpc64le-unknown-unknown \
// RUN: %s -Wno-nan-infinity-disabled -menable-no-nans -std=c++23

// no-fast-no-diagnostics

Expand Down Expand Up @@ -136,13 +154,41 @@ int compareit(float a, float b) {
// no-inf-warning@+1 {{use of infinity is undefined behavior due to the currently enabled floating-point options}}
p = __builtin_isfinite(a);

// These should NOT warn, since they are not using NaN or infinity.
// These should NOT warn, since they are not using NaN or infinity.
j = a > 1.1;
j = b < 1.1;
j = a >= 1.1;
j = b <= 1.1;
j = isunorderedf(a, b);

#ifndef INFINITY
j = a;
#endif
#ifndef NAN
j = b;
#endif
#ifdef INFINITY
j = a;
#endif
#ifdef NAN
j = b;
#endif
#if defined(INFINITY)
j = a;
#elifndef(INFINITY)
j = b;
#endif
#if defined(INFINITY)
j = a;
#elifndef(NAN)
j = b;
#endif
#if defined(NAN)
j = a;
#elifndef(INFINITY)
j = b;
#endif

// no-inf-no-nan-warning@+4 {{use of infinity via a macro is undefined behavior due to the currently enabled floating-point option}}
// no-inf-no-nan-warning@+3 {{use of NaN via a macro is undefined behavior due to the currently enabled floating-point options}}
// no-inf-warning@+2 {{use of infinity via a macro is undefined behavior due to the currently enabled floating-point options}}
Expand Down Expand Up @@ -176,4 +222,4 @@ int compareit(float a, float b) {
j = numeric_limits<float>::infinity();
return 0;

}
}
7 changes: 7 additions & 0 deletions clang/test/SemaCXX/warn-unsafe-buffer-usage-debug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,10 @@ void test_struct_claim_use() {
x[6] = 8; // expected-warning{{unsafe buffer access}}
x++; // expected-warning{{unsafe pointer arithmetic}}
}

void use(int*);
void array2d(int idx) {
int buffer[10][5]; // expected-warning{{'buffer' is an unsafe buffer that does not perform bounds checks}}
use(buffer[idx]); // expected-note{{used in buffer access here}} \
// debug-note{{safe buffers debug: failed to produce fixit for 'buffer' : has an unclaimed use}}
}
58 changes: 28 additions & 30 deletions clang/unittests/Analysis/FlowSensitive/SignAnalysisTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -364,60 +364,58 @@ class SignPropagationAnalysis
LatticeTransferState State(L, Env);
TransferMatchSwitch(Elt, getASTContext(), State);
}
bool merge(QualType Type, const Value &Val1, const Environment &Env1,
const Value &Val2, const Environment &Env2, Value &MergedVal,
Environment &MergedEnv) override;
void join(QualType Type, const Value &Val1, const Environment &Env1,
const Value &Val2, const Environment &Env2, Value &MergedVal,
Environment &MergedEnv) override;

private:
CFGMatchSwitch<TransferState<NoopLattice>> TransferMatchSwitch;
};

// Copied from crubit.
BoolValue &mergeBoolValues(BoolValue &Bool1, const Environment &Env1,
BoolValue &Bool2, const Environment &Env2,
Environment &MergedEnv) {
BoolValue &joinBoolValues(BoolValue &Bool1, const Environment &Env1,
BoolValue &Bool2, const Environment &Env2,
Environment &JoinedEnv) {
if (&Bool1 == &Bool2) {
return Bool1;
}

auto &B1 = Bool1.formula();
auto &B2 = Bool2.formula();

auto &A = MergedEnv.arena();
auto &MergedBool = MergedEnv.makeAtomicBoolValue();
auto &A = JoinedEnv.arena();
auto &JoinedBool = JoinedEnv.makeAtomicBoolValue();

// If `Bool1` and `Bool2` is constrained to the same true / false value,
// `MergedBool` can be constrained similarly without needing to consider the
// path taken - this simplifies the flow condition tracked in `MergedEnv`.
// `JoinedBool` can be constrained similarly without needing to consider the
// path taken - this simplifies the flow condition tracked in `JoinedEnv`.
// Otherwise, information about which path was taken is used to associate
// `MergedBool` with `Bool1` and `Bool2`.
// `JoinedBool` with `Bool1` and `Bool2`.
if (Env1.proves(B1) && Env2.proves(B2)) {
MergedEnv.assume(MergedBool.formula());
JoinedEnv.assume(JoinedBool.formula());
} else if (Env1.proves(A.makeNot(B1)) && Env2.proves(A.makeNot(B2))) {
MergedEnv.assume(A.makeNot(MergedBool.formula()));
JoinedEnv.assume(A.makeNot(JoinedBool.formula()));
}
return MergedBool;
return JoinedBool;
}

bool SignPropagationAnalysis::merge(QualType Type, const Value &Val1,
const Environment &Env1, const Value &Val2,
const Environment &Env2, Value &MergedVal,
Environment &MergedEnv) {
void SignPropagationAnalysis::join(QualType Type, const Value &Val1,
const Environment &Env1, const Value &Val2,
const Environment &Env2, Value &JoinedVal,
Environment &JoinedEnv) {
if (!Type->isIntegerType())
return false;
return;
SignProperties Ps1 = getSignProperties(Val1, Env1);
SignProperties Ps2 = getSignProperties(Val2, Env2);
if (!Ps1.Neg || !Ps2.Neg)
return false;
BoolValue &MergedNeg =
mergeBoolValues(*Ps1.Neg, Env1, *Ps2.Neg, Env2, MergedEnv);
BoolValue &MergedZero =
mergeBoolValues(*Ps1.Zero, Env1, *Ps2.Zero, Env2, MergedEnv);
BoolValue &MergedPos =
mergeBoolValues(*Ps1.Pos, Env1, *Ps2.Pos, Env2, MergedEnv);
setSignProperties(MergedVal,
SignProperties{&MergedNeg, &MergedZero, &MergedPos});
return true;
return;
BoolValue &JoinedNeg =
joinBoolValues(*Ps1.Neg, Env1, *Ps2.Neg, Env2, JoinedEnv);
BoolValue &JoinedZero =
joinBoolValues(*Ps1.Zero, Env1, *Ps2.Zero, Env2, JoinedEnv);
BoolValue &JoinedPos =
joinBoolValues(*Ps1.Pos, Env1, *Ps2.Pos, Env2, JoinedEnv);
setSignProperties(JoinedVal,
SignProperties{&JoinedNeg, &JoinedZero, &JoinedPos});
}

template <typename Matcher>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -672,26 +672,23 @@ class NullPointerAnalysis final
: ComparisonResult::Different;
}

bool merge(QualType Type, const Value &Val1, const Environment &Env1,
const Value &Val2, const Environment &Env2, Value &MergedVal,
Environment &MergedEnv) override {
// Nothing to say about a value that is not a pointer.
void join(QualType Type, const Value &Val1, const Environment &Env1,
const Value &Val2, const Environment &Env2, Value &JoinedVal,
Environment &JoinedEnv) override {
// Nothing to say about a value that is not a pointer...
if (!Type->isPointerType())
return false;
return;

// ... or, a pointer without the `is_null` property.
auto *IsNull1 = cast_or_null<BoolValue>(Val1.getProperty("is_null"));
if (IsNull1 == nullptr)
return false;

auto *IsNull2 = cast_or_null<BoolValue>(Val2.getProperty("is_null"));
if (IsNull2 == nullptr)
return false;
if (IsNull1 == nullptr || IsNull2 == nullptr)
return;

if (IsNull1 == IsNull2)
MergedVal.setProperty("is_null", *IsNull1);
JoinedVal.setProperty("is_null", *IsNull1);
else
MergedVal.setProperty("is_null", MergedEnv.makeTopBoolValue());
return true;
JoinedVal.setProperty("is_null", JoinedEnv.makeTopBoolValue());
}
};

Expand Down Expand Up @@ -1176,7 +1173,7 @@ TEST_F(FlowConditionTest, Join) {
// Note: currently, arbitrary function calls are uninterpreted, so the test
// exercises this case. If and when we change that, this test will not add to
// coverage (although it may still test a valuable case).
TEST_F(FlowConditionTest, OpaqueFlowConditionMergesToOpaqueBool) {
TEST_F(FlowConditionTest, OpaqueFlowConditionJoinsToOpaqueBool) {
std::string Code = R"(
bool foo();

Expand Down Expand Up @@ -1211,7 +1208,7 @@ TEST_F(FlowConditionTest, OpaqueFlowConditionMergesToOpaqueBool) {
// the first instance), so the test exercises this case. If and when we change
// that, this test will not add to coverage (although it may still test a
// valuable case).
TEST_F(FlowConditionTest, OpaqueFieldFlowConditionMergesToOpaqueBool) {
TEST_F(FlowConditionTest, OpaqueFieldFlowConditionJoinsToOpaqueBool) {
std::string Code = R"(
struct Rec {
Rec* Next;
Expand Down Expand Up @@ -1249,7 +1246,7 @@ TEST_F(FlowConditionTest, OpaqueFieldFlowConditionMergesToOpaqueBool) {
// condition is not meaningfully interpreted. Adds to above by nesting the
// interestnig case inside a normal branch. This protects against degenerate
// solutions which only test for empty flow conditions, for example.
TEST_F(FlowConditionTest, OpaqueFlowConditionInsideBranchMergesToOpaqueBool) {
TEST_F(FlowConditionTest, OpaqueFlowConditionInsideBranchJoinsToOpaqueBool) {
std::string Code = R"(
bool foo();

Expand Down
8 changes: 8 additions & 0 deletions clang/unittests/Format/FormatTestComments.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1909,6 +1909,14 @@ TEST_F(FormatTestComments, ReflowsComments) {
"// @param arg",
getLLVMStyleWithColumns(20)));

// Don't reflow lines starting with '\'.
verifyFormat("// long long long\n"
"// long\n"
"// \\param arg",
"// long long long long\n"
"// \\param arg",
getLLVMStyleWithColumns(20));

// Don't reflow lines starting with 'TODO'.
EXPECT_EQ("// long long long\n"
"// long\n"
Expand Down
16 changes: 8 additions & 8 deletions compiler-rt/lib/memprof/memprof_allocator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -515,8 +515,7 @@ struct Allocator {
return ptr;
}

void CommitBack(MemprofThreadLocalMallocStorage *ms,
BufferedStackTrace *stack) {
void CommitBack(MemprofThreadLocalMallocStorage *ms) {
AllocatorCache *ac = GetAllocatorCache(ms);
allocator.SwallowCache(ac);
}
Expand Down Expand Up @@ -561,7 +560,7 @@ struct Allocator {
return reinterpret_cast<MemprofChunk *>(p - kChunkHeaderSize)->UsedSize();
}

void Purge(BufferedStackTrace *stack) { allocator.ForceReleaseToOS(); }
void Purge() { allocator.ForceReleaseToOS(); }

void PrintStats() { allocator.PrintStats(); }

Expand All @@ -583,8 +582,7 @@ static MemprofAllocator &get_allocator() { return instance.allocator; }
void InitializeAllocator() { instance.InitLinkerInitialized(); }

void MemprofThreadLocalMallocStorage::CommitBack() {
GET_STACK_TRACE_MALLOC;
instance.CommitBack(this, &stack);
instance.CommitBack(this);
}

void PrintInternalAllocatorStats() { instance.PrintStats(); }
Expand Down Expand Up @@ -699,7 +697,7 @@ static const void *memprof_malloc_begin(const void *p) {
return (const void *)m->Beg();
}

uptr memprof_malloc_usable_size(const void *ptr, uptr pc, uptr bp) {
uptr memprof_malloc_usable_size(const void *ptr) {
if (!ptr)
return 0;
uptr usable_size = instance.AllocationSize(reinterpret_cast<uptr>(ptr));
Expand All @@ -714,15 +712,15 @@ using namespace __memprof;
uptr __sanitizer_get_estimated_allocated_size(uptr size) { return size; }

int __sanitizer_get_ownership(const void *p) {
return memprof_malloc_usable_size(p, 0, 0) != 0;
return memprof_malloc_usable_size(p) != 0;
}

const void *__sanitizer_get_allocated_begin(const void *p) {
return memprof_malloc_begin(p);
}

uptr __sanitizer_get_allocated_size(const void *p) {
return memprof_malloc_usable_size(p, 0, 0);
return memprof_malloc_usable_size(p);
}

uptr __sanitizer_get_allocated_size_fast(const void *p) {
Expand All @@ -732,6 +730,8 @@ uptr __sanitizer_get_allocated_size_fast(const void *p) {
return ret;
}

void __sanitizer_purge_allocator() { instance.Purge(); }

int __memprof_profile_dump() {
instance.FinishAndWrite();
// In the future we may want to return non-zero if there are any errors
Expand Down
2 changes: 1 addition & 1 deletion compiler-rt/lib/memprof/memprof_allocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ void *memprof_aligned_alloc(uptr alignment, uptr size,
BufferedStackTrace *stack);
int memprof_posix_memalign(void **memptr, uptr alignment, uptr size,
BufferedStackTrace *stack);
uptr memprof_malloc_usable_size(const void *ptr, uptr pc, uptr bp);
uptr memprof_malloc_usable_size(const void *ptr);

void PrintInternalAllocatorStats();

Expand Down
2 changes: 1 addition & 1 deletion compiler-rt/lib/memprof/memprof_interceptors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ INTERCEPTOR(int, pthread_create, void *thread, void *attr,
}

INTERCEPTOR(int, pthread_join, void *t, void **arg) {
return real_pthread_join(t, arg);
return REAL(pthread_join)(t, arg);
}

DEFINE_REAL_PTHREAD_FUNCTIONS
Expand Down
4 changes: 0 additions & 4 deletions compiler-rt/lib/memprof/memprof_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@
#include "sanitizer_common/sanitizer_libc.h"
#include "sanitizer_common/sanitizer_stacktrace.h"

#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__)
#error "The MemProfiler run-time should not be instrumented by MemProfiler"
#endif

// Build-time configuration options.

// If set, memprof will intercept C++ exception api call(s).
Expand Down
4 changes: 1 addition & 3 deletions compiler-rt/lib/memprof/memprof_malloc_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,7 @@ INTERCEPTOR(void *, aligned_alloc, uptr boundary, uptr size) {
#endif // SANITIZER_INTERCEPT_ALIGNED_ALLOC

INTERCEPTOR(uptr, malloc_usable_size, void *ptr) {
GET_CURRENT_PC_BP_SP;
(void)sp;
return memprof_malloc_usable_size(ptr, pc, bp);
return memprof_malloc_usable_size(ptr);
}

#if SANITIZER_INTERCEPT_MALLOPT_AND_MALLINFO
Expand Down
4 changes: 3 additions & 1 deletion flang/include/flang/Lower/ConvertCall.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ namespace Fortran::lower {
/// link to internal procedures.
/// \p isElemental must be set to true if elemental call is being produced.
/// It is only used for HLFIR.
fir::ExtendedValue genCallOpAndResult(
/// The returned boolean indicates if finalization has been emitted in
/// \p stmtCtx for the result.
std::pair<fir::ExtendedValue, bool> genCallOpAndResult(
mlir::Location loc, Fortran::lower::AbstractConverter &converter,
Fortran::lower::SymMap &symMap, Fortran::lower::StatementContext &stmtCtx,
Fortran::lower::CallerInterface &caller, mlir::FunctionType callSiteType,
Expand Down
3 changes: 2 additions & 1 deletion flang/include/flang/Optimizer/Builder/MutableBox.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ void syncMutableBoxFromIRBox(fir::FirOpBuilder &builder, mlir::Location loc,
fir::ExtendedValue genMutableBoxRead(fir::FirOpBuilder &builder,
mlir::Location loc,
const fir::MutableBoxValue &box,
bool mayBePolymorphic = true);
bool mayBePolymorphic = true,
bool preserveLowerBounds = true);

/// Returns the fir.ref<fir.box<T>> of a MutableBoxValue filled with the current
/// association / allocation properties. If the fir.ref<fir.box> already exists
Expand Down
60 changes: 43 additions & 17 deletions flang/lib/Lower/ConvertCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ static llvm::cl::opt<bool> useHlfirIntrinsicOps(
llvm::cl::desc("Lower via HLFIR transformational intrinsic operations such "
"as hlfir.sum"));

static constexpr char tempResultName[] = ".tmp.func_result";

/// Helper to package a Value and its properties into an ExtendedValue.
static fir::ExtendedValue toExtendedValue(mlir::Location loc, mlir::Value base,
llvm::ArrayRef<mlir::Value> extents,
Expand Down Expand Up @@ -147,7 +149,7 @@ static bool mustCastFuncOpToCopeWithImplicitInterfaceMismatch(
return false;
}

fir::ExtendedValue Fortran::lower::genCallOpAndResult(
std::pair<fir::ExtendedValue, bool> Fortran::lower::genCallOpAndResult(
mlir::Location loc, Fortran::lower::AbstractConverter &converter,
Fortran::lower::SymMap &symMap, Fortran::lower::StatementContext &stmtCtx,
Fortran::lower::CallerInterface &caller, mlir::FunctionType callSiteType,
Expand Down Expand Up @@ -478,6 +480,7 @@ fir::ExtendedValue Fortran::lower::genCallOpAndResult(
[](const auto &) {});

// 7.5.6.3 point 5. Derived-type finalization for nonpointer function.
bool resultIsFinalized = false;
// Check if the derived-type is finalizable if it is a monomorphic
// derived-type.
// For polymorphic and unlimited polymorphic enities call the runtime
Expand All @@ -499,6 +502,7 @@ fir::ExtendedValue Fortran::lower::genCallOpAndResult(
fir::runtime::genDerivedTypeDestroy(*bldr, loc,
fir::getBase(*allocatedResult));
});
resultIsFinalized = true;
} else {
const Fortran::semantics::DerivedTypeSpec &typeSpec =
retTy->GetDerivedTypeSpec();
Expand All @@ -513,14 +517,17 @@ fir::ExtendedValue Fortran::lower::genCallOpAndResult(
mlir::Value box = bldr->createBox(loc, *allocatedResult);
fir::runtime::genDerivedTypeDestroy(*bldr, loc, box);
});
resultIsFinalized = true;
}
}
}
return *allocatedResult;
return {*allocatedResult, resultIsFinalized};
}

// subroutine call
if (!resultType)
return mlir::Value{}; // subroutine call
return {fir::ExtendedValue{mlir::Value{}}, /*resultIsFinalized=*/false};

// For now, Fortran return values are implemented with a single MLIR
// function return value.
assert(callNumResults == 1 && "Expected exactly one result in FUNCTION call");
Expand All @@ -533,10 +540,10 @@ fir::ExtendedValue Fortran::lower::genCallOpAndResult(
funcType.getResults()[0].dyn_cast<fir::CharacterType>();
mlir::Value len = builder.createIntegerConstant(
loc, builder.getCharacterLengthType(), charTy.getLen());
return fir::CharBoxValue{callResult, len};
return {fir::CharBoxValue{callResult, len}, /*resultIsFinalized=*/false};
}

return callResult;
return {callResult, /*resultIsFinalized=*/false};
}

static hlfir::EntityWithAttributes genStmtFunctionRef(
Expand Down Expand Up @@ -1389,7 +1396,7 @@ genUserCall(Fortran::lower::PreparedActualArguments &loweredActuals,
// Prepare lowered arguments according to the interface
// and map the lowered values to the dummy
// arguments.
fir::ExtendedValue result = Fortran::lower::genCallOpAndResult(
auto [result, resultIsFinalized] = Fortran::lower::genCallOpAndResult(
loc, callContext.converter, callContext.symMap, callContext.stmtCtx,
caller, callSiteType, callContext.resultType,
callContext.isElementalProcWithArrayArgs());
Expand All @@ -1404,24 +1411,43 @@ genUserCall(Fortran::lower::PreparedActualArguments &loweredActuals,
if (!fir::getBase(result))
return std::nullopt; // subroutine call.

hlfir::Entity resultEntity =
extendedValueToHlfirEntity(loc, builder, result, ".tmp.func_result");
if (fir::isPointerType(fir::getBase(result).getType()))
return extendedValueToHlfirEntity(loc, builder, result, tempResultName);

if (!fir::isPointerType(fir::getBase(result).getType())) {
if (!resultIsFinalized) {
hlfir::Entity resultEntity =
extendedValueToHlfirEntity(loc, builder, result, tempResultName);
resultEntity = loadTrivialScalar(loc, builder, resultEntity);

if (resultEntity.isVariable()) {
// Function result must not be freed, since it is allocated on the stack.
// Note that in non-elemental case, genCallOpAndResult()
// is responsible for establishing the clean-up that destroys
// the derived type result or deallocates its components
// without finalization.
// If the result has no finalization, it can be moved into an expression.
// In such case, the expression should not be freed after its use since
// the result is stack allocated or deallocation (for allocatable results)
// was already inserted in genCallOpAndResult.
auto asExpr = builder.create<hlfir::AsExprOp>(
loc, resultEntity, /*mustFree=*/builder.createBool(loc, false));
resultEntity = hlfir::EntityWithAttributes{asExpr.getResult()};
return hlfir::EntityWithAttributes{asExpr.getResult()};
}
return hlfir::EntityWithAttributes{resultEntity};
}
return hlfir::EntityWithAttributes{resultEntity};
// If the result has finalization, it cannot be moved because use of its
// value have been created in the statement context and may be emitted
// after the hlfir.expr destroy, so the result is kept as a variable in
// HLFIR. This may lead to copies when passing the result to an argument
// with VALUE, and this do not convey the fact that the result will not
// change, but is correct, and using hlfir.expr without the move would
// trigger a copy that may be avoided.

// Load allocatable results before emitting the hlfir.declare and drop its
// lower bounds: this is not a variable From the Fortran point of view, so
// the lower bounds are ones when inquired on the caller side.
const auto *allocatable = result.getBoxOf<fir::MutableBoxValue>();
fir::ExtendedValue loadedResult =
allocatable
? fir::factory::genMutableBoxRead(builder, loc, *allocatable,
/*mayBePolymorphic=*/true,
/*preserveLowerBounds=*/false)
: result;
return extendedValueToHlfirEntity(loc, builder, loadedResult, tempResultName);
}

/// Create an optional dummy argument value from an entity that may be
Expand Down
12 changes: 8 additions & 4 deletions flang/lib/Lower/ConvertExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2846,8 +2846,10 @@ class ScalarExprLowering {
}
}

ExtValue result = Fortran::lower::genCallOpAndResult(
loc, converter, symMap, stmtCtx, caller, callSiteType, resultType);
ExtValue result =
Fortran::lower::genCallOpAndResult(loc, converter, symMap, stmtCtx,
caller, callSiteType, resultType)
.first;

// Sync pointers and allocatables that may have been modified during the
// call.
Expand Down Expand Up @@ -4866,8 +4868,10 @@ class ArrayExprLowering {
[&](const auto &) { return fir::getBase(exv); });
caller.placeInput(argIface, arg);
}
return Fortran::lower::genCallOpAndResult(
loc, converter, symMap, getElementCtx(), caller, callSiteType, retTy);
return Fortran::lower::genCallOpAndResult(loc, converter, symMap,
getElementCtx(), caller,
callSiteType, retTy)
.first;
};
}

Expand Down
Loading