The basic idea to implement this is to associate a table with function pointers with each object. Here, a table would have two entries: one for the GetColor() method and one for the Area() function. The Shape class and the Circle class each have such a table. The tables differ in the entry for the Area() function, which calls different code depending on the type of the object. This table is called the virtual method table, often abbreviated as vtable.
The vtable alone is not useful. We must connect it with an object. To do so, we always add a pointer to the vtable as the first data member to the structure. At the LLVM level, this is what becomes of the @Shape type:

@Shape = type { ptr, i64 }

The @Circle type is similarly extended.
The resulting memory structure is shown in Figure 5.1:

Figure 5.1 – Memory layout of the classes and the virtual method tables
In terms of LLVM IR, the vtable for the Shape class can be visualized as the following, where the two pointers correspond to the GetColor() and GetArea() methods, as represented in Figure 5.1:
@ShapeVTable = constant { ptr, ptr } { GetColor(), Area() }
Furthermore, LLVM does not have void pointers. Pointers to bytes are used instead. With the introduction of the hidden vtable field, there is now also the need to have a way to initialize it. In C++, this is part of calling the constructor. In Oberon-2, the field is initialized automatically when the memory is allocated.
A dynamic call to a method is then executed with the following steps:

  1. Calculate the offset of the vtable pointer via the getelementptr instruction.
  2. Load the pointer to the vtable.
  3. Calculate the offset of the function in the vtable.
  4. Load the function pointer.
  5. Indirectly call the function via the pointer with the call instruction.
    We can visualize the dynamic call to a virtual method, such as Area(), within LLVM IR, as well. First, we load a pointer from the corresponding designated location of the Shape class. The following load represents loading the pointer to the actual vtable for Shape: // Load a pointer from the corresponding location.
    %ptrToShapeObj = load ptr, …
    // Load the first element of the Shape class.
    %vtable = load ptr, ptr %ptrToShapeObj, align 8

Following this, a getelementptr gets to the offset to call the Area() method:

%offsetToArea = getelementptr inbounds ptr, ptr %vtable, i64 1

Then, we load the function pointer to Area():

%ptrToAreaFunction = load ptr, ptr %offsetToArea, align 8

Finally, the Area() function is called through the pointer with the call, similar to the general steps that are highlighted previously:

%funcCall = call noundef float %ptrToAreaFunction(ptr noundef nonnull align 8 dereferenceable(12) %ptrToShapeObj)

Leave a Reply

Your email address will not be published. Required fields are marked *