
GNATpolyglot: Mastering Multiple Languages
Many of AdaCore’s customers work on projects that utilize multiple programming languages. Typically, a mix of C or C++ and Ada and/or SPARK, and Rust is becoming an interesting topic for many projects recently. There is usually a thin binding between components written in different languages. Simple data structures, an API-like interface with one-way data passing. This thin binding is static; it is based on the interface of the component, and it does not change frequently.
As the demand for memory safe languages grows, projects are looking to convert more and more existing code into a memory safe language, such as Ada or Rust.
A Sample Project
Let’s look at a hypothetical use case. Say there is an encryption component implemented in C++ that is used in many places in a software design. There have been many problems in the past with buffer overruns, and the roadmap calls for new features to be implemented in this component. Instead of adding more complexity into the existing code, the team would like to re-implement this component in SPARK and prove the absence of runtime errors as well as the correctness of the encryption algorithms.
However, the interface between the encryption component and the rest of the code uses more advanced language concepts such as polymorphism, exceptions, and overloading. Ideally, the team would like to re-implement the encryption component in SPARK and then call it from C++. On the C++ side, the team would like to use standard C++ constructs and not have to think about how that gets translated into the SPARK side.
More specifically, say the interface defines an Encryption class. On the C++ side, the user would create an object of the Encryption class and interact with it. On the SPARK side, this is automatically translated into the proper Ada concepts.
And, as is common in software development, the interface can change as the team works through implementing the new functionality.
GNATpolyglot
Enter GNATpolyglot, a new capability in AdaCore’s GNAT Pro toolchain. GNATpolyglot creates the thick binding glue code that allows software developers to write code in one language and call an implementation implemented in another language.
GNATpolyglot starts with a definition of an interface in one language, and from there it creates proxies as well as the interface in the other language. In our hypothetical example, it would take the specification of an Encryption package in Ada and generate the bindings consisting of the proxies as well as the build environment.
The image below highlights at a high level the architecture of the thick binding. The blue boxes with white text are the pieces that GNATpolyglot generates during the build process. This code is 100% generated and not expected to be modified by developers.
GNATpolyglot generates the C++ source that is built and included in the Rest-of-Project, and when the user uses the interface, proxy objects are created on the heap for both the C++ and the Ada side, and calls are automatically made across the thick binding.

To further illustrate the process, let’s have a look at some source code. Let’s assume that we have a sample interface on the Ada side that looks like this:
package Animals is
-- Abstract type
type Animal is abstract tagged null record;
procedure Shout (A : Animal) is abstract;
-- Enum type
type Color is (Red, Green, Blue);
function Image (C: Color) return String is (C'Image);
-- Tagged type
type Parrot is new Animal with record
C : Color;
end record;
procedure Shout (A: Parrot);
procedure Repeat (A: Parrot; S: String);
procedure Call_Shout (A: Animal'Class);
-- Access type
type Parrot_Acc is access all Parrot;
-- Array type
type Flock is array (Positive range <>) of Parrot_Acc;
procedure Shout (F: Flock);
end Animals;GNATpolyglot generates full C++ proxy code for this that the user does not have to edit and can just compile and link into their main program. For illustration, beginning of the header file of that proxy code looks something like:
namespace animals {
class Animal;
enum class Color: int8_t {
RED = 0,
GREEN = 1,
BLUE = 2,
};
#if __cplusplus >= 201103L
constexpr const
std::array<Color, 3>
color_values = {
Color::RED,
Color::GREEN,
Color::BLUE,
};
#endif
class Parrot;
class Animal {
private:
protected:
Animal(Animal *_self);
public:
explicit Animal(void *data);
Animal(const ::animals::Animal & self);<not all code included>
And users would be able to create objects in their C++ code and call across that interface into the Ada implementation of the Animal class:
#include "2cpp/include/animals.h"
#include "polyglot_ada_arrays.h"
#include "polyglot_ada_strings.h"
#include "polyglot_ptr.h"
using namespace polyglot;
using namespace polyglot::ada::arrays;
class Dog : public animals::Animal {
public:
// Use the shadow object ctor (takes an extra `this` argument)
Dog() : animals::Animal(this) {}
void shout() const override {
std::cout << "Woof!\n";
}
};
int main() {
animals::Parrot p(animals::Color::BLUE);
// `Shout` and `Repeat` are bound as as member functions
p.shout();
p.repeat(polyglot::ada::strings::from_string("Ada"));
p.repeat("C++"); // the implicit constructor also exists.
std::cout << "p is "
<< polyglot::ada::strings::to_string(animals::image(p.get_c()))
<< "\n";
Dog d;
d.shout();
animals::call_shout(d);The example code above shows how the user can not only use the classes defined on the Ada side, but even override them on the C++ side. The user can write idiomatic C++ code and GNATpolyglot handles the translation between the two domains.
End-to-End Use Case
Think bindings make it easier to carve out a piece of your component architecture and re-implement it in Ada. The work of re-implementing a class in Ada can be largely outsourced to Large Language Models (LLMs), especially in an agent based workflow. LLMs have been proven to be particularly good at converting code between languages and then iteratively fixing problems, especially if an existing test harness can be reused. SPARK contracts can then be added into the code to prove the absence of runtime errors and prove functional correctness. LLMs can assist with this as well, though they often do need a bit of guidance from a software engineer.
Conclusion
GNATpolyglot is available in beta mode from AdaCore for systems that want to make calls into Ada/SPARK code from C++ and other languages and directions are planned in the roadmap. If you are interested in giving it a try on your codebase, register your interest via the webform here or reach out to your AdaCore account manager.
Author
Mark Hermeling

Mark has over 25 years’ experience in software development tools for high-integrity, secure, embedded and real-time systems across automotive, aerospace, defence and industrial domains. As Head of Technical Marketing at AdaCore, he links technical capabilities to business value and is a regular author and speaker on on topics ranging from the software development lifecycle, DevSecOps to formal methods and software verification.





