❌ About FreshRSS

Normal view

There are new articles available, click to refresh the page.
Before yesterdayNews from the Ada programming language world

Following GNAT's advice to fix elaboration order circularities itself just takes me in circles

As I've previously posted here, I'm porting a medium to largish Ada codebase from the DDC-i Ada compiler to GNAT Studio in the hopes of creating a simulator/emulator/trainer/brain-in-a-box for the device this code is the firmware of. The signature software architectural style of the company who wrote this code, across projects and languages, is to make giant hairballs where everything depends on everything. There's no obvious conceptual order to use for an elaboration order, and evidently they had developed around whatever accidental/implicit order DDC-i compiled things in.

Examples:

  1. The main event loop calls logging. The logging package sends log messages to the comm package. The comm package deposits "please do I/O" events on the main loop.
  2. Package A provides types and procedures for serial data format A. Package B provides types and procedures for data format B. Packages A and B 'with' each other and use types and procs from each other.
  3. The main event loop needs types from data formats A and B to send to subprocedures. Instead of the authors creating spec file for shared types with no dependencies, the main loop must 'with' packages A and B to get those types. A and B are mutually co-dependent; see: problem 2. Also, A and B perform logging; see: problem 1.

It goes on and on like that; GNAT produces >2,000 lines of elaboration order circularities detected. Actually before I cleaned it up some, it was so bad that processing this codebase was causing the compiler to crash with internal errors, sometimes producing an exception message, sometimes just halting with no output. I couldn't get a good minimal example for a bug report because it wasn't deterministic and the last code location it printed before dying wasn't (evidently) where the problem code was (and I'm not at liberty to share the unredacted code).

I got some advice on my last post(s) about this. Advice to mark packages as "with Pure" or "with Preelaborate" didn't have any effect, and I eventually reverted that change. The advice that helped was for each package to have a pragma Elaborate_All listing all depended-upon packages, and progressively relax things from there. That accomplished two things; it made the compiler complain about circularity problems early, and it seems to avoid the compiler crashing later.

Now the problem I have is, once I've identified an elaboration order I think will work, and I take one or more depended-upon packages out of the list, it doesn't change the complained-about circularities at all. Or if I try to follow the compiler's suggestions it will give advice either telling me to do what I've already done, or it will go in circles. I.e. it'll tell me change Elaborate_All to Elaborate for a package; on recompile it says remove Elaborate for the same package; on recompile it says change/remove Elaborate_all for the package that isn't even listed anymore! Or the suggestion tells me to put things back to how I had them in the first place.

Now I realize that there are actual unresolvable circularities in the organization of this code, but it must also be the case (surely?) that it is possible to have two packages A and B whose .ADB (body) files call each other, as long as their .ADS (spec) files don't mutually depend upon each other? Taking the previous example of data formats A and B, I pulled out the shared types into new separate spec files that don't have any dependencies. Now it's just the bodies of packages A and B that call procs or functions each from the other. Yet, I still can't make GNAT happy no matter which way I pull them out of the Elaborate_all pragmas or attempt to influence the elaboration order. Why?

The closest thing I found to answering this is "https://groups.google.com/g/comp.lang.ada/c/aRUD89LJIT0". It starts out asking about Parent.Child packages, but the main loop package in my codebase has a lot of child packages, so that's relevant anyway. What that thread seems to be saying is that pragma Elaborate_all is transitive, whereas pragma Elaborate is not transitive. That would seem to make some sense, and could explain why changing just a few pragmas at a time doesn't change the number and content of warning messages I'm receiving (if other, transitive uses of the package are causing its dependencies to be early elaborated anyway).

Although even there there's room for confusion what transitive means: is it transitive only along unbroken chains of Elaborate_all, or does Elaborate_all override Elaborate, transitively? That is, given file A has pragma Elaborate_all(B), file B has Elaborate(C), does A's use of Elaborate_all transitively transmogrify B's "Elaborate(C)" into an "Elaborate_all(C)", or does B's use of plain Elaborate terminate the chain of transitive Elaborate_all's? To borrow terminology from regular expressions, is Elaborate_all greedy or non-greedy?

The other thing in that thread which could help me understand, but due to insufficient detail leaves me feeling even more confused, is that later in the thread they say Elaborate_all is actually the default for with'ed packages (that GNAT is more strict than standard Ada, in this respect). Okay if that's the case it would certainly help explain why removing individual names from Elaborate_all pragmas didn't change the circularity warnings. On the other hand if it was already the default, why did explicitly adding them fix my compiler crashes and get me further towards a full compilation?

They say in that thread that if you don't do anything you get Elaborate_all for your with'ed packages, so you have to explicitly put the package name into a pragma Elaborate to change GNAT's behavior. But, what if you don't want to Elaborate_all *or* Elaborate? Where's the pragma Elaborate_None? There's a pragma Elaborate_Body, but that's the OPPOSITE of what I need; at most I need "pragma Elaborate_Spec".

I have read, "https://gcc.gnu.org/onlinedocs/gnat_ugn/Controlling-the-Elaboration-Order-in-Ada.html" but it still doesn't answer my basic question: what do you do if you have packages A and B that need to call each other, only in their bodies (but not in the spec)? In C/C++ this is trivial: each .c or .cpp file could include the .h (header) file for the other, which provides the function specifications, but does not require each to be compiled before the other. Are you telling me Ada (as GNAT strictly interprets it) can't/won't do that? I understand elaboration order is different from compilation order, and has to do with initialization of static resources, but the problem to be solved, "how do I make the compiler happy when I have two different compilation units which mutually call into each other?" is still the same.

P.S. Some other advice I received was to try compiling smaller subsets of this codebase, fix problems there and accrete packages as I get them working. That's sensible advice that I'd also give myself. Unfortunately I can't see how to implement it here because everything is so inter-connected. If there were pieces without dependencies I could pull out and compile separately, they wouldn't have elaboration order circularities! Bottom-up the most I can pull out is trivial definitions (specs) files - many of them I created myself by pulling common shared types out of circularly dependent packages - but as soon as you get one level removed from that you get into The Hairball where everything depends on everything. Approaching the pulling out of packages top-down, I would have to stub out so many packages and hundreds of methods that it seems not worth the effort. And right in the middle sits this main loop package that has a dozen child packages that's even worse because I'm not sure how to separate a child package from the rest of it and vice-versa.

submitted by /u/valdocs_user
[link] [comments]

What's the best way to go about fixing the elaboration order in a largish pile of Ada code that was written without concern for it?

I have a legacy Ada codebase that I'm porting from a proprietary compiler to GNAT Studio. It generates hundreds of elaboration order warnings, and then the compiler crashes with an internal error. (I don't know if the latter is related to the former, but fixing the elaboration order seems like a place I could start.)

I'm guessing the original authors (20 years ago) relied on the arbitrary order that the proprietary compiler used, or else that compiler has its own way to work this out. I found next to no directives in the original codebase having to do with elaboration order hints.

(Interestingly a coworker of mine was having trouble building the original codebase with the original compiler and - now that I think of it - those were also ~100 errors with the word elaboration in the middle of a file name that looks like garbage memory access. I don't know what to make of this.)

Part of the problem (with my attempt to build it with GNAT Studio) might be because I ran the codebase through gnatchop which turned a some of the larger single files into several. However I went back and looked at the original consolidated files and none of the package bodies are defined before they're used; they're all defined further down than their call sites. (So I'm assuming taking the order they're in in the original consolidated file as the canonical elaboration order won't fix this as that would still have them elaborated after their calls appear.)

Or do I have an incorrect assumption baked into my interpretation of "package body not seen before use" where as long as the package body is in the same file the call can appear before the body?

(I realize my understanding of elaboration order - what it is and what it needs to be, and what needs to be done to fix this - borders on incoherent.)

submitted by /u/valdocs_user
[link] [comments]

Since MSys2 dropped support for Ada (!), how can I build Ada projects such as sdlada and gprbuild-bootstrap that require command-line tools (e.g. makefiles or bootstrap.sh) on Windows?

I'm several levels down a rabbit hole here, but if you'll bear with me I want to outline the whole chain in case there's a better way to achieve my original goal that I missed.

  • I want to port some old embedded-device Ada code to run in a gtkada application.
    • I have installed GNAT Studio and can build/run simple gtkada projects with it.
  • I downloaded the "sdlada" project to learn more about interfacing Ada with C code
    • Also I might want to use SDL visuals in my GTK app
    • However I can't directly open sdlada's .gpr files with GNAT Studio
      • Because it's missing some Ada source code that's generated in the build
      • Apparently you must use the makefile to build sdlada
      • When I try on MSys2, the makefile fails because my system is missing gprbuild
  • So I tried to follow the Bootstrapping instructions for gprbuild
    • https://github.com/AdaCore/gprbuild/
    • But the gprbuild bootstrap.sh script fails because my system doesn't have gnatmake
      • Facepalm - because MSys2 dropped Ada support
      • "There's a hole in the bucket"
  • On an MSys2 issue it was suggested a user might build gcc from source to get back Ada support
    • I have built gcc before (for a cross-compiler) so... maybe? I could try this...
    • However if the MSys2 maintainer can't get it to build for him why would it work for me?
  • But this is getting off in the weeds considering - I have GNAT Studio and can already build+run Ada programs
    • But how do I run a makefile with gprbuild from GNAT Studio on Windows?
      • Is there a "GNAT Studio Command Line" (terminal) available somewhere? (Like how an install of Visual Studio includes shortcuts to open a command line preloaded with paths to MSVC tools.)
      • Is it possible (and advisable) to try to MSys2 make refer to the tools in "C:\GNAT\2021\bin"?

Edit: although MSys2 did announce they were dropping Ada support "until further notice" (and never gave any other notice since), as a commenter below pointed out it does appear Ada support is back. I had to do two things to get the missing tools: first, a full system upgrade in MSys2 so pacman would see the re-enabled packages and second, make sure I was using the correct terminal, MSYS2 MINGW64 Shell, and not the MSYS2 MSYS shell. Then I was able to see tools like gnatmake and in turn build (bootstrap) gprbuild. Thanks to all for your help.

submitted by /u/valdocs_user
[link] [comments]

New to GNAT Studio and Ada, looking for any big open source codebases I can study that demonstrate aggregate projects, mixed C/C++ and Ada linked together?

I'm an experienced C++ developer, Ada newbie, trying to port a mixed C and Ada legacy codebase to GNAT Studio. I want to compile the C and Ada sources to multiple static libraries and use them from a GtkAda executable. Actually it's a pretty complicated situation as I'll detail below.

Initially I was going to ask what is the GNAT Studio equivalent of a workspace or solution file, because I wasn't finding it. But just before posting that question, I stumbled upon the documentation section for "aggregate project" in .gpr file. Yahtzee. ("Aggregate" was the one synonym I hadn't thought to google for!)

But, I still want to see this in use before I'm comfortable using it from scratch. My learning style is I learn best from examples. First I mimic, then I understand. The syntax seems simple enough, but the documentation on aggregate projects doesn't (for instance) devote any words to common conventions like whether the .gpr file is usually mixed in with the sources or at a directory level above, etc.

And the same applies to linking and calling the C code from the Ada code (or even calling the Ada lib from the exe, for that matter). As a C++ programmer (and also C#) I'm familiar with the issues (e.g. name mangling and ABI/calling conventions) with cross-language marshaling. I still want to look at a big (real) Ada project that does it.

Finally, I read that the aggregate project feature in GNAT Studio supports the same source file being included - perhaps with different interpretations - in multiple different projects. That's good news because this legacy codebase uses that model heavily to actually build EPROM (firmware) images for multiple similar-but-different circuit boards. I actually want to run all of them within the one Gtk executable (it will be a simulator or emulator, depending how you term it), so I'll have multiple different Ada libs, that used to build independent .hex or .bin files, now being combined into one executable.

Actually it's even more complicated than that. There's two legacy codebases, the second for a still-old but newer generation of the equipment, and I'm hoping to put both of those together too. (You'd select which version of the equipment you want to simulate with a radio button.) The codebases for those have different versions of the same files. If I had to I can just make that two different GtkAda programs.

Because of both combining what were multiple different EPROM images into one executable, and the possibility of combining two similar-but-different sets of EPROM images, I'm wondering if and how namespace collisions are manageable in an aggregate project. I solved a similar problem combining C++ codebases associated with these two equipment generations into one application by segregating the codebases by DLL. Since a DLL only exports the names you tell it to, it doesn't matter if the same name is used for different things internally; they can still be linked into the same program.

submitted by /u/valdocs_user
[link] [comments]

Porting old firmware written in Ada to modern program

I work on an MFC application (C++, Windows) that communicates over serial port to an embedded system. This piece of equipment has firmware written in a combination of assembly, C, and Ada code. Although it is an x86 processor (80196 to be exact, with about 32Kb memory), it's custom hardware and not PC based. Also the underlying OS is a unique RTOS developed by the equipment vendor, not based on any other OS or RTOS.

I'd like to run the actual firmware in a Windows program, either in an emulator or port the code to run as a Windows program so I can debug it and see where data goes as my MFC application communicates with it. Emulating the system so it runs the binary firmware is one possible avenue, but I'm writing this post to ask about the second - porting the source code so I can make a Windows program out of it.

I am experienced porting C to other operating systems, and the assembly language and RTOS functions I believe I could implement or stub out myself. (This would considerably easier than the original development of the RTOS, as I could use a higher level language and as much resources as I want.)

What I'm less strong on is the Ada code. I'm more of a C++ developer. So I'm not sure the best approach here. Is Ada more like Java (write once run anywhere) so that Ada code written in the late 80s through the 90s can also be compiled on a modern Ada compiler for different OS? Or is it like VB6 to VB.NET transition where the old style of the language is hopelessly out of date? Or kind of in-between like C where there's a lot of backward compatible support, but porting it I might have to fix places where it makes assumptions about the word size of the hardware, etc.?

What tools or compilers would you use if you were me? I'm evaluating a long-abandoned open source Ada to C++ translator (if I just transpired all the Ada code to C++ once and compiled that, it would meet my needs), but I don't know whether it was fully functioning or barely implemented before the project was abandoned.

I also thought about writing an Ada interpreter as then I could handle details of emulating virtual hardware within the interpreter. (Lest that sound crazily ambitious, or a non sequitur since Ada is typically compiled, allow me to point out writing a compiler or an interpreter that only needs to work for ONE given program is a significantly less general task than writing a full one. And C interpreters exist.)

As I write this, I'm realizing building a mixed Ada and C++ program is probably the less masochistic way to approach this (if only because finishing an abandoned translator or writing an interpreter are even more so). I think I was mostly scared of finding gcc not supporting this dialect or vintage of Ada (they used an old version of the DDCi compiler), or difficulty stubbing out the hardware support.

submitted by /u/valdocs_user
[link] [comments]
❌
❌