The easiest way to fix this is to look for the actual implementation.
Consider the following code:
struct Base { virtual void foo() = 0; }; struct Derived { virtual void foo() { } }; Base& base(); void bar() { Base& b = base(); b.foo();
And now submit this to the Clan's Try Out page to get the LLVM IR:
; ModuleID = '/tmp/webcompile/_6336_0.bc' target datalayout = "ep:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64" target triple = "x86_64-unknown-linux-gnu" %struct.Base = type { i32 (...)** } define void @_Z3barv() { %1 = tail call %struct.Base* @_Z4basev() %2 = bitcast %struct.Base* %1 to void (%struct.Base*)*** %3 = load void (%struct.Base*)*** %2, align 8 %4 = load void (%struct.Base*)** %3, align 8 tail call void %4(%struct.Base* %1) ret void } declare %struct.Base* @_Z4basev()
Since I assume that you do not yet know about IR, let it be reviewed in parts.
Come up with something to worry about first. It identifies the architecture (processor and system) for which it is compiled along with its properties.
; ModuleID = '/tmp/webcompile/_6336_0.bc' target datalayout = "ep:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64" target triple = "x86_64-unknown-linux-gnu"
Then LLVMs study the types:
%struct.Base = type { i32 (...)** }
He analyzes types structurally. Thus, we only get that Base will consist of a single i32 (...)** element: this is actually a โnotoriousโ v-table pointer. Why is this weird type? Because we will store many pointers to functions of different types in the v-table. This means that we will have a heterogeneous array (which is impossible), so instead we consider it as an array of "common" unknown elements (to mark that we provide what is), and this up to the application to cast a pointer to the corresponding the type of the function pointer before its actual use (or rather, it would be if we were in C or C ++, IR is much lower).
Jumping to the end:
declare %struct.Base* @_Z4basev()
this declares a function ( _Z4basev , the name is _Z4basev ) that returns a pointer to Base : in IR links and pointers, both are represented by pointers.
So, let's look at the definition of bar (or _Z3barv , as it is distorted). Here were some interesting things:
%1 = tail call %struct.Base* @_Z4basev()
A Base call that returns a pointer to Base (the return type is always optimized on the call site, much easier to parse), this is stored in a constant named %1 .
%2 = bitcast %struct.Base* %1 to void (%struct.Base*)***
A strange bit-bit that converts our Base* to a pointer that it changes things ... In fact, we get a v-table pointer. It was not โnamed,โ and we simply ensured in the type definition that it was the first element.
%3 = load void (%struct.Base*)*** %2, align 8 %4 = load void (%struct.Base*)** %3, align 8
First, load the v-table (name it %2 ), and then load the function pointer (pointing to %3 ). Therefore, at this moment, %4 &Derived::foo .
tail call void %4(%struct.Base* %1)
Finally, we call the function, and we pass it the this element specified here.