Summary
This down-to-the-detail article deals with the long-term effort at GAMS to translate major chunks of its historically grown codebase into modern C++. The General Algebraic Modeling System (GAMS), initially developed in Pascal and later Delphi (due to its academic popularity), transitioned to an in-house Pascal-to-C transpiler (p3c) to address performance and portability limitations of Delphi compilers. With the stagnation of p3c maintenance, GAMS undertook a migration to C++17 to leverage improved tooling, a larger pool of programmers, and enhanced performance. This article discusses GAMS’s journey from Delphi to C++17.
The shift introduces challenges like longer compilation times due to C++’s separate compilation model, differences in language features (e.g. nested functions, array indexing, variant parts in records, “with” statements), and the need of keeping tailored handwritten data structures over the generic one-size-fits-all C++ STL containers from the standard library.
Major programs like CMEX (CoMpilation and EXecution system - the program behind the “gams”-call) and the GDX utilities are being translated. The GDX library and utilities are already fully translated into C++17 and available on GitHub as open source software. The compiler part of the C++17 translation of CMEX is at feature parity with the Delphi version, but the execution system is still a work in progress. The C++ compiler preview can be activated via the CompilerPreview GAMS option for those feeling brave.
Historical background
The General Algebraic Modeling System was first conceived in the early 1970s by a team working at the World Bank. A first notable presentation of the work in progress system was at the International Symposium on Mathematical Programming in Budapest in 1976 . A later publication in 1982 from Johannes Bisschop and Alexander Meeraus titled “On the development of a general algebraic modeling system in a strategic planning environment” already shows core concepts of the GAMS language like tabular data definition and the syntax inspired by algebraic notation with sets, parameters, equations, and variables. The first implementations of the GAMS modeling language were written in Fortran at the World Bank. During the late 1970s and early 1980s, the educational language Pascal from Niklaus Wirth reached high levels of popularity in the academic community and slowly began to overtake Fortran in terms of adoption. This explains why the main implementation of GAMS migrated over to Pascal and the vast majority of code for GAMS itself and supporting utility programs were formulated in Pascal or later in its object-oriented successor Delphi which is also known as Object Pascal. Additional information regarding the history of GAMS can be found in this blog post which commemorates the passing of David Kendrick, who was also a significant contributor to GAMS.
The p3c transpiler as intermediate step
Since the proprietary and free Pascal and Delphi compilers from Borland and its buyer companies during the years (until it eventually ended up at Embarcadero) lacked the performance in the generated machine code and portability to various platforms in comparison with modern C compilers like GCC and later Clang, Søren S. Nielsen from the Technical University of Denmark developed a Pascal to C transpilation utility called p3c (which is independent of the GNU project p2c transpiler from Dave Gillespie ). While this allowed GAMS to continue maintaining its historically grown Pascal codebase but also benefit from the advances in C tooling, the transpiler was not meant to produce code for human consumption but instead focused on generating C code that the C compiler can swiftly transform into efficient machine code. Besides the work on the transpilation tool itself and also extending it for the more recently available object-oriented Delphi language features, Søren also wrote a partial re-implementation of the Pascal and Delphi standard library functions. While some calls can be directly translated into C standard library function invocations with a similar function name, some elementary functions like rounding behave fundamentally differently in C and Pascal. Even the compiler and base libraries (like math) can lead to subtle differences e.g., when computing logarithms. For example, the logarithmic function of the Microsoft and Intel C standard libraries return slightly different results for 0.15 as argument.
The transpiler is very impressive as it supports a big subset of the Pascal and Delphi language standard even up to Delphi versions from the 2000s. It also has a good runtime performance, helpful error messages, and produces C code that can be compiled into efficient machine code while still being somewhat readable.
Unfortunately, the original author and main contributor of the transpiler, Søren S. Nielsen, unexpectedly passed away at a young age. Besides p3c, Søren was a talented researcher and co-authored the accompanying book for the library of “Practical Financial Optimization Models ” which is included with GAMS . His co-authors credit him for the GAMS implementation of many “finlib” models. His loss deeply affected GAMS and as a result, development of p3c slowed in the years that followed, but received another spark of activity after veteran GAMS developer and current president Steven Dirkse did a major rework of the p3c internals in order to make exceptions thread-safe (which included a switch from C to C++ as target language). Through a combination of limited developer need, being dependent on robust support for legacy Delphi code (e.g. in the GAMS IDE), and the intent of keeping p3c lean, the introduction of post Delphi 7 language features like generics, closures, and foreach-loops never materialized.
A less seamless debugging experience is another drawback of the two staged build approach that first transpiles the source Delphi into C before a C compiler finally produces runnable machine code. While most problems can be directly debugged using an interactive Delphi debugger like the Lazarus IDE, some more recent features like multithreading and socket communication are not fully implemented in raw Delphi and are only supported via inline C and C++ code when using the transpiler. These features require debugging the intermediate C code in a debugger like gdb or lldb.
Future-proofing the codebase via manual translation into modern C++
Why C++17?
Delphi losing popularity after the early 2000s, and thus not being able to hire Delphi experienced programmers easily, together with the transpiler getting less and less attention, increases the pressure for GAMS to finally migrate its full codebase from Delphi to C++. C++ was chosen as it is a popular systems programming language that offers modern abstractions with a limited performance penalty in comparison to other more modern languages. Its compatibility with C also seems very beneficial, as GAMS already has libraries written in C and the tools previously written in Delphi often communicate with dynamic libraries via a C API. The language version C++17 seems sensible, due to it being fully supported by all major compilers (MSVC, GCC, Clang, AppleClang, and Intel’s C++ compiler). Compared to raw C, C++ offers additional abstractions that improve maintainability like object orientation, lambdas, templates, and a more extensive standard library.
In comparison to Delphi, C++ is also a step forward in terms of available tooling. There is a bigger number of good integrated development environments for C++ (good examples being Visual Studio, VSCode, CLion, QtCreator) plus many static analyzers, compilers, debuggers, and profilers. C++ compilers can target many platforms and operating systems. In addition, there are more third-party libraries available for C++ and the language itself evolved more rapidly than Delphi in recent years.
Potential alternative choices for the new target language could’ve been Rust, Nim, Zig, or Go but they either lack in terms of seamless C interoperability (Rust), raw performance (Go), availability of tooling (Nim, Zig), or likelihood of being still relevant in 20-30 years in the future as they are quite young (see Lindy effect ). Hence using C++ looks like a safe bet despite its shortcomings due to backwards compatibility to itself (C++98) and its C roots.
Compilation speed regression
Speaking of C++ suffering from backwards compatibility: One major “downgrade” so to speak when translating software from a Pascal derived language to a C derived language is the time required for a clean rebuild. Unlike Delphi, where compilation units include both interface (e.g. function signatures) and implementation (e.g. actual function code) together, the C++ programming language shares the historically emerged separate compilation model from C. The separation of interface and implementation into different files with the preprocessor-based inclusion directives leads to significantly longer build times in comparison to the Pascal/Delphi compilation model with self-contained units and smarter “uses” dependency tracking. C and C++ compilers often must process header files many times when building a program, as the preprocessor operations like macros and defines can affect their precise contents based on the order of inclusion. The long build times in C and C++ can be slightly improved by using forward declarations where possible, reducing unnecessary inclusions (e.g. with the help of tools like iwyu ), and using caching mechanisms like precompiled headers and ccache (or sccache ). The first two require extra manual effort whereas the caching is limited in its effectiveness by cache misses due to modified compiler arguments and code in header files that depend on the setting of preprocessor defines that can differ in different parts of a project. While C++20 introduced modules as a solution and GAMS internal first experiments were already promising in 2021, currently in 2025 the compilers used for building the GAMS distributions (MSVC, Clang, GCC, Intel C++) do not offer a portable and full implementation of the C++20 modules standard just yet, hence GAMS is forced to still use the somewhat outdated C++17 standard with separate header and compilation units.
Delphi and C++ feature sets differ
While the main programming paradigms followed in both C and Pascal and descendants are matching, the designs of both languages follow different philosophies in the details. Additionally, during their evolution, Pascal and its successor Delphi received features that do not have a direct counterpart in C or C++. For example, unlike C, Pascal allows array indices to be more flexible with custom bounds, non-contiguous indices, and it even defaults to 1-based indexing (where C and the many influenced languages start counting at 0 inspired by address offsets).
Pascal also offers subrange and set types that are not available in C, but this can be imitated (mostly) with templated utility class Bounded<T,lb,ub>
. Similarly, C++ doesn’t have the object properties from Delphi, but those can be simply replaced with getters and setters methods. The switch from Delphi to C++ allowed using smart pointers for secure memory management (instead of manual Type.Create
and Type.Destroy
calls widespread in Delphi object-oriented code). Instead of nested functions and procedures, C++17 has more flexible lambda expressions that can mimic nested functions but are also more powerful.
Fixed-size strings (character buffers) like the ShortString
often used in Delphi have performance advantages over more flexible variable length strings like std::string
in C++ and can be easily replicated in C++ via a custom class built on top of std::array<char, 256>
with convenient methods for conversion into std::string
and C-style pointers to null terminated character buffers.
Another language feature that requires heightened attention when translating is variant parts in records in Delphi. In C++ they can be approximated by having a struct with union fields plus a variable storing which alternative part of the union is active. Even with this approximation, the syntax of accessing fields of the struct varies slightly from the corresponding Delphi code.
Another miniscule but very important difference between C/C++ and Pascal/Delphi is the default zero initialization of variables and fields. In the majority of cases, they behave exactly identical, but for example when constructing an object of a class or struct on heap, its fields with built-in types (like e.g. int
) are not zero initialized automatically in C++ but in Delphi they are.
Tailored data structures are more efficient than generic ones
A major performance observation after migrating to C++17 was the inadequacy of C++ STL containers as replacement for handwritten dynamic array and hashmap data structures from the GAMS codebase. Using a RB-tree based std::map
and std::vector
as naive counterparts significantly slowed down execution. This can be partly explained due to C++ standard library containers being more general and flexible than GAMS’s handwritten data structures that for example only allow growing (element insertion) but not shrinking (deletion). Doing a deep translation of these custom collection classes closed the initial performance gap caused by adoption of std::*
containers as simpler replacements.
“with”-statement: resolution can be tricky
Amongst the Delphi features not found in C++, the “with”-statement is a frequent source of headaches in the translation process. Essentially, it is meant to reduce repetition when having many instructions affecting one object, e.g. method calls or reading and writing object fields and properties. So, for example “o.x(); o.a := 2;
” can be shortened to “with o begin x; a := 2; end
”. In short code snippets this seems like a wise way to reduce redundancy and typing effort. But, when this feature is used inflationary in a nested way in huge functions, it can get difficult to determine which object a certain line of code refers to. While the language specification clearly defines the last object having a “with binding” to take precedence, it can still involve lots of scrolling in the codebase to identify the relevant object which has the field or method that is being used somewhere later in the code. Hence a more pragmatic way to resolve the with statement is looking at it in the Lazarus IDE (a graphical IDE for FreePascal) debugger, which displays the referred object as a tooltip over the line. Yet, the “with”-statement is widely known to be problematic and is nowadays only used with care by modern Delphi programmers.
Fine grained memory management
Another fine detail where Delphi and C++ differ is in memory alignment, packing, and sizing minutiae. One example is the default width of variables with enum type, which in turn can affect the memory layout of structs and introduce unintended padding that causes increased memory usage. Just naively declaring an enum causes variable instances of it to have the full width of an int (4 bytes on x64). The workaround is to explicitly declare it as “enum name : uint8_t { … }
” to force it being stored in a single byte, which is possible for enumerations with values in the range of 0..255
.
While structs/records and class objects must be stored on the heap in Delphi, they can be placed on the stack sometimes in C++. In cases where that is not feasible e.g. due to late initialization, smart pointers like std::unique_ptr<T>
can be used to at least make their deconstructor-calls and freeing automatic. So, C++ allows more data to be stored on the “faster” stack instead of being forced to use the heap like in Delphi.
Current status and outlook
Major GAMS programs that were translated or are in the process of translation are the Compiler and Execution System (CMEX) and the library and utilities for GAMS Data eXchange files (GDX). The utilities are most notably gdxdump (print GDX contents), gdxmerge (merge two GDX files), and gdxdiff (compare two GDX files). While the GAMS compiler in C++17 (internally called CPPMEX) is pretty much at feature (and performance) parity with its original Delphi implementation, the execution system is still a work in progress and only works for two dozen of simple GAMS Model Library models yet. But work progresses swiftly with the full GAMS CMEX being translated into C++ soon. If you want to experiment with the C++ translation of the compiler, you can manually opt into activating it by using the CompilerPreview GAMS option.