First, I understand why virtual destructors are needed in terms of single inheritance and deleting an object through a base pointer. This is specifically about
It works because the standard says that it works.
In practice, the compiler inserts implicit calls to ~A() and ~B() into ~AB(). The mechanism is exactly the same as with single inheritance, except that there are multiple base destructors for the compiler to call.
I think the main source of confusion in your diagram is the multiple separate vtable entries for the virtual destructor. In practice, there will be a single entry that would point to ~A(), ~B() and ~AB() for A, B and AB() respectively.
For example, if I compile your code using gcc and examine the assembly, I see the following code in ~AB():
LEHE0:
movq -24(%rbp), %rax
addq $16, %rax
movq %rax, %rdi
LEHB1:
call __ZN1BD2Ev
LEHE1:
movq -24(%rbp), %rax
movq %rax, %rdi
LEHB2:
call __ZN1AD2Ev
This calls ~B() followed by ~A().
The virtual tables of the three classes look as follows:
; A
__ZTV1A:
.quad 0
.quad __ZTI1A
.quad __ZN1AD1Ev
.quad __ZN1AD0Ev
; B
__ZTV1B:
.quad 0
.quad __ZTI1B
.quad __ZN1BD1Ev
.quad __ZN1BD0Ev
; AB
__ZTV2AB:
.quad 0
.quad __ZTI2AB
.quad __ZN2ABD1Ev
.quad __ZN2ABD0Ev
.quad -16
.quad __ZTI2AB
.quad __ZThn16_N2ABD1Ev
.quad __ZThn16_N2ABD0Ev
For each class, entry #2 refers to the class's "complete object destructor". For A, this points to ~A() etc.