Skip to content

Commit fb3bf37

Browse files
committed
merge main into amd-staging
Merges commit '990bed64' into amd-staging Change-Id: Ie4718fee0948abe7e867017089d1c5c8d65f3239
2 parents 226e581 + 990bed6 commit fb3bf37

File tree

124 files changed

+1894
-891
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

124 files changed

+1894
-891
lines changed

bolt/include/bolt/Profile/BoltAddressTranslation.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ class BoltAddressTranslation {
9090
std::error_code parse(raw_ostream &OS, StringRef Buf);
9191

9292
/// Dump the parsed address translation tables
93-
void dump(raw_ostream &OS);
93+
void dump(raw_ostream &OS) const;
9494

9595
/// If the maps are loaded in memory, perform the lookup to translate LBR
9696
/// addresses in function located at \p FuncAddress.
@@ -137,7 +137,8 @@ class BoltAddressTranslation {
137137
/// emitted for the start of the BB. More entries may be emitted to cover
138138
/// the location of calls or any instruction that may change control flow.
139139
void writeEntriesForBB(MapTy &Map, const BinaryBasicBlock &BB,
140-
uint64_t FuncInputAddress, uint64_t FuncOutputAddress);
140+
uint64_t FuncInputAddress,
141+
uint64_t FuncOutputAddress) const;
141142

142143
/// Write the serialized address translation table for a function.
143144
template <bool Cold>
@@ -152,7 +153,7 @@ class BoltAddressTranslation {
152153

153154
/// Returns the bitmask with set bits corresponding to indices of BRANCHENTRY
154155
/// entries in function address translation map.
155-
APInt calculateBranchEntriesBitMask(MapTy &Map, size_t EqualElems);
156+
APInt calculateBranchEntriesBitMask(MapTy &Map, size_t EqualElems) const;
156157

157158
/// Calculate the number of equal offsets (output = input - skew) in the
158159
/// beginning of the function.

bolt/lib/Profile/BoltAddressTranslation.cpp

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,9 @@ namespace bolt {
2020

2121
const char *BoltAddressTranslation::SECTION_NAME = ".note.bolt_bat";
2222

23-
void BoltAddressTranslation::writeEntriesForBB(MapTy &Map,
24-
const BinaryBasicBlock &BB,
25-
uint64_t FuncInputAddress,
26-
uint64_t FuncOutputAddress) {
23+
void BoltAddressTranslation::writeEntriesForBB(
24+
MapTy &Map, const BinaryBasicBlock &BB, uint64_t FuncInputAddress,
25+
uint64_t FuncOutputAddress) const {
2726
const uint64_t BBOutputOffset =
2827
BB.getOutputAddressRange().first - FuncOutputAddress;
2928
const uint32_t BBInputOffset = BB.getInputOffset();
@@ -138,8 +137,8 @@ void BoltAddressTranslation::write(const BinaryContext &BC, raw_ostream &OS) {
138137
<< " basic block hashes\n";
139138
}
140139

141-
APInt BoltAddressTranslation::calculateBranchEntriesBitMask(MapTy &Map,
142-
size_t EqualElems) {
140+
APInt BoltAddressTranslation::calculateBranchEntriesBitMask(
141+
MapTy &Map, size_t EqualElems) const {
143142
APInt BitMask(alignTo(EqualElems, 8), 0);
144143
size_t Index = 0;
145144
for (std::pair<const uint32_t, uint32_t> &KeyVal : Map) {
@@ -422,7 +421,7 @@ void BoltAddressTranslation::parseMaps(std::vector<uint64_t> &HotFuncs,
422421
}
423422
}
424423

425-
void BoltAddressTranslation::dump(raw_ostream &OS) {
424+
void BoltAddressTranslation::dump(raw_ostream &OS) const {
426425
const size_t NumTables = Maps.size();
427426
OS << "BAT tables for " << NumTables << " functions:\n";
428427
for (const auto &MapEntry : Maps) {
@@ -447,11 +446,15 @@ void BoltAddressTranslation::dump(raw_ostream &OS) {
447446
OS << formatv(" hash: {0:x}", BBHashMap.getBBHash(Val));
448447
OS << "\n";
449448
}
450-
if (IsHotFunction)
451-
OS << "NumBlocks: " << NumBasicBlocksMap[Address] << '\n';
452-
if (SecondaryEntryPointsMap.count(Address)) {
449+
if (IsHotFunction) {
450+
auto NumBasicBlocksIt = NumBasicBlocksMap.find(Address);
451+
assert(NumBasicBlocksIt != NumBasicBlocksMap.end());
452+
OS << "NumBlocks: " << NumBasicBlocksIt->second << '\n';
453+
}
454+
auto SecondaryEntryPointsIt = SecondaryEntryPointsMap.find(Address);
455+
if (SecondaryEntryPointsIt != SecondaryEntryPointsMap.end()) {
453456
const std::vector<uint32_t> &SecondaryEntryPoints =
454-
SecondaryEntryPointsMap[Address];
457+
SecondaryEntryPointsIt->second;
455458
OS << SecondaryEntryPoints.size() << " secondary entry points:\n";
456459
for (uint32_t EntryPointOffset : SecondaryEntryPoints)
457460
OS << formatv("{0:x}\n", EntryPointOffset);

bolt/lib/Profile/StaleProfileMatching.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "llvm/ADT/Bitfields.h"
3131
#include "llvm/ADT/Hashing.h"
3232
#include "llvm/Support/CommandLine.h"
33+
#include "llvm/Support/Timer.h"
3334
#include "llvm/Support/xxhash.h"
3435
#include "llvm/Transforms/Utils/SampleProfileInference.h"
3536

@@ -42,6 +43,7 @@ using namespace llvm;
4243

4344
namespace opts {
4445

46+
extern cl::opt<bool> TimeRewrite;
4547
extern cl::OptionCategory BoltOptCategory;
4648

4749
cl::opt<bool>
@@ -707,6 +709,10 @@ void assignProfile(BinaryFunction &BF,
707709

708710
bool YAMLProfileReader::inferStaleProfile(
709711
BinaryFunction &BF, const yaml::bolt::BinaryFunctionProfile &YamlBF) {
712+
713+
NamedRegionTimer T("inferStaleProfile", "stale profile inference", "rewrite",
714+
"Rewrite passes", opts::TimeRewrite);
715+
710716
if (!BF.hasCFG())
711717
return false;
712718

bolt/lib/Rewrite/RewriteInstance.cpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ extern cl::list<std::string> ReorderData;
8686
extern cl::opt<bolt::ReorderFunctions::ReorderType> ReorderFunctions;
8787
extern cl::opt<bool> TerminalTrap;
8888
extern cl::opt<bool> TimeBuild;
89+
extern cl::opt<bool> TimeRewrite;
8990

9091
cl::opt<bool> AllowStripped("allow-stripped",
9192
cl::desc("allow processing of stripped binaries"),
@@ -235,11 +236,6 @@ UseGnuStack("use-gnu-stack",
235236
cl::ZeroOrMore,
236237
cl::cat(BoltCategory));
237238

238-
static cl::opt<bool>
239-
TimeRewrite("time-rewrite",
240-
cl::desc("print time spent in rewriting passes"), cl::Hidden,
241-
cl::cat(BoltCategory));
242-
243239
static cl::opt<bool>
244240
SequentialDisassembly("sequential-disassembly",
245241
cl::desc("performs disassembly sequentially"),

bolt/lib/Utils/CommandLineOpts.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,10 @@ cl::opt<bool> TimeOpts("time-opts",
179179
cl::desc("print time spent in each optimization"),
180180
cl::cat(BoltOptCategory));
181181

182+
cl::opt<bool> TimeRewrite("time-rewrite",
183+
cl::desc("print time spent in rewriting passes"),
184+
cl::Hidden, cl::cat(BoltCategory));
185+
182186
cl::opt<bool> UseOldText(
183187
"use-old-text",
184188
cl::desc("re-use space in old .text if possible (relocation mode)"),

clang/docs/ReleaseNotes.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,14 @@ Bug Fixes to C++ Support
766766
explicit object argument member functions. Fixes (#GH92188).
767767
- Fix a C++11 crash when a non-const non-static member function is defined out-of-line with
768768
the ``constexpr`` specifier. Fixes (#GH61004).
769+
- Clang no longer transforms dependent qualified names into implicit class member access expressions
770+
until it can be determined whether the name is that of a non-static member.
771+
- Clang now correctly diagnoses when the current instantiation is used as an incomplete base class.
772+
- Clang no longer treats ``constexpr`` class scope function template specializations of non-static members
773+
as implicitly ``const`` in language modes after C++11.
774+
- Fixed a crash when trying to emit captures in a lambda call operator with an explicit object
775+
parameter that is called on a derived type of the lambda.
776+
Fixes (#GH87210), (GH89541).
769777

770778
Bug Fixes to AST Handling
771779
^^^^^^^^^^^^^^^^^^^^^^^^^

clang/include/clang/AST/ASTContext.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ class VarTemplateDecl;
110110
class VTableContextBase;
111111
class XRayFunctionFilter;
112112

113+
/// A simple array of base specifiers.
114+
typedef SmallVector<CXXBaseSpecifier *, 4> CXXCastPath;
115+
113116
namespace Builtin {
114117

115118
class Context;
@@ -1170,6 +1173,12 @@ class ASTContext : public RefCountedBase<ASTContext> {
11701173
/// in device compilation.
11711174
llvm::DenseSet<const FunctionDecl *> CUDAImplicitHostDeviceFunUsedByDevice;
11721175

1176+
/// For capturing lambdas with an explicit object parameter whose type is
1177+
/// derived from the lambda type, we need to perform derived-to-base
1178+
/// conversion so we can access the captures; the cast paths for that
1179+
/// are stored here.
1180+
llvm::DenseMap<const CXXMethodDecl *, CXXCastPath> LambdaCastPaths;
1181+
11731182
ASTContext(LangOptions &LOpts, SourceManager &SM, IdentifierTable &idents,
11741183
SelectorTable &sels, Builtin::Context &builtins,
11751184
TranslationUnitKind TUKind);

clang/include/clang/Basic/DiagnosticParseKinds.td

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,8 +1345,8 @@ def note_pragma_attribute_namespace_on_attribute : Note<
13451345
"omit the namespace to add attributes to the most-recently"
13461346
" pushed attribute group">;
13471347
def warn_no_support_for_eval_method_source_on_m32 : Warning<
1348-
"Setting the floating point evaluation method to `source` on a target"
1349-
" without SSE is not supported.">, InGroup<Pragmas>;
1348+
"setting the floating point evaluation method to `source` on a target "
1349+
"without SSE is not supported">, InGroup<Pragmas>;
13501350
// - #pragma __debug
13511351
def warn_pragma_debug_dependent_argument : Warning<
13521352
"%select{value|type}0-dependent expression passed as an argument to debug "

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7525,6 +7525,11 @@ def err_explicit_object_parameter_mutable: Error<
75257525
def err_invalid_explicit_object_type_in_lambda: Error<
75267526
"invalid explicit object parameter type %0 in lambda with capture; "
75277527
"the type must be the same as, or derived from, the lambda">;
7528+
def err_explicit_object_lambda_ambiguous_base : Error<
7529+
"lambda %0 is inaccessible due to ambiguity:%1">;
7530+
def err_explicit_object_lambda_inaccessible_base : Error<
7531+
"invalid explicit object parameter type %0 in lambda with capture; "
7532+
"the type must derive publicly from the lambda">;
75287533

75297534
def err_ref_qualifier_overload : Error<
75307535
"cannot overload a member function %select{without a ref-qualifier|with "
@@ -11335,18 +11340,18 @@ def err_omp_reduction_vla_unsupported : Error<
1133511340
def err_omp_linear_distribute_var_non_loop_iteration : Error<
1133611341
"only loop iteration variables are allowed in 'linear' clause in distribute directives">;
1133711342
def warn_omp_non_trivial_type_mapped : Warning<
11338-
"Type %0 is not trivially copyable and not guaranteed to be mapped correctly">,
11343+
"type %0 is not trivially copyable and not guaranteed to be mapped correctly">,
1133911344
InGroup<OpenMPMapping>;
1134011345
def err_omp_requires_clause_redeclaration : Error <
11341-
"Only one %0 clause can appear on a requires directive in a single translation unit">;
11346+
"only one %0 clause can appear on a requires directive in a single translation unit">;
1134211347
def note_omp_requires_previous_clause : Note <
1134311348
"%0 clause previously used here">;
1134411349
def err_omp_directive_before_requires : Error <
1134511350
"'%0' region encountered before requires directive with '%1' clause">;
1134611351
def note_omp_requires_encountered_directive : Note <
1134711352
"'%0' previously encountered here">;
1134811353
def err_omp_device_ancestor_without_requires_reverse_offload : Error <
11349-
"Device clause with ancestor device-modifier used without specifying 'requires reverse_offload'">;
11354+
"device clause with ancestor device-modifier used without specifying 'requires reverse_offload'">;
1135011355
def err_omp_invalid_scope : Error <
1135111356
"'#pragma omp %0' directive must appear only in file scope">;
1135211357
def note_omp_invalid_length_on_this_ptr_mapping : Note <

clang/include/clang/Sema/Sema.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7083,7 +7083,9 @@ class Sema final : public SemaBase {
70837083
StorageClass SC, ArrayRef<ParmVarDecl *> Params,
70847084
bool HasExplicitResultType);
70857085

7086-
void DiagnoseInvalidExplicitObjectParameterInLambda(CXXMethodDecl *Method);
7086+
/// Returns true if the explicit object parameter was invalid.
7087+
bool DiagnoseInvalidExplicitObjectParameterInLambda(CXXMethodDecl *Method,
7088+
SourceLocation CallLoc);
70877089

70887090
/// Perform initialization analysis of the init-capture and perform
70897091
/// any implicit conversions such as an lvalue-to-rvalue conversion if

clang/lib/CodeGen/CGExpr.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4676,7 +4676,8 @@ LValue CodeGenFunction::EmitMemberExpr(const MemberExpr *E) {
46764676
LValue CodeGenFunction::EmitLValueForLambdaField(const FieldDecl *Field,
46774677
llvm::Value *ThisValue) {
46784678
bool HasExplicitObjectParameter = false;
4679-
if (const auto *MD = dyn_cast_if_present<CXXMethodDecl>(CurCodeDecl)) {
4679+
const auto *MD = dyn_cast_if_present<CXXMethodDecl>(CurCodeDecl);
4680+
if (MD) {
46804681
HasExplicitObjectParameter = MD->isExplicitObjectMemberFunction();
46814682
assert(MD->getParent()->isLambda());
46824683
assert(MD->getParent() == Field->getParent());
@@ -4693,6 +4694,17 @@ LValue CodeGenFunction::EmitLValueForLambdaField(const FieldDecl *Field,
46934694
else
46944695
LambdaLV = MakeAddrLValue(AddrOfExplicitObject,
46954696
D->getType().getNonReferenceType());
4697+
4698+
// Make sure we have an lvalue to the lambda itself and not a derived class.
4699+
auto *ThisTy = D->getType().getNonReferenceType()->getAsCXXRecordDecl();
4700+
auto *LambdaTy = cast<CXXRecordDecl>(Field->getParent());
4701+
if (ThisTy != LambdaTy) {
4702+
const CXXCastPath &BasePathArray = getContext().LambdaCastPaths.at(MD);
4703+
Address Base = GetAddressOfBaseClass(
4704+
LambdaLV.getAddress(), ThisTy, BasePathArray.begin(),
4705+
BasePathArray.end(), /*NullCheckValue=*/false, SourceLocation());
4706+
LambdaLV = MakeAddrLValue(Base, QualType{LambdaTy->getTypeForDecl(), 0});
4707+
}
46964708
} else {
46974709
QualType LambdaTagType = getContext().getTagDeclType(Field->getParent());
46984710
LambdaLV = MakeNaturalAlignAddrLValue(ThisValue, LambdaTagType);

clang/lib/Sema/SemaLambda.cpp

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "clang/Sema/SemaLambda.h"
1313
#include "TypeLocBuilder.h"
1414
#include "clang/AST/ASTLambda.h"
15+
#include "clang/AST/CXXInheritance.h"
1516
#include "clang/AST/ExprCXX.h"
1617
#include "clang/Basic/TargetInfo.h"
1718
#include "clang/Sema/DeclSpec.h"
@@ -386,30 +387,69 @@ buildTypeForLambdaCallOperator(Sema &S, clang::CXXRecordDecl *Class,
386387
// parameter, if any, of the lambda's function call operator (possibly
387388
// instantiated from a function call operator template) shall be either:
388389
// - the closure type,
389-
// - class type derived from the closure type, or
390+
// - class type publicly and unambiguously derived from the closure type, or
390391
// - a reference to a possibly cv-qualified such type.
391-
void Sema::DiagnoseInvalidExplicitObjectParameterInLambda(
392-
CXXMethodDecl *Method) {
392+
bool Sema::DiagnoseInvalidExplicitObjectParameterInLambda(
393+
CXXMethodDecl *Method, SourceLocation CallLoc) {
393394
if (!isLambdaCallWithExplicitObjectParameter(Method))
394-
return;
395+
return false;
395396
CXXRecordDecl *RD = Method->getParent();
396397
if (Method->getType()->isDependentType())
397-
return;
398+
return false;
398399
if (RD->isCapturelessLambda())
399-
return;
400-
QualType ExplicitObjectParameterType = Method->getParamDecl(0)
401-
->getType()
400+
return false;
401+
402+
ParmVarDecl *Param = Method->getParamDecl(0);
403+
QualType ExplicitObjectParameterType = Param->getType()
402404
.getNonReferenceType()
403405
.getUnqualifiedType()
404406
.getDesugaredType(getASTContext());
405407
QualType LambdaType = getASTContext().getRecordType(RD);
406408
if (LambdaType == ExplicitObjectParameterType)
407-
return;
408-
if (IsDerivedFrom(RD->getLocation(), ExplicitObjectParameterType, LambdaType))
409-
return;
410-
Diag(Method->getParamDecl(0)->getLocation(),
411-
diag::err_invalid_explicit_object_type_in_lambda)
412-
<< ExplicitObjectParameterType;
409+
return false;
410+
411+
// Don't check the same instantiation twice.
412+
//
413+
// If this call operator is ill-formed, there is no point in issuing
414+
// a diagnostic every time it is called because the problem is in the
415+
// definition of the derived type, not at the call site.
416+
//
417+
// FIXME: Move this check to where we instantiate the method? This should
418+
// be possible, but the naive approach of just marking the method as invalid
419+
// leads to us emitting more diagnostics than we should have to for this case
420+
// (1 error here *and* 1 error about there being no matching overload at the
421+
// call site). It might be possible to avoid that by also checking if there
422+
// is an empty cast path for the method stored in the context (signalling that
423+
// we've already diagnosed it) and then just not building the call, but that
424+
// doesn't really seem any simpler than diagnosing it at the call site...
425+
if (auto It = Context.LambdaCastPaths.find(Method);
426+
It != Context.LambdaCastPaths.end())
427+
return It->second.empty();
428+
429+
CXXCastPath &Path = Context.LambdaCastPaths[Method];
430+
CXXBasePaths Paths(/*FindAmbiguities=*/true, /*RecordPaths=*/true,
431+
/*DetectVirtual=*/false);
432+
if (!IsDerivedFrom(RD->getLocation(), ExplicitObjectParameterType, LambdaType,
433+
Paths)) {
434+
Diag(Param->getLocation(), diag::err_invalid_explicit_object_type_in_lambda)
435+
<< ExplicitObjectParameterType;
436+
return true;
437+
}
438+
439+
if (Paths.isAmbiguous(LambdaType->getCanonicalTypeUnqualified())) {
440+
std::string PathsDisplay = getAmbiguousPathsDisplayString(Paths);
441+
Diag(CallLoc, diag::err_explicit_object_lambda_ambiguous_base)
442+
<< LambdaType << PathsDisplay;
443+
return true;
444+
}
445+
446+
if (CheckBaseClassAccess(CallLoc, LambdaType, ExplicitObjectParameterType,
447+
Paths.front(),
448+
diag::err_explicit_object_lambda_inaccessible_base))
449+
return true;
450+
451+
BuildBasePathArray(Paths, Path);
452+
return false;
413453
}
414454

415455
void Sema::handleLambdaNumbering(

clang/lib/Sema/SemaOverload.cpp

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6472,17 +6472,20 @@ ExprResult Sema::InitializeExplicitObjectArgument(Sema &S, Expr *Obj,
64726472
Obj->getExprLoc(), Obj);
64736473
}
64746474

6475-
static void PrepareExplicitObjectArgument(Sema &S, CXXMethodDecl *Method,
6475+
static bool PrepareExplicitObjectArgument(Sema &S, CXXMethodDecl *Method,
64766476
Expr *Object, MultiExprArg &Args,
64776477
SmallVectorImpl<Expr *> &NewArgs) {
64786478
assert(Method->isExplicitObjectMemberFunction() &&
64796479
"Method is not an explicit member function");
64806480
assert(NewArgs.empty() && "NewArgs should be empty");
6481+
64816482
NewArgs.reserve(Args.size() + 1);
64826483
Expr *This = GetExplicitObjectExpr(S, Object, Method);
64836484
NewArgs.push_back(This);
64846485
NewArgs.append(Args.begin(), Args.end());
64856486
Args = NewArgs;
6487+
return S.DiagnoseInvalidExplicitObjectParameterInLambda(
6488+
Method, Object->getBeginLoc());
64866489
}
64876490

64886491
/// Determine whether the provided type is an integral type, or an enumeration
@@ -15612,8 +15615,10 @@ ExprResult Sema::BuildCallToMemberFunction(Scope *S, Expr *MemExprE,
1561215615
CallExpr *TheCall = nullptr;
1561315616
llvm::SmallVector<Expr *, 8> NewArgs;
1561415617
if (Method->isExplicitObjectMemberFunction()) {
15615-
PrepareExplicitObjectArgument(*this, Method, MemExpr->getBase(), Args,
15616-
NewArgs);
15618+
if (PrepareExplicitObjectArgument(*this, Method, MemExpr->getBase(), Args,
15619+
NewArgs))
15620+
return ExprError();
15621+
1561715622
// Build the actual expression node.
1561815623
ExprResult FnExpr =
1561915624
CreateFunctionRefExpr(*this, Method, FoundDecl, MemExpr,
@@ -15927,9 +15932,7 @@ Sema::BuildCallToObjectOfClassType(Scope *S, Expr *Obj,
1592715932
// Initialize the object parameter.
1592815933
llvm::SmallVector<Expr *, 8> NewArgs;
1592915934
if (Method->isExplicitObjectMemberFunction()) {
15930-
// FIXME: we should do that during the definition of the lambda when we can.
15931-
DiagnoseInvalidExplicitObjectParameterInLambda(Method);
15932-
PrepareExplicitObjectArgument(*this, Method, Obj, Args, NewArgs);
15935+
IsError |= PrepareExplicitObjectArgument(*this, Method, Obj, Args, NewArgs);
1593315936
} else {
1593415937
ExprResult ObjRes = PerformImplicitObjectArgumentInitialization(
1593515938
Object.get(), /*Qualifier=*/nullptr, Best->FoundDecl, Method);

0 commit comments

Comments
 (0)