The difference in results has to do with pointer provenance tracking and aliasing. With both approaches, the first call to an interface function will almost definitely be devirtualized. The problem is that that first call will also modify implementation state. If the implementation function is not inlined (which is common), this is tracked as a modification to the memory region containing the implementation state. But with the fieldParentPtr model, that's the same memory region containing the vtable! So this breaks constant propagation on the vtable and any later calls must always be fully virtual, even if the optimizer can see the whole way from vtable creation to virtual call.
You can introduce intentional graph cuts if you want, and build multiple objects, but you are limited to the C ABI at these boundaries.
You can then cache the function call
When your vtable is in the object you are mutating, it needs to read each time