Closed ystade closed 3 weeks ago
Would the device vendor need an ABC_QDMI_query...
because it will have a different function signature than QDMI_query...
? If so, it would be better to change the signature of QDMI_query...
or introduce a new QDMI_query_xyz...
.
Would the device vendor need an
ABC_QDMI_query...
because it will have a different function signature thanQDMI_query...
? If so, it would be better to change the signature ofQDMI_query...
or introduce a newQDMI_query_xyz...
.
See https://github.com/Munich-Quantum-Software-Stack/QDMI/pull/86 for a proof-of-concept realization (still in flux/draft) ;-)
In the following, I add some lessons learned while experimenting with different options in #86.
It is possible to substitute symbols in a header by previously defining substitution rules, such as
#define <old symbol> <new symbol>
In that way, a generic "substitution" header can be defined, see device_shifted.h.in
that contains a placeholder for the prefix that can be replaced by CMake's configure_file(...)
. When this header is included in a device, the symbols are all name-shifted by the specified prefix.
The problem with that solution is, that the device_shifted.h.in
header transitively includes qdmi/device.h
with the substitutions applied. When all devices are linked statically together into one driver, the issue arises that the substitution rules for the first device that is compiled are applied since the header qdmi/device.h
will only be included once due to the preprocessor directive #pragma once.
objcopy
Another solution to rename the symbols in the compiled device libraries to rename the symbols after compilation in the object files using objcopy
on Linux, and ld
with the option -alias_list
in Mac OS, see
https://github.com/Munich-Quantum-Software-Stack/QDMI/blob/9b475de8fc5fe3dc13c57091cf10fcee22191a40/examples/device/unprefixed_c/CMakeLists.txt#L19
https://github.com/Munich-Quantum-Software-Stack/QDMI/blob/9b475de8fc5fe3dc13c57091cf10fcee22191a40/examples/device/unprefixed_c/CMakeLists.txt#L28
With this approach, no source code, neither in the header nor in the source code, must be touched for the device implementations. To this end, when statically linking all devices together into one library, the header that must be included is always the same and it suffices that this is included only once aligning with #pragma once
.
While the function symbols can be renamed with objcopy
or ld
, respectively, that is not possible for types defined by the devices. Since every device must define their type for the Job, Site, and Operation this leads to multiple definitions for QDMI_Job
, QDMI_Site
. and QDMI_Operation
. This issue cannot be resolved using objcopy
or ld
. The only way would be to define the previously mentioned types as void*
. However, this could lead to type unsafe implementations.
One way to avoid having the void*
mentioned in the previous section could be to define a struct in the driver behind the QDMI_Job
that contains a void pointer to the QDMI_Job
of the respective device together with a pointer to the actual device. It would be the job of the driver then to cast the object behind the void pointer to the correct data-type for the respective device. This way, the driver would need to deal with more complexity but the client and driver could still safely use types for jobs, sites, and operations.
As pointed out in #83 , this approach causes problems in information returned from the device. For example, when querying the coupling map, the device needs to refer to the sites. The device has only access to its representation of a site and will return those to refer to certain sites. The client receiving this information (unfiltered—as the driver does (and should) not know what information is passed there) sees the objects of the site's type defined by the device. However, the client only knows the site's representation returned from the driver, which is the wrapper around the device's sites.
As pointed out in the previous section, it is unavoidable for devices to have their own implementation of the jobs, sites, and operations, to define them under different names–and this must happen already on the source code level and cannot be achieved by applying objcopy
afterward. To facilitate the different naming of the types (and function symbols), the qdmi header must be modified (prepending the prefix) before they are included in a device's implementation. To circumvent the problem described in the first section, the header files must be copied to follow the constraint that one header is only included once. Having this copy also allows the modification of this copy in place, which makes the substitution rules, as described in the first section, superfluous.
The devices can include their modified header that defines all types and symbols implemented in the device prepended with a custom prefix. The device must then use the prefix for every function and type. By this, also the (static) driver knows the signature (including the types of parameters) from the device's functions and can cast the arguments accordingly.
For an implementation of that check out #86 .
Prefixing the functions of a device would have the following advantages:
ABC_QDMI_query...
for the company ABCOverall, most changes should involve the driver. However, the device implementations also need to include the header in a way that supports those prefixes.