I'm very happy to announce that I have finally updated GNU/EDMA to support 64 bits platforms. Unfortunately I signed off as maintainer earlier this year so I still do not know how to release the update. In any case I will explain in this post what was the problem and how I solved it.
GNU/EDMA provides an interface based on variadic functions. The class' methods are defined as normal C functions, but method invocation requires some intermediate work in order to support the Object Oriented features provided by GNU/EDMA. This way, in order to invoke a method in an object, the programmer has to invoke the edma_met3
function (the 3 stand for level 3 that is the one providing OO functionalities).
This function (edma_met3
) identifies the method by name and accepts the same parameters than the underlying method the programmer wants to invoke. So, the solution was to declare this function as variadic (variable number of parameters as each method in each class will be different), and then process the parameters inside based on the class's interface information that the system has(the system keeps track of the signature of all methods of all classes).
The original GNU/EDMA version worked on 32bits. For those systems, function parameters are passed on the stack, so the edma_met3
function just needed to push the original stack again before calling the class method, something that could be done using standard C even when the code was pretty ugly....
But 64bits platform changed the ABI. Parameters are passed on registers instead of on the stack. In this new situation, there is no standard way to re-implement the previous interface in plain C. Some assembler is needed in order to manipulate the registers to have the right values before calling the final method implementation.
libFFI
The solution proposed is to use FFI (Foreigner Function Interface), that actually does exactly what we need for different platforms. So, now, GNU/EDMA portability depends on which platforms are supported by libFFI, which, is way better that what we had before.
The modification required by GNU/EDMA to make use of libFFI to invoke class methods, requires two main changes:
- The first one is to generate the FFI context required to invoke the function. This is basically a FFI specific format to store the actual function signature. To create this context for the different methods, we need to translate the GNU/EDMA string signatures into an FFI context... which is a straightforward task. However, the system to load class interfaces in GNU/EDMA requires some clean-up before going into the implementation of this change
- The second change is the method invocation itself. To be able to invoke
ffi_call
looks like we need to make a copy of all the parameters received by the function, and then build an array of pointers to all of them. Fortunately, all the changes needed to implement this have to take place atpri3x.c
.
Internal Method invocation
The actual method invocation in GNU/EDMA is a relatively complex task that involves different flow paths. Some of those paths ends up invoking GNU/EDMA primitives and a couple of them actually lead to an actual function execution. This section provides a basic description of how method invocation works.
In case you are not familiar with GNU/EMDA (which is likely the case :), method invocation is performed using the following system function:
edma_met3 (IdObj, MetName, par1, par2,...)
The first parameter is the Object on which we want to execute the method. Think of it as the this
parameter in C++ or Java that is before the methods name. Then it follows the method name that is actually a string and finally the parameters expected for that method. The function call above is equivalent to IdObj->MetName (par1, par,...
) in Java or C++.
Method invocation starts doing some sanity checks on the received parameters and then it tries to find the indicated method in the indicated object. The function doing that is _edma_locate_method
and it is much more than a search function. It provides many of the dynamic inheritance capabilities of GNU/EDMA. The function will do its stuff and return a subobject identifier that will contain the method we are looking for.
When the method is not found, GNU/EDMA still does a further check. In case the object returned by _edma_locate_method
(that may be different to the original one) is a SIU proxy, the method invocation process will be delegated to that object. The Met3
method in the SIU proxy will be invoked with all the original data. Now it is the responsibility of the SIU object to find the code and execute it. If the object is not a SIU object an exception will be thrown as the method cannot be found.
NOTE: SIU stands for Sistema de Integración Universal that is Spanish for Universal Integration System (yes I know it sounds a bit arrogant... What can I say?...I was young). SIU allows to interface to other systems by representing the external objects/entities as GNU/EDMA objects known as proxies. The proxy objects just act as interface between the GNU/EDMA and the external system. That external system may be, for instance, a Java Object, a Remote object, anything...
Otherwise, if we found the method, we look for it and update information in case the method has been override by other object. After that we invoke the method with the updated information (that is basically a new object id). Otherwise we execute the method with the original object id. In this process, the class reference count is updated (class may be different for an overridden virtual method) before and after the method execution... this is important for the hotswap capabilities of the system.
Regarding the implementation, finally I had to use the method signature to properly extract the values from the stack, into an array of unions. In the 32bits function everything were bytes on the stack, however, now, parameters goes into registers, and integer and floating point parameters goes in different sets of registers (regular registers or SSE registers). Haven't tested this in detail but va_args(valist,(long)
on a double
parameter returns the wrong value even after casting....
Internal Interfaces definitions
In order to modify GNU/EDMA to support 64bits we need to create the FFI context for each method loaded into the system. Right now there are different ways to get an interface from a class into memory. We will quickly go through all of them and then explain what was modified.
Interfaces can be loaded into the core using the following methods:
- Method 1. From a system or application repository using the default IDF format
- Method 2. From a system or application repository using a IngrIDF parser
- Method 3. Defined locally in an application (
edma_idf*
andedma_add_local*
) - Method 4. Using LEA functions
NOTE: IngrIDF is another GNU/EDMA system that allows us to write our own IDF parsers (IDF stands for Interface Definition File). IngrIDF parsers are regular GNU/EDMA classes implementing a specific interface. When a class is registered to use an IngrIDF parser, GNU/EDMA will create an object of the associated parser and then ask the object to parse the interface file... otherwise, GNU/EDMA will use its internal IDF parser..
In the rest of this document we will point out the relevant parts of the code used for loading the interfaces using these different methods so we can unify the process as much as possible
IDF format from repositories
At start up GNU/EDMA will start registering classes following the order below:
- System classes. This is done by
edma_register_system_classes()
. This are internal classes required for the basic working of GNU/EDMA. Right now this just creates theEDMA_EXCEPTION
class that is thrown by multiple primitives. - Shared Repos: This is done by
edma_add_shared_repo()
. These is a shared repository for the whole system. Class definitions are stored in shared memory and accessible for all GNU/EDMA processes. A change in this repository will affect all programs using it. - Default Repos: This is done by
edma_add_default_repos()
. GNU/EDMA application can optionally provide a list of defaults repositories to look for their classes. This is specified in a file that this function process in order to load all required classes definitions. - App Repos: This is done by
edma_add_app_repos()
. These are application specific repositories. Some times classes are really application specific and there is no point on sharing them with other processes/applications.
All the repo related calls just load class definitions from different sources but they do not really resolve classes' interfaces. Only the first call to register the system classes actually registers a class
edma_register_system_classes()
This function is defined at sclasses.c
. For the time being this function just registers the class EDMA_EXCEPTION
and adds it as a LOCAL_REPO
class.
The EDMA_EXCEPTION
class is defined in sclass_ex.c
. This includes the usual GNU/EDMA class implementation for all defined methods and the data block. The class is created using the EDMA_EXCEPTION_class_factory
, function that makes use of the edma_idf*
and edma_add_local*
functions.
The process of registration of a class using these functions is as follows:
CLASSID cid;
// Obtain a class ID
cid = edma_idf_get_free_class_id (EDMA_LOCAL_CLASS);
// Set classname, namespace and version
edma_idf_set_class_name (cid, "EDMA_EXCEPTION");
edma_idf_set_class_namespace (cid, "");
edma_idf_set_class_version (cid, 0, 0);
// Add properties
edma_add_local_class_property (cid, "type", DT_ESINT32, E_L, 0);
edma_add_local_class_property (cid, "desc", DT_EZSTRING, E_L, 0);
// Add methods
edma_add_local_class_method (cid, "print", "", (PPROC)EDMA_EXCEPTIONprint, 1, 0, 0);
edma_add_local_class_method (cid, "throw", "", (PPROC)EDMA_EXCEPTIONthrow, 1, 0, 0);
edma_add_local_class_method (cid, "set", "S32Z", (PPROC)EDMA_EXCEPTIONsetS32Z, 1, 0, 0);
edma_add_local_class_method (cid, "brief", "", (PPROC)EDMA_EXCEPTIONbrief, 1, 0, 0);
// Actually register the class in the repository
edma_idf_set_class_id (cid);
Local classes defined on applications uses also this sequence of functions.
Interface Loading
Interface loading happens on demand when needed, that is, when an object of a given class is instantiated.
The object creation process involved several stages but the one relevant for class interface loading is the first one, implemented by _edma_newobj_basic_stage
.
The first thing the system does is to check if the class is mapped in the process memory. If the class is not loaded in memory, GNU/EDMA tries to load it... firing a edma_load_class_imp
defined at clas.c
. The function tries to load the associated shared library from the appropriate location. That will depend on the repository details the class belongs to.
Before that, it checks if the interface is already defined. and if not, it tries to load it calling edma_load_class_int
defined in the same file clas.c
. The information from the interface will be later used to map the implementation (the shared library functions) to the class.
The function first checks if the class to load has an IDF parser associated. In that case it invokes the IngriDF subsystem to create a parser object and invoke the Parse
method on it for reading the interface in memory. Otherwise the _edma_read_class_interface
is invoked for reading classical EDMA IDF files.
Reading classical IDF files
The _edma_read_class_interface
function is defined in idf.c
.
This function read .idf
files directly from the disk. Once the information is extracted, it calls the edma_idf_*
functions to actually define the class interface.
For the specific case of methods, all information from the interface is stored in the class private storage area. The function also creates the method dictionary.
Normal methods are processed by _edma_read_edmaidf_met
that ends up calling edma_idf_set_met
defined in multiidf.c
, however, methods defined by interfaces (a class may implement multiple interfaces) are processed manually.
Reading interface through IngrIDF parsers.
IngrIDF parsers are regular GNU/EDMA classes implementing the Parse
method. This method makes use of the edma_idf*
functions defined in multiidf.c
to define class interfaces. This means they will just work once all other cases are supported.
Local class interface definition
The main discrepancy on loading methods definition on classes is for local defined classes versus classes being read from repositories. As described above the last case is managed by multidf.c
classes, specifically edma_idf_set_met
. The former case is managed by edma_add_local_class_method
function defined at classrt.c
.
First thing is that signatures are different:
ESint32 EDMAPROC
edma_add_local_class_method (CLASSID class_id, EPChar met_name, EPChar met_sig,
PPROC *f,
ESint32 mvirtual,
ESint32 mstatic,
ESint32 mabstract);
ESint32 EDMAPROC
edma_idf_set_met (CLASSID iC,EUint32 iP,EPChar mName,EPChar Sig,
EByte vFlag, EByte aFlag, EByte sFlag)
The local version does not force an index for the method and allows to already provide the pointer to the function. Being defined locally, the code is already loaded so we can completely define the method in one go. When loading from a interface (in a separated file), we do not know the function address (as the implementation has not been loaded yet) and we are using an index for the method.
Other than that these are the two places to include the code to integrate FFI. The FFI context only depends on the function interface so it can be easily integrated in both cases. So the solution was to refactor this code in a third function including both extra parameters so, all the method definition is done in a single place. The two function above edma_add_local_class_method
and edma_idf_set_met
now just call the new refactored function that was named edma_idf_set_met2
. I know, that was not very original.
LEA interface
The LEA interface is intended to provide a simpler, less verbose interface for in-memory local class creation. It is intended to support the definition of local classes and therefore makes use of edma_idf*
and edma_add_local*
function so it is already covered by the previous modification.
Conclusion
The problem with 64bits was related to the programs ABI that is not easily modifiable from C so I used the libffi
library that allows me to recreate this ABI programmatically. Currently libffi
support many platforms including ARM
, MIPS
, PowerPC
, Spark
, however I haven't checked them yet; .... In the coming weeks I will be posting a few tutorials specially for some features added in the previous version 0.18.6
, that are not really documented anywhere. By the way, GNU/EDMA needs a new maintainer in case somebody is interested.
■