Skip to content

Commit fbfe72f

Browse files
committed
[Distributed] Remote DA inits should not run bodies for remote actors
1 parent dfc5840 commit fbfe72f

7 files changed

+115
-128
lines changed

lib/SILGen/SILGenDestructor.cpp

Lines changed: 38 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ void SILGenFunction::emitDestroyingDestructor(DestructorDecl *dd) {
3131
Loc.markAutoGenerated();
3232

3333
auto cd = cast<ClassDecl>(dd->getDeclContext());
34+
auto &C = cd->getASTContext();
3435
SILValue selfValue = emitSelfDecl(dd->getImplicitSelfDecl());
3536

3637
// Create a basic block to jump to for the implicit destruction behavior
@@ -40,11 +41,12 @@ void SILGenFunction::emitDestroyingDestructor(DestructorDecl *dd) {
4041

4142
auto cleanupLoc = CleanupLocation(Loc);
4243

44+
SILBasicBlock *deinitBodyBB = nullptr;
4345
SILBasicBlock *finishBB = nullptr;
4446
if (cd->isDistributedActor()) {
45-
finishBB = createBasicBlock("finishBB");
46-
auto continueRemoteBB = createBasicBlock("continueRemoteBB");
47-
auto deinitBodyBB = createBasicBlock("deinitBody");
47+
auto remoteBB = createBasicBlock("remoteActorDeinitBB");
48+
finishBB = createBasicBlock("finishDeinitBB");
49+
deinitBodyBB = createBasicBlock("deinitBodyBB");
4850

4951
// FIXME: what should the type of management be for this?
5052
auto managedSelf = ManagedValue::forBorrowedRValue(selfValue);
@@ -53,17 +55,36 @@ void SILGenFunction::emitDestroyingDestructor(DestructorDecl *dd) {
5355
emitDistributedIfRemoteBranch(
5456
SILLocation(Loc),
5557
managedSelf, selfTy,
56-
/*if remote=*/continueRemoteBB,
58+
/*if remote=*/remoteBB,
5759
/*if local=*/deinitBodyBB);
5860

59-
B.emitBlock(continueRemoteBB);
60-
B.createBranch(SILLocation(Loc), finishBB);
61+
{
62+
B.emitBlock(remoteBB);
6163

62-
B.emitBlock(deinitBodyBB);
64+
// Note that we do NOT execute user-declared the deinit body.
65+
// They would be free to access state which does not exist in a remote DA
66+
67+
// we are a remote instance,
68+
// the only properties we can destroy are the id and system properties.
69+
for (VarDecl *vd : cd->getStoredProperties()) {
70+
if (getActorIsolation(vd) == ActorIsolation::ActorInstance)
71+
continue;
72+
73+
// Just to double-check, we only want to destroy `id` and `actorSystem`
74+
if (vd->getBaseIdentifier() == C.Id_id ||
75+
vd->getBaseIdentifier() == C.Id_actorSystem) {
76+
destroyClassMember(cleanupLoc, managedSelf, vd);
77+
}
78+
}
79+
80+
B.createBranch(SILLocation(Loc), finishBB);
81+
}
6382
}
6483

6584
emitProfilerIncrement(dd->getTypecheckedBody());
6685
// Emit the destructor body.
86+
if (deinitBodyBB)
87+
B.emitBlock(deinitBodyBB);
6788
emitStmt(dd->getTypecheckedBody());
6889

6990
Optional<SILValue> maybeReturnValue;
@@ -100,24 +121,6 @@ void SILGenFunction::emitDestroyingDestructor(DestructorDecl *dd) {
100121
resultSelfValue = selfValue;
101122
}
102123

103-
/// A (local) distributed actor resigns its identity as it is deallocated.
104-
/// A (remote) distributed actor must not run any of the declared deinit's body,
105-
/// since it might attempt touching actor state which isn't there.
106-
if (cd->isDistributedActor()) {
107-
SILBasicBlock *continueBB = createBasicBlock("continueBB");
108-
109-
RegularLocation loc(dd);
110-
if (dd->isImplicit())
111-
loc.markAutoGenerated();
112-
113-
// FIXME: what should the type of management be for this?
114-
auto managedSelf = ManagedValue::forBorrowedRValue(selfValue);
115-
/// This way the transport knows it must not deliver any more messages to it,
116-
/// and can remove it from its (weak) lookup tables.
117-
emitConditionalResignIdentityCall(loc, cd, managedSelf, continueBB, finishBB);
118-
B.emitBlock(continueBB);
119-
}
120-
121124
ArgumentScope S(*this, Loc);
122125
ManagedValue borrowedValue =
123126
ManagedValue::forUnmanaged(resultSelfValue).borrow(*this, cleanupLoc);
@@ -127,6 +130,16 @@ void SILGenFunction::emitDestroyingDestructor(DestructorDecl *dd) {
127130
B.createUncheckedRefCast(cleanupLoc, borrowedValue, classTy);
128131
}
129132

133+
// A distributed actor must invoke `actorSystem.resignID` as it deinits.
134+
if (cd->isDistributedActor()) {
135+
// This must only be called by a *local* distributed actor (not a remote proxy).
136+
// Since this call is emitted after the user-declared body of the deinit,
137+
// just before returning; this is guaranteed to only be executed in the local
138+
// actor case - because the body is never executed for a remote proxy either.
139+
emitDistributedActorSystemResignIDCall(
140+
cleanupLoc, cd, ManagedValue::forBorrowedRValue(selfValue));
141+
}
142+
130143
// Release our members.
131144
emitClassMemberDestruction(borrowedValue, cd, cleanupLoc, finishBB);
132145

@@ -387,25 +400,6 @@ void SILGenFunction::emitClassMemberDestruction(ManagedValue selfValue,
387400
SILBasicBlock *finishBB) {
388401
assert(selfValue.getOwnershipKind() == OwnershipKind::Guaranteed);
389402

390-
/// If this ClassDecl is a distributed actor, we must synthesise another code
391-
/// path for deallocating a 'remote' actor. In that case, these basic blocks
392-
/// are used to return to the "normal" (i.e. 'local' instance) destruction.
393-
///
394-
/// For other cases, the basic blocks are not necessary and the destructor
395-
/// can just emit all the normal destruction code right into the current block.
396-
// If set, used as the basic block for the destroying of all members.
397-
SILBasicBlock *normalMemberDestroyBB = nullptr;
398-
399-
/// A distributed actor may be 'remote' in which case there is no need to
400-
/// destroy "all" members, because they never had storage to begin with.
401-
if (cd->isDistributedActor()) {
402-
normalMemberDestroyBB = createBasicBlock("normalMemberDestroyBB");
403-
404-
emitDistributedActorClassMemberDestruction(cleanupLoc, selfValue, cd,
405-
normalMemberDestroyBB,
406-
finishBB);
407-
}
408-
409403
// Before we destroy all fields, we check if any of them are
410404
// recursively the same type as `self`, so we can iteratively
411405
// deinitialize them, to prevent deep recursion and potential
@@ -416,9 +410,6 @@ void SILGenFunction::emitClassMemberDestruction(ManagedValue selfValue,
416410

417411
/// Destroy all members.
418412
{
419-
if (normalMemberDestroyBB)
420-
B.emitBlock(normalMemberDestroyBB);
421-
422413
for (VarDecl *vd : cd->getStoredProperties()) {
423414
if (recursiveLinks.contains(vd))
424415
continue;

lib/SILGen/SILGenDistributed.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -557,13 +557,15 @@ SILGenFunction::emitConditionalResignIdentityCall(SILLocation loc,
557557

558558
void SILGenFunction::emitDistributedActorClassMemberDestruction(
559559
SILLocation cleanupLoc, ManagedValue selfValue, ClassDecl *cd,
560-
SILBasicBlock *normalMemberDestroyBB, SILBasicBlock *finishBB) {
560+
SILBasicBlock *normalMemberDestroyBB,
561+
SILBasicBlock *remoteMemberDestroyBB,
562+
SILBasicBlock *finishBB) {
561563
auto selfTy = cd->getDeclaredInterfaceType();
562564

563565
Scope scope(Cleanups, CleanupLocation(cleanupLoc));
564566

565567
auto isLocalBB = createBasicBlock("isLocalBB");
566-
auto remoteMemberDestroyBB = createBasicBlock("remoteMemberDestroyBB");
568+
// auto remoteMemberDestroyBB = createBasicBlock("remoteMemberDestroyBB");
567569

568570
// if __isRemoteActor(self) {
569571
// ...

lib/SILGen/SILGenFunction.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2140,7 +2140,9 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction
21402140

21412141
void emitDistributedActorClassMemberDestruction(
21422142
SILLocation cleanupLoc, ManagedValue selfValue, ClassDecl *cd,
2143-
SILBasicBlock *normalMemberDestroyBB, SILBasicBlock *finishBB);
2143+
SILBasicBlock *normalMemberDestroyBB,
2144+
SILBasicBlock *remoteMemberDestroyBB,
2145+
SILBasicBlock *finishBB);
21442146

21452147
//===--------------------------------------------------------------------===//
21462148
// Declarations

test/Distributed/Runtime/distributed_actor_deinit.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ distributed actor DA_userDefined2 {
3535

3636
deinit {
3737
print("Deinitializing \(self.id) remote:\(__isRemoteActor(self))")
38-
return
3938
}
4039
}
4140

test/Distributed/Runtime/distributed_actor_remote_fieldsDontCrashDeinit.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,11 @@ func test_remote() async {
4848
try! SomeSpecificDistributedActor.resolve(id: address, using: system)
4949
// Check the id and system are the right values, and not trash memory
5050
print("remote.id = \(remote!.id)") // CHECK: remote.id = ActorAddress(address: "sact://127.0.0.1/example#1234")
51-
print("remote.system = \(remote!.actorSystem)")
51+
print("remote.system = \(remote!.actorSystem)") // CHECK: remote.system = FakeActorSystem()
5252

53-
remote = nil // CHECK: deinit ActorAddress(address: "sact://127.0.0.1/example#1234")
53+
remote = nil
54+
// CHECK-NOT: deinit ActorAddress(address: "sact://127.0.0.1/example#1234")
55+
// CHECK-NEXT: done
5456
print("done")
5557
}
5658

test/Distributed/Runtime/distributed_actor_remote_retains_system.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,6 @@ func test_remote() async {
147147
print("remote.id = \(remote.id)") // CHECK: remote.id = ActorAddress(address: "sact://127.0.0.1/example#1234")
148148
print("remote.system = \(remote.actorSystem)") // CHECK: remote.system = main.FakeActorSystem
149149

150-
// only once we exit the function and the remote is released, the system has no more references
151-
// CHECK-DAG: deinit ActorAddress(address: "sact://127.0.0.1/example#1234")
152150
// system must deinit after the last actor using it does deinit
153151
// CHECK-DAG: deinit main.FakeActorSystem
154152
}

test/Distributed/SIL/distributed_actor_default_deinit_sil.swift

Lines changed: 66 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -22,82 +22,76 @@ distributed actor MyDistActor {
2222

2323
// MARK: distributed actor check
2424

25-
// This test checks that we resign the identity for local deallocations,
26-
// destroy only the correct stored properties whether remote or local, and also
27-
// destroy the executor.
28-
29-
// CHECK-LABEL: sil hidden{{.*}} @$s14default_deinit11MyDistActorCfd : $@convention(method) (@guaranteed MyDistActor) -> @owned Builtin.NativeObject {
25+
// ===== -----------------------------------------------------------------------
26+
// CHECK-LABEL: sil hidden @$s14default_deinit11MyDistActorCfd : $@convention(method) (@guaranteed MyDistActor) -> @owned Builtin.NativeObject {
3027
// CHECK: bb0([[SELF:%[0-9]+]] : $MyDistActor):
31-
// CHECK: [[EXI_SELF:%[0-9]+]] = init_existential_ref [[SELF]] : $MyDistActor
32-
// CHECK: [[IS_REMOTE_FN:%[0-9]+]] = function_ref @swift_distributed_actor_is_remote
33-
// CHECK: [[IS_REMOTE:%[0-9]+]] = apply [[IS_REMOTE_FN]]([[EXI_SELF]])
34-
// CHECK: [[RAW_BOOL:%[0-9]+]] = struct_extract [[IS_REMOTE]] : $Bool, #Bool._value
35-
// CHECK: cond_br [[RAW_BOOL]], [[REMOTE_BB:bb[0-9]+]], [[LOCAL_BB:bb[0-9]+]]
36-
37-
// *** If local... invoke system.resignID()
38-
// CHECK: [[LOCAL_BB]]:
39-
// CHECK: [[ID_REF:%[0-9]+]] = ref_element_addr [[SELF]] : $MyDistActor, #MyDistActor.id
40-
// CHECK: [[SYS_REF:%[0-9]+]] = ref_element_addr [[SELF]] : $MyDistActor, #MyDistActor.actorSystem
41-
// CHECK: [[ID_LOAD:%[0-9]+]] = load [[ID_REF]] : $*ActorAddress
42-
// CHECK: [[SYS_LOAD:%[0-9]+]] = load [[SYS_REF]] : $*FakeActorSystem
43-
// CHECK: [[RESIGN:%[0-9]+]] = function_ref @$s27FakeDistributedActorSystems0aC6SystemV8resignIDyyAA0C7AddressVF : $@convention(method) (@guaranteed ActorAddress, @guaranteed FakeActorSystem) -> ()
44-
// CHECK: apply [[RESIGN]]([[ID_LOAD]], [[SYS_LOAD]])
45-
// CHECK: br [[CONTINUE:bb[0-9]+]]
46-
47-
// *** If remote...
48-
// CHECK: [[REMOTE_BB]]:
49-
// CHECK: br [[CONTINUE]]
50-
51-
// Now we deallocate stored properties, and how we do that depends again on
52-
// being remote or local. Default code emission does another is_remote test,
53-
// so we check for that here and leave tail-merging to the optimizer, for now.
54-
// CHECK: [[CONTINUE]]:
55-
// *** this is entirely copy-pasted from the first check in bb0 ***
56-
// CHECK: [[EXI_SELF:%[0-9]+]] = init_existential_ref [[SELF]] : $MyDistActor
57-
// CHECK: [[IS_REMOTE_FN:%[0-9]+]] = function_ref @swift_distributed_actor_is_remote
58-
// CHECK: [[IS_REMOTE:%[0-9]+]] = apply [[IS_REMOTE_FN]]([[EXI_SELF]])
59-
// CHECK: [[RAW_BOOL:%[0-9]+]] = struct_extract [[IS_REMOTE]] : $Bool, #Bool._value
60-
// CHECK: cond_br [[RAW_BOOL]], [[REMOTE_BB_DEALLOC:bb[0-9]+]], [[LOCAL_BB_DEALLOC:bb[0-9]+]]
61-
62-
// *** only destroy the id and system if remote ***
63-
// CHECK: [[REMOTE_BB_DEALLOC]]:
64-
// *** destroy identity ***
65-
// CHECK: [[REF:%[0-9]+]] = ref_element_addr [[SELF]] : $MyDistActor, #MyDistActor.id
66-
// CHECK: [[ACCESS:%[0-9]+]] = begin_access [deinit] [static] [[REF]]
67-
// CHECK: destroy_addr [[ACCESS]] : $*ActorAddress
68-
// CHECK: end_access [[ACCESS]]
69-
// *** destroy system ***
70-
// CHECK: [[REF:%[0-9]+]] = ref_element_addr [[SELF]] : $MyDistActor, #MyDistActor.actorSystem
71-
// CHECK: [[ACCESS:%[0-9]+]] = begin_access [deinit] [static] [[REF]]
72-
// CHECK: destroy_addr [[ACCESS]] : $*FakeActorSystem
73-
// CHECK: end_access [[ACCESS]]
74-
// CHECK: br [[AFTER_DEALLOC:bb[0-9]+]]
75-
76-
// *** destroy everything if local ***
77-
// CHECK: [[LOCAL_BB_DEALLOC]]:
78-
// *** destroy identity ***
79-
// CHECK: [[REF:%[0-9]+]] = ref_element_addr [[SELF]] : $MyDistActor, #MyDistActor.id
80-
// CHECK: [[ACCESS:%[0-9]+]] = begin_access [deinit] [static] [[REF]]
81-
// CHECK: destroy_addr [[ACCESS]] : $*ActorAddress
82-
// CHECK: end_access [[ACCESS]]
83-
// *** destroy system ***
84-
// SKIP: [[REF:%[0-9]+]] = ref_element_addr [[SELF]] : $MyDistActor, #MyDistActor.actorSystem
85-
// SKIP: [[ACCESS:%[0-9]+]] = begin_access [deinit] [static] [[REF]]
86-
// SKIP: destroy_addr [[ACCESS]] : $*FakeActorSystem
87-
// SKIP: end_access [[ACCESS]]
88-
// *** destroy the user-defined field ***
89-
// CHECK: [[REF:%[0-9]+]] = ref_element_addr [[SELF]] : $MyDistActor, #MyDistActor.localOnlyField
90-
// CHECK: [[ACCESS:%[0-9]+]] = begin_access [deinit] [static] [[REF]]
91-
// CHECK: destroy_addr [[ACCESS]] : $*SomeClass
92-
// CHECK: end_access [[ACCESS]]
93-
// CHECK: br [[AFTER_DEALLOC]]
28+
// CHECK: [[EXI_SELF:%[0-9]+]] = init_existential_ref [[SELF]] : $MyDistActor : $MyDistActor, $AnyObject
29+
// CHECK: [[IS_REMOTE_FN:%[0-9]+]] = function_ref @swift_distributed_actor_is_remote : $@convention(thin) (@guaranteed AnyObject) -> Bool
30+
// CHECK: [[IS_REMOTE:%[0-9]+]] = apply [[IS_REMOTE_FN]]([[EXI_SELF]])
31+
// CHECK: [[RAW_BOOL:%[0-9]+]] = struct_extract [[IS_REMOTE]] : $Bool, #Bool._value
32+
// CHECK: cond_br [[RAW_BOOL]], [[REMOTE_ACTOR_DEINIT_BB:bb[0-9]+]], [[DEINIT_BODY_BB:bb[0-9]+]]
33+
34+
// ===== -----------------------------------------------------------------------
35+
// CHECK: // deinitBodyBB
36+
// CHECK: [[DEINIT_BODY_BB]]:
37+
38+
// Resign the ID by calling actorSystem.resignID, before we destroy properties:
39+
// CHECK: [[ID_REF:%[0-9]+]] = ref_element_addr [[SELF]] : $MyDistActor, #MyDistActor.id
40+
// CHECK: [[SYS_REF:%[0-9]+]] = ref_element_addr [[SELF]] : $MyDistActor, #MyDistActor.actorSystem
41+
// CHECK: [[ID_LOAD:%[0-9]+]] = load [[ID_REF]] : $*ActorAddress
42+
// CHECK: [[SYS_LOAD:%[0-9]+]] = load [[SYS_REF]] : $*FakeActorSystem
43+
// CHECK: [[FN_REF:%[0-9]+]] = function_ref @$s27FakeDistributedActorSystems0aC6SystemV8resignIDyyAA0C7AddressVF : $@convention(method) (@guaranteed ActorAddress, @guaranteed FakeActorSystem) -> ()
44+
// CHECK: [[DROP:%[0-9]+]] = apply [[FN_REF]]([[ID_LOAD]], [[SYS_LOAD]]) : $@convention(method) (@guaranteed ActorAddress, @guaranteed FakeActorSystem) -> ()
45+
// CHECK: [[DROP:%[0-9]+]] = tuple ()
46+
47+
// Destroy implicit Distributed Actor field: id
48+
// CHECK: [[REF:%[0-9]+]] = ref_element_addr [[SELF]] : $MyDistActor, #MyDistActor.id
49+
// CHECK: [[ACCESS:%[0-9]+]] = begin_access [deinit] [static] [[REF]] : $*ActorAddress
50+
// CHECK: destroy_addr [[ACCESS]] : $*ActorAddress
51+
// CHECK: end_access [[ACCESS]] : $*ActorAddress
52+
53+
// Destroy implicit Distributed Actor field: actorSystem
54+
// CHECK: [[REF:%[0-9]+]] = ref_element_addr [[SELF]] : $MyDistActor, #MyDistActor.actorSystem
55+
// CHECK: [[ACCESS:%[0-9]+]] = begin_access [deinit] [static] [[REF]] : $*FakeActorSystem
56+
// CHECK: destroy_addr [[ACCESS]] : $*FakeActorSystem
57+
// CHECK: end_access [[ACCESS]] : $*FakeActorSystem
58+
59+
// Destroy local fields:
60+
// CHECK: [[REF:%[0-9]+]] = ref_element_addr [[SELF]] : $MyDistActor, #MyDistActor.localOnlyField
61+
// CHECK: [[ACCESS:%[0-9]+]] = begin_access [deinit] [static] [[REF]] : $*SomeClass
62+
// CHECK: destroy_addr [[ACCESS]] : $*SomeClass
63+
// CHECK: end_access [[ACCESS]] : $*SomeClass
64+
// CHECK: br [[FINISH_DEINIT_BB:bb[0-9]+]]
65+
66+
// ===== -----------------------------------------------------------------------
67+
// CHECK: // finishDeinitBB
68+
// CHECK: [[FINISH_DEINIT_BB]]:
69+
// CHECK: [[BUILTIN:%[0-9]+]] = builtin "destroyDefaultActor"([[SELF]] : $MyDistActor) : $()
70+
// CHECK: [[CAST:%[0-9]+]] = unchecked_ref_cast [[SELF]] : $MyDistActor to $Builtin.NativeObject
71+
// CHECK: return [[CAST]] : $Builtin.NativeObject
72+
73+
// ===== -----------------------------------------------------------------------
74+
// CHECK: // remoteActorDeinitBB
75+
// CHECK: [[REMOTE_ACTOR_DEINIT_BB]]:
76+
77+
// Even just the remote deinit branch must destroy the ID
78+
// CHECK: [[REF:%[0-9]+]] = ref_element_addr [[SELF]] : $MyDistActor, #MyDistActor.id
79+
// CHECK: [[ACCESS:%[0-9]+]] = begin_access [deinit] [static] [[REF]] : $*ActorAddress
80+
// CHECK: destroy_addr [[ACCESS]] : $*ActorAddress
81+
// CHECK: end_access [[ACCESS]] : $*ActorAddress
82+
83+
// As well as the actor system:
84+
// CHECK: [[REF:%[0-9]+]] = ref_element_addr [[SELF]] : $MyDistActor, #MyDistActor.actorSystem
85+
// CHECK: [[ACCESS:%[0-9]+]] = begin_access [deinit] [static] [[REF]] : $*FakeActorSystem
86+
// CHECK: destroy_addr [[ACCESS]] : $*FakeActorSystem
87+
// CHECK: end_access [[ACCESS]] : $*FakeActorSystem
88+
// CHECK: br [[FINISH_DEINIT_BB]]
9489

95-
// CHECK: [[AFTER_DEALLOC]]:
96-
// CHECK: builtin "destroyDefaultActor"([[SELF]] : $MyDistActor)
97-
// CHECK: [[CAST:%[0-9]+]] = unchecked_ref_cast [[SELF]]
98-
// CHECK: return [[CAST]] : $Builtin.NativeObject
9990
// CHECK: } // end sil function '$s14default_deinit11MyDistActorCfd'
10091

92+
// This test checks that we resign the identity for local deallocations,
93+
// destroy only the correct stored properties whether remote or local, and also
94+
// destroy the executor.
10195

10296
// MARK: local actor check
10397

@@ -111,7 +105,6 @@ actor SimpleActor {
111105

112106
// additionally, we add basic coverage for a non-distributed actor's deinit
113107

114-
115108
// CHECK-LABEL: sil hidden{{.*}} @$s14default_deinit11SimpleActorCfd : $@convention(method) (@guaranteed SimpleActor) -> @owned Builtin.NativeObject {
116109
// CHECK: bb0([[SELF:%[0-9]+]] : $SimpleActor):
117110
// CHECK: [[REF:%[0-9]+]] = ref_element_addr [[SELF]] : $SimpleActor, #SimpleActor.someField

0 commit comments

Comments
 (0)