Skip to content

Commit 4424903

Browse files
authored
[DebugInfo][RemoveDIs] Handle DPValues at remaining dbg.value using sites (llvm#73788)
This patch updates the last few places in LLVM using findDbgValues that don't also collect and handle DPValue objects. This largely involves instcombine and mem2reg changes, and are largely mechanical, calling existing utilities on collections of DPValues instead of just DbgValuesInsts. A variety of tests have had RemoveDIs RUN lines added to them to cover these behaviours. We have some technical debt of the instcombine sinking code for DPValues not being implemented yet, so I've left FIXME stubs indicating that we intend to cover tests with RemoveDIs but haven't yet.
1 parent ea602cb commit 4424903

21 files changed

+101
-28
lines changed

llvm/lib/Transforms/InstCombine/InstructionCombining.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2665,9 +2665,10 @@ Instruction *InstCombinerImpl::visitAllocSite(Instruction &MI) {
26652665
// If we are removing an alloca with a dbg.declare, insert dbg.value calls
26662666
// before each store.
26672667
SmallVector<DbgVariableIntrinsic *, 8> DVIs;
2668+
SmallVector<DPValue *, 8> DPVs;
26682669
std::unique_ptr<DIBuilder> DIB;
26692670
if (isa<AllocaInst>(MI)) {
2670-
findDbgUsers(DVIs, &MI);
2671+
findDbgUsers(DVIs, &MI, &DPVs);
26712672
DIB.reset(new DIBuilder(*MI.getModule(), /*AllowUnresolved=*/false));
26722673
}
26732674

@@ -2707,6 +2708,9 @@ Instruction *InstCombinerImpl::visitAllocSite(Instruction &MI) {
27072708
for (auto *DVI : DVIs)
27082709
if (DVI->isAddressOfVariable())
27092710
ConvertDebugDeclareToDebugValue(DVI, SI, *DIB);
2711+
for (auto *DPV : DPVs)
2712+
if (DPV->isAddressOfVariable())
2713+
ConvertDebugDeclareToDebugValue(DPV, SI, *DIB);
27102714
} else {
27112715
// Casts, GEP, or anything else: we're about to delete this instruction,
27122716
// so it can not have any valid uses.
@@ -2745,9 +2749,15 @@ Instruction *InstCombinerImpl::visitAllocSite(Instruction &MI) {
27452749
// If there is a dead store to `%a` in @trivially_inlinable_no_op, the
27462750
// "arg0" dbg.value may be stale after the call. However, failing to remove
27472751
// the DW_OP_deref dbg.value causes large gaps in location coverage.
2752+
//
2753+
// FIXME: the Assignment Tracking project has now likely made this
2754+
// redundant (and it's sometimes harmful).
27482755
for (auto *DVI : DVIs)
27492756
if (DVI->isAddressOfVariable() || DVI->getExpression()->startsWithDeref())
27502757
DVI->eraseFromParent();
2758+
for (auto *DPV : DPVs)
2759+
if (DPV->isAddressOfVariable() || DPV->getExpression()->startsWithDeref())
2760+
DPV->eraseFromParent();
27512761

27522762
return eraseInstFromFunction(MI);
27532763
}

llvm/lib/Transforms/Scalar/JumpThreading.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1981,7 +1981,7 @@ void JumpThreadingPass::updateSSA(
19811981
});
19821982

19831983
// If there are no uses outside the block, we're done with this instruction.
1984-
if (UsesToRename.empty() && DbgValues.empty())
1984+
if (UsesToRename.empty() && DbgValues.empty() && DPValues.empty())
19851985
continue;
19861986
LLVM_DEBUG(dbgs() << "JT: Renaming non-local uses of: " << I << "\n");
19871987

llvm/lib/Transforms/Utils/PromoteMemoryToRegister.cpp

Lines changed: 62 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "llvm/IR/Constants.h"
3232
#include "llvm/IR/DIBuilder.h"
3333
#include "llvm/IR/DebugInfo.h"
34+
#include "llvm/IR/DebugProgramInstruction.h"
3435
#include "llvm/IR/Dominators.h"
3536
#include "llvm/IR/Function.h"
3637
#include "llvm/IR/InstrTypes.h"
@@ -172,6 +173,7 @@ class AssignmentTrackingInfo {
172173

173174
struct AllocaInfo {
174175
using DbgUserVec = SmallVector<DbgVariableIntrinsic *, 1>;
176+
using DPUserVec = SmallVector<DPValue *, 1>;
175177

176178
SmallVector<BasicBlock *, 32> DefiningBlocks;
177179
SmallVector<BasicBlock *, 32> UsingBlocks;
@@ -182,6 +184,7 @@ struct AllocaInfo {
182184

183185
/// Debug users of the alloca - does not include dbg.assign intrinsics.
184186
DbgUserVec DbgUsers;
187+
DPUserVec DPUsers;
185188
/// Helper to update assignment tracking debug info.
186189
AssignmentTrackingInfo AssignmentTracking;
187190

@@ -192,6 +195,7 @@ struct AllocaInfo {
192195
OnlyBlock = nullptr;
193196
OnlyUsedInOneBlock = true;
194197
DbgUsers.clear();
198+
DPUsers.clear();
195199
AssignmentTracking.clear();
196200
}
197201

@@ -225,7 +229,7 @@ struct AllocaInfo {
225229
}
226230
}
227231
DbgUserVec AllDbgUsers;
228-
findDbgUsers(AllDbgUsers, AI);
232+
findDbgUsers(AllDbgUsers, AI, &DPUsers);
229233
std::copy_if(AllDbgUsers.begin(), AllDbgUsers.end(),
230234
std::back_inserter(DbgUsers), [](DbgVariableIntrinsic *DII) {
231235
return !isa<DbgAssignIntrinsic>(DII);
@@ -329,6 +333,7 @@ struct PromoteMem2Reg {
329333
/// describes it, if any, so that we can convert it to a dbg.value
330334
/// intrinsic if the alloca gets promoted.
331335
SmallVector<AllocaInfo::DbgUserVec, 8> AllocaDbgUsers;
336+
SmallVector<AllocaInfo::DPUserVec, 8> AllocaDPUsers;
332337

333338
/// For each alloca, keep an instance of a helper class that gives us an easy
334339
/// way to update assignment tracking debug info if the alloca is promoted.
@@ -525,14 +530,18 @@ static bool rewriteSingleStoreAlloca(
525530

526531
// Record debuginfo for the store and remove the declaration's
527532
// debuginfo.
528-
for (DbgVariableIntrinsic *DII : Info.DbgUsers) {
529-
if (DII->isAddressOfVariable()) {
530-
ConvertDebugDeclareToDebugValue(DII, Info.OnlyStore, DIB);
531-
DII->eraseFromParent();
532-
} else if (DII->getExpression()->startsWithDeref()) {
533-
DII->eraseFromParent();
533+
auto ConvertDebugInfoForStore = [&](auto &Container) {
534+
for (auto *DbgItem : Container) {
535+
if (DbgItem->isAddressOfVariable()) {
536+
ConvertDebugDeclareToDebugValue(DbgItem, Info.OnlyStore, DIB);
537+
DbgItem->eraseFromParent();
538+
} else if (DbgItem->getExpression()->startsWithDeref()) {
539+
DbgItem->eraseFromParent();
540+
}
534541
}
535-
}
542+
};
543+
ConvertDebugInfoForStore(Info.DbgUsers);
544+
ConvertDebugInfoForStore(Info.DPUsers);
536545

537546
// Remove dbg.assigns linked to the alloca as these are now redundant.
538547
at::deleteAssignmentMarkers(AI);
@@ -629,12 +638,18 @@ static bool promoteSingleBlockAlloca(
629638
StoreInst *SI = cast<StoreInst>(AI->user_back());
630639
// Update assignment tracking info for the store we're going to delete.
631640
Info.AssignmentTracking.updateForDeletedStore(SI, DIB, DbgAssignsToDelete);
641+
632642
// Record debuginfo for the store before removing it.
633-
for (DbgVariableIntrinsic *DII : Info.DbgUsers) {
634-
if (DII->isAddressOfVariable()) {
635-
ConvertDebugDeclareToDebugValue(DII, SI, DIB);
643+
auto DbgUpdateForStore = [&](auto &Container) {
644+
for (auto *DbgItem : Container) {
645+
if (DbgItem->isAddressOfVariable()) {
646+
ConvertDebugDeclareToDebugValue(DbgItem, SI, DIB);
647+
}
636648
}
637-
}
649+
};
650+
DbgUpdateForStore(Info.DbgUsers);
651+
DbgUpdateForStore(Info.DPUsers);
652+
638653
SI->eraseFromParent();
639654
LBI.deleteValue(SI);
640655
}
@@ -644,9 +659,14 @@ static bool promoteSingleBlockAlloca(
644659
AI->eraseFromParent();
645660

646661
// The alloca's debuginfo can be removed as well.
647-
for (DbgVariableIntrinsic *DII : Info.DbgUsers)
648-
if (DII->isAddressOfVariable() || DII->getExpression()->startsWithDeref())
649-
DII->eraseFromParent();
662+
auto DbgUpdateForAlloca = [&](auto &Container) {
663+
for (auto *DbgItem : Container)
664+
if (DbgItem->isAddressOfVariable() ||
665+
DbgItem->getExpression()->startsWithDeref())
666+
DbgItem->eraseFromParent();
667+
};
668+
DbgUpdateForAlloca(Info.DbgUsers);
669+
DbgUpdateForAlloca(Info.DPUsers);
650670

651671
++NumLocalPromoted;
652672
return true;
@@ -657,6 +677,7 @@ void PromoteMem2Reg::run() {
657677

658678
AllocaDbgUsers.resize(Allocas.size());
659679
AllocaATInfo.resize(Allocas.size());
680+
AllocaDPUsers.resize(Allocas.size());
660681

661682
AllocaInfo Info;
662683
LargeBlockInfo LBI;
@@ -720,6 +741,8 @@ void PromoteMem2Reg::run() {
720741
AllocaDbgUsers[AllocaNum] = Info.DbgUsers;
721742
if (!Info.AssignmentTracking.empty())
722743
AllocaATInfo[AllocaNum] = Info.AssignmentTracking;
744+
if (!Info.DPUsers.empty())
745+
AllocaDPUsers[AllocaNum] = Info.DPUsers;
723746

724747
// Keep the reverse mapping of the 'Allocas' array for the rename pass.
725748
AllocaLookup[Allocas[AllocaNum]] = AllocaNum;
@@ -795,11 +818,16 @@ void PromoteMem2Reg::run() {
795818
}
796819

797820
// Remove alloca's dbg.declare intrinsics from the function.
798-
for (auto &DbgUsers : AllocaDbgUsers) {
799-
for (auto *DII : DbgUsers)
800-
if (DII->isAddressOfVariable() || DII->getExpression()->startsWithDeref())
801-
DII->eraseFromParent();
802-
}
821+
auto RemoveDbgDeclares = [&](auto &Container) {
822+
for (auto &DbgUsers : Container) {
823+
for (auto *DbgItem : DbgUsers)
824+
if (DbgItem->isAddressOfVariable() ||
825+
DbgItem->getExpression()->startsWithDeref())
826+
DbgItem->eraseFromParent();
827+
}
828+
};
829+
RemoveDbgDeclares(AllocaDbgUsers);
830+
RemoveDbgDeclares(AllocaDPUsers);
803831

804832
// Loop over all of the PHI nodes and see if there are any that we can get
805833
// rid of because they merge all of the same incoming values. This can
@@ -1041,9 +1069,13 @@ void PromoteMem2Reg::RenamePass(BasicBlock *BB, BasicBlock *Pred,
10411069
// The currently active variable for this block is now the PHI.
10421070
IncomingVals[AllocaNo] = APN;
10431071
AllocaATInfo[AllocaNo].updateForNewPhi(APN, DIB);
1044-
for (DbgVariableIntrinsic *DII : AllocaDbgUsers[AllocaNo])
1045-
if (DII->isAddressOfVariable())
1046-
ConvertDebugDeclareToDebugValue(DII, APN, DIB);
1072+
auto ConvertDbgDeclares = [&](auto &Container) {
1073+
for (auto *DbgItem : Container)
1074+
if (DbgItem->isAddressOfVariable())
1075+
ConvertDebugDeclareToDebugValue(DbgItem, APN, DIB);
1076+
};
1077+
ConvertDbgDeclares(AllocaDbgUsers[AllocaNo]);
1078+
ConvertDbgDeclares(AllocaDPUsers[AllocaNo]);
10471079

10481080
// Get the next phi node.
10491081
++PNI;
@@ -1098,9 +1130,13 @@ void PromoteMem2Reg::RenamePass(BasicBlock *BB, BasicBlock *Pred,
10981130
IncomingLocs[AllocaNo] = SI->getDebugLoc();
10991131
AllocaATInfo[AllocaNo].updateForDeletedStore(SI, DIB,
11001132
&DbgAssignsToDelete);
1101-
for (DbgVariableIntrinsic *DII : AllocaDbgUsers[ai->second])
1102-
if (DII->isAddressOfVariable())
1103-
ConvertDebugDeclareToDebugValue(DII, SI, DIB);
1133+
auto ConvertDbgDeclares = [&](auto &Container) {
1134+
for (auto *DbgItem : Container)
1135+
if (DbgItem->isAddressOfVariable())
1136+
ConvertDebugDeclareToDebugValue(DbgItem, SI, DIB);
1137+
};
1138+
ConvertDbgDeclares(AllocaDbgUsers[ai->second]);
1139+
ConvertDbgDeclares(AllocaDPUsers[ai->second]);
11041140
SI->eraseFromParent();
11051141
}
11061142
}

llvm/test/DebugInfo/Generic/mem2reg-promote-alloca-1.ll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
; RUN: opt -passes=mem2reg %s -S -o - | FileCheck %s
2+
; RUN: opt -passes=mem2reg %s -S -o - --try-experimental-debuginfo-iterators | FileCheck %s
23

34
;; Check that mem2reg removes dbg.value(%param.addr, DIExpression(DW_OP_deref...))
45
;; when promoting the alloca %param.addr.

llvm/test/DebugInfo/Generic/mem2reg-promote-alloca-2.ll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
; RUN: opt -passes=mem2reg %s -S -o - | FileCheck %s
2+
; RUN: opt -passes=mem2reg %s -S -o - --try-experimental-debuginfo-iterators | FileCheck %s
23

34
;; Check that mem2reg removes dbg.value(%local, DIExpression(DW_OP_deref...))
45
;; that instcombine LowerDbgDeclare inserted before the call to 'esc' when

llvm/test/DebugInfo/Generic/mem2reg-promote-alloca-3.ll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
; RUN: opt -passes=mem2reg %s -S -o - | FileCheck %s
2+
; RUN: opt -passes=mem2reg %s -S -o - --try-experimental-debuginfo-iterators | FileCheck %s
23

34
;; Check that mem2reg removes dbg.value(%local, DIExpression(DW_OP_deref...))
45
;; that instcombine LowerDbgDeclare inserted before the call to 'esc' when

llvm/test/DebugInfo/Generic/volatile-alloca.ll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
; RUN: opt -passes=mem2reg,instcombine %s -o - -S | FileCheck %s
2+
; RUN: opt -passes=mem2reg,instcombine %s -o - -S --try-experimental-debuginfo-iterators | FileCheck %s
23
;
34
; Test that a dbg.declare describing am alloca with volatile
45
; load/stores is not lowered into a dbg.value, since the alloca won't

llvm/test/DebugInfo/X86/mem2reg_fp80.ll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
; RUN: opt < %s -passes=mem2reg -S | FileCheck %s
2+
; RUN: opt < %s -passes=mem2reg -S --try-experimental-debuginfo-iterators | FileCheck %s
23

34
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
45
target triple = "x86_64-unknown-linux-gnu"

llvm/test/Transforms/InstCombine/cast-set-preserve-signed-dbg-val.ll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
; RUN: opt -passes=instcombine -S < %s | FileCheck %s
2+
; RUN: opt -passes=instcombine -S < %s --try-experimental-debuginfo-iterators | FileCheck %s
23

34
; CHECK-LABEL: define {{.*}} @test5
45
define i16 @test5(i16 %A) !dbg !34 {

llvm/test/Transforms/InstCombine/consecutive-fences.ll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
; RUN: opt -passes=instcombine -S %s | FileCheck %s
2+
; RUN: opt -passes=instcombine -S %s --try-experimental-debuginfo-iterators | FileCheck %s
23

34
; Make sure we collapse the fences in this case
45

llvm/test/Transforms/InstCombine/dbg-scalable-store-fixed-frag.ll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
22
; RUN: opt < %s -passes='instcombine' -S | FileCheck %s
3+
; RUN: opt < %s -passes='instcombine' -S --try-experimental-debuginfo-iterators | FileCheck %s
34

45
define i32 @foo(<vscale x 2 x i32> %x) {
56
; CHECK-LABEL: @foo(

llvm/test/Transforms/InstCombine/debuginfo-dce2.ll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
; RUN: opt -passes=instcombine -S %s -o - | FileCheck %s
2+
; RUN: opt -passes=instcombine -S %s -o - --try-experimental-debuginfo-iterators | FileCheck %s
23

34
; In this example, the cast from ptr to ptr becomes trivially dead. We should
45
; salvage its debug info.

llvm/test/Transforms/InstCombine/debuginfo-skip.ll

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
; RUN: opt -instcombine-lower-dbg-declare=0 < %s -passes=instcombine -S | FileCheck %s
22
; RUN: opt -instcombine-lower-dbg-declare=1 < %s -passes=instcombine -S | FileCheck %s
33

4+
; RUN: opt -instcombine-lower-dbg-declare=0 < %s -passes=instcombine -S --try-experimental-debuginfo-iterators | FileCheck %s
5+
; RUN: opt -instcombine-lower-dbg-declare=1 < %s -passes=instcombine -S --try-experimental-debuginfo-iterators | FileCheck %s
6+
47
define i32 @foo(i32 %j) #0 !dbg !7 {
58
entry:
69
%j.addr = alloca i32, align 4

llvm/test/Transforms/InstCombine/debuginfo.ll

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
; RUN: | FileCheck %s --check-prefix=CHECK --check-prefix=NOLOWER
33
; RUN: opt < %s -passes=instcombine -instcombine-lower-dbg-declare=1 -S | FileCheck %s
44

5+
; RUN: opt < %s -passes=instcombine -instcombine-lower-dbg-declare=0 -S --try-experimental-debuginfo-iterators \
6+
; RUN: | FileCheck %s --check-prefix=CHECK --check-prefix=NOLOWER
7+
; RUN: opt < %s -passes=instcombine -instcombine-lower-dbg-declare=1 -S --try-experimental-debuginfo-iterators | FileCheck %s
8+
59
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
610
target triple = "x86_64--linux"
711

llvm/test/Transforms/InstCombine/debuginfo_add.ll

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
; RUN: opt -passes=instcombine %s -o - -S | FileCheck %s
2+
; FIXME RemoveDIs project: this can't yet be enabled because we haven't
3+
; implemented DPValue sinking for instcombine-sinks.
4+
; run: opt -passes=instcombine %s -o - -S --try-experimental-debuginfo-iterators | FileCheck %s
25
; typedef struct v *v_t;
36
; struct v {
47
; unsigned long long p;

llvm/test/Transforms/InstCombine/erase-dbg-values-at-dead-alloc-site.ll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
; RUN: opt -S -passes=instcombine %s | FileCheck %s -check-prefix=RUN-ONCE
2+
; RUN: opt -S -passes=instcombine %s --try-experimental-debuginfo-iterators | FileCheck %s -check-prefix=RUN-ONCE
23

34
; This example was reduced from a test case in which InstCombine ran at least
45
; twice:

llvm/test/Transforms/InstCombine/lower-dbg-declare.ll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
; RUN: opt -passes=instcombine < %s -S | FileCheck %s
2+
; RUN: opt -passes=instcombine < %s -S --try-experimental-debuginfo-iterators | FileCheck %s
23

34
; This tests dbg.declare lowering for CallInst users of an alloca. The
45
; resulting dbg.value expressions should add a deref to the declare's expression.

llvm/test/Transforms/InstCombine/pr43893.ll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
; Check for setting dbg.value as undef which depends on trivially dead instructions.
22
; RUN: opt -passes=instcombine -S -o - %s | FileCheck %s
3+
; RUN: opt -passes=instcombine -S -o - %s --try-experimental-debuginfo-iterators | FileCheck %s
34

45
@a = common dso_local global i8 0, align 1, !dbg !0
56
@b = common dso_local global i8 0, align 1, !dbg !6

llvm/test/Transforms/InstCombine/sink-instruction-introduces-unnecessary-poison-value.ll

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
; RUN: opt -passes=instcombine -S -o - < %s | FileCheck %s
2+
; FIXME RemoveDIs project: this can't yet be enabled because we haven't
3+
; implemented instcombine sinking.
4+
; run: opt -passes=instcombine -S -o - < %s --try-experimental-debuginfo-iterators | FileCheck %s
25

36
; When the 'int Four = Two;' is sunk into the 'case 0:' block,
47
; the debug value for 'Three' is set incorrectly to 'poison'.

llvm/test/Transforms/InstCombine/stacksave-debuginfo.ll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
; dbg.value intrinsics should not affect peephole combining of stacksave/stackrestore.
33
; PR37713
44
; RUN: opt -passes=instcombine %s -S | FileCheck %s
5+
; RUN: opt -passes=instcombine %s -S --try-experimental-debuginfo-iterators | FileCheck %s
56

67
declare ptr @llvm.stacksave() #0
78
declare void @llvm.stackrestore(ptr) #0

llvm/test/Transforms/InstCombine/unavailable-debug.ll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
; RUN: opt < %s -passes=instcombine -S | FileCheck %s
2+
; RUN: opt < %s -passes=instcombine -S --try-experimental-debuginfo-iterators | FileCheck %s
23

34
; Make sure to update the debug value after dead code elimination.
45
; CHECK: %call = call signext i8 @b(i32 6), !dbg !39

0 commit comments

Comments
 (0)