From Code to Garbage
What are the VO Compiler, Linker & Runtime doing with my code ?
In this article I will give a description of what Visual Objects does with your object oriented code at compile time, link time and runtime, and show you the effects of some of the choices that you can make when developing your classes and when writing code.
The Example
The main sample for the paper is the following code:
CLASS Person
PROTECT _cName AS STRING
PROTECT _nAge AS DWORD
PROTECT _cGender AS STRING
METHOD Init(cName, nAge, cGender) CLASS Person
_cName := cName
_nAge := nAge
_cGender := cGender
ACCESS Name CLASS Person
RETURN _cName
ASSIGN Name(cName) CLASS Person
_cName := cName
return
The compiler
Compiling the class definition
When the compiler finds a class definition like the one above it will do a lot of things:
- It generates assembly code that allows the runtime to create the class. This code will use the (undocumented) runtime function DeclareClass(). The parameters for this function are the class name (a symbol), the optional parent class (again a symbol), the size of each object and a list of the exported instance variables and their names & types. In this case the size of the objects will be 20 bytes: 12 bytes for the actual data (strings are stored as a pointer to the actual value) and 8 bytes for a pointer to the (optional) Vtable and a pointer to the Class Structure (a structure that holds the class description). The linker will later make sure that this code gets called at start-up of the application.
- The VTable is a list of pointers to the methods and accesses/assigns and this will be created when your class is using strongly typed methods and/or accesses/assigns.
- The Class structure will be created by the runtime based on the information that the DeclareClass function has given.
Compiling the (access & assign) methods
- When compiling a method the compiler will of course check to see if the class that it belongs to is available.
- Of course it will also generate the necessary machine code to execute the code in the methods. The compiler will have to generate automatic conversions from usual arguments to the appropriate types, because the parameters of methods are not typed. Since the return value of ‘regular’ methods is also a usual, the compiler will have to convert the result back to a usual. The pseudo code generated for the name assign looks like this:STARTUP Code:
LOAD Parameter in Registers // Usual (8 byte)
CALL Usual2String // Can generate error !
STORE Register to Ivar // 4 bytes
DEREFERENCE Parameter //
STORE RESULT // 8 bytes IN EAX/EDX
CLEANUP CODE
RETURN
- Finally the compiler also generates (unnamed) code to register the method with the runtime system, using the undocumented DeclareMethod function:
PUSH 1
PUSH Address method
PUSH Name
PUSH Person
CALL DeclareMethod
- The linker will later make sure that this code gets called at startup of the application.
Compiling method calls
Let’s assume we will use the class that we have seen above in a small sample application like this:
FUNCTION Start
Local oPerson as Person
oPerson := Person{“Mr Data”,40,”M”}
? oPerson:Name
wait
The compiler will then generate the following pseudo code to create the object:
Push “M” // as USUAL, 8 bytes
Push 40 // see above
Push “Mr Data” // see above
Push #Person // see above
Push 4 // # of Usual arguments on the stack
CALL CreateInstance()
To show the Person:Name Access the compiler generates the following pseudo code:
Push #Name // as SYMBOL, 4 bytes
Push oPerson // as Object, 4 bytes
Call IVarGet() // Read property
Store Result in temp var. // 8 bytes, result = USUAL
Push temp var // as USUAL so 8 bytes
Push 1 // # of arguments for Qout
Call QOUT
You see that the code generated by the compiler does not call the access method directly, but uses the IVarGet() function. It always does that for untyped code.
Using exported Instance variables
Had we used an exported instance variable instead of an Access like you see in the class declaration below, the generated pseudo code would look like this:
Load address of oPerson in Register
Check if NOT blank // New for 2.7 !
Push contents of memory location oPerson+8
Push STRING type
Push 1 // # of arguments for Qout
Call QOUT
This code, that uses the exported instance variables, will run much faster than the code we have seen before, but unfortunately exporting instance variables is not always a good idea. For example it makes it much harder to change or extend the way in which the class works.
One of the ways to get a better performance is to type your (access/assign) methods
The effect of strong typing your methods
One of the ways to get a better performance is to type your (access/assign) methods. That could look like this:
ACCESS Name AS STRING PASCAL CLASS Person
.
ASSIGN Name(cName as STRING)
AS VOID PASCAL CLASS Person
.
If you want to use strong typed methods or accesses/assigns, you also need to change the class declaration and add declaration lines to the class definition.
The compiler uses the information in the class declaration to build the VTable:
DECLARE ASSIGN Name
DECLARE ACCESS Name
After typing the Name assign the pseudo code looks like:
STARTUPCODE
STORE Parameter to IVAR // Write IVar
MOVE EAX Parameter // Return value
CLEANUPCODE
RETURN
The code has become more efficient than the code we have seen before, that also has the conversion and de-referencing of the Usual.
The call to Name:Access in the start function has also become more efficient, since it won’t use the IVarGet() anymore:
CALL PERSON:NAME:ACCESS
Store Result in temp var. // 8 bytes, result = USUAL
Push temp var // as USUAL so 8 bytes
Push 1 // # of arguments for Qout
Call QOUT
Stub methods
When you compile typed methods/accesses/assigns, the compiler will generate some extra code that we did not see when generating ‘regular’ methods. We call this a so-called stub-method, a unnamed method:
LOAD Parameter in Registers // USUAL (8 byte)
CALL Usual2String // Can generate Type error !
PUSH Result // STRING PTR
CALL PERSON:NAME:ASSIGN // Result in EAX
STORE STRING TYPE // in EDX,
RETURN
This code will be called by the VO runtime when you try to access the Person:Name access late bound, as you can see in the code below:
LOCAL oPerson as OBJECT
oPerson := FunctionThatReturnsAPerson()
oPerson:Name := “Robert”
So the compiler will generate two methods in this case:
- The typed method
- A non-typed stub method.
The first method will be called in the generated code whenever the compiler can determine the type of an object at compile time. The second method will be used if it can’t determine the type and is called by the IvarGet()/IVarPut() en Send() functions.
OnlyEarly
Some of the VO Users did not like this double code generation, and thought it was too much. That is one of the reasons that the ONLYEARLY compiler pragma is added in 2.5.
That can give a substantial reduction of code.
This compiler pragma has two meanings, depending on the context where it is used:
- If you use it in the class definition, it tells the compiler NOT to generate stub methods and NOT to generate DeclareMethod code for the methods that follow the pragma. That can give a substantial reduction of code. In this case you use the pragma as follows:
CLASS Person
PROTECT _cName AS STRING
PROTECT _nAge AS DWORD
PROTECT _cGender AS STRING
~”ONLYEARLY+”
DECLARE ASSIGN Name
DECLARE ACCESS Name
~”ONLYEARLY-”
- The seond use of the ONLYEARLY pragma is in code that uses strong typed objects. It tells the compiler to make sure that NO late bound calls are generated. If the compiler detects a method call or the access of an instance variable or access/assign method, it will make sure that it can detect the type of the object. If it can’t detect the type, it will generate a compiler error. In your code this will look like:<
FUNCTION Start
~”ONLYEARLY+”
Local oPerson as Person
oPerson := Person{“Mr Data”,40,”M”}
? oPerson:Name
wait
~”ONLYEARLY+”
The Linker
Collect the initialization code
One of the roles of the linker in VO is to collect all the unnamed code that the compiler has generated and create a startup function that calls this code. The linker will create a special function that you can find in every VO EXE and DLL that calls this code. The name of the function is __VoDllClassInit(). Of course it is also responsible for placing all the code in the EXE/DLL, but that won’t be a surprise to you.
The Runtime
Last but not least the Runtime has an important role in creating and managing objects. We have seen that the compiler and linker have prepared the information about the class structures and methods and this code gets called by the __VoDLLClassInit() function.
This function is automatically started when the Operating System loads a VO EXE or DLL.
The picture below shows how the classes and objects will be stored in memory.

Fig. 1: How classes and objects are stored in memory
The BLUE rectangles contain structures that are stored in static memory based on the information given to the DeclareClass() and DeclareMethod() functions.
The ORANGE rectangles represent the code that is in the EXE/DLL
The YELLOW rectangles are objects located in dynamic memory.
Creating objects by the Runtime
Based on the information that the runtime finds in the class structure, it will create objects in dynamic memory. As we have described above, all objects will have a reference to the class structure and the (optional) VTable. The runtime is responsible for filling these references.
Runtime functions such as ClassName(), IsInstanceOf(), IVarGet(), Send() etc. will use the link to the class structure to get the information that they need.
Lifetime Management
The runtime is also responsible for managing the lifetime of an object. We will look at this in more detail during the VO VooDoo session. In short it works like as follows.
The dynamic memory from VO consists of two areas. Each time VO detects an ‘out of memory’ situation when allocating dynamic memory, it will start the Garbage Collector (GC). This process will then stop the VO application and start to copy the information (strings, objects, arrays & floats) that is still in use from the active area in the dynamic memory to the inactive area, and will adjust all references to the information. Usually this will generate enough space in the new area. If not, the GC will adjust the size of the Dynamic Memory to allow the new information to be stored.
When there are no more references to an object it simply ceases to exist. It does not get copied to the new dynamic memory area.
If you want to detect the moment your objects ceases to exist, you need to use a so-called Axit() method, and tell the runtime that you want this method to be called when your object ceases to exist. You can do that by calling the RegisterAxit() function. This will do two things:
- The object will me marked as ‘having an Axit’
- A reference to the object will be stored in a table.
The UnRegisterAxit() function of course does the reverse.
After the GC has moved all objects in dynamic memory to a new location in the second area, it will loop through the table that contains the objects having an Axit. If it detects that an object has been moved it will adjust the memory location in that table. If it detects that the object has NOT been moved, it will automatically call the Axit() method on the object.
So you should avoid allocating memory in your Axit() methods !
To avoid memory problems (for example if you would allocate memory in the Axit() method) the GC locks down the dynamic memory using a DynLock(). So you should avoid allocating memory in your Axit() methods!
The number of objects in the Axit table is default 16000. Before VO 2.7 the only way to increase this was through the registry. Unfortunately that setting was ‘global’ and would effect all VO applications running on the same machine. In VO 2.7 you can now increase the size of that table through the new function SetMaxRegisteredAxitMethods().