Source code for early GNAT versions

Does anyone happen to have copies of the source code for very early GNAT releases? The earliest I can find is GNAT 1.67 from 1993. This version is mostly written in Ada 83, but its source code contains a few uses of Ada 95 features. (e.g. child packages)

From what I can tell GNAT was first bootstrapped in June of 1993; before that point it was built using a proprietary Alsys compiler.

Would be valuable to have the earliest GNAT release that was capable of self-hosting for historical reasons and for people interested in bootstrapping GNAT without relying on pre-built binaries. (it is much easier to write an Ada 83 compiler than an Ada 95 compiler)

Maybe AdaCore has a copy?

Hi !

I did not succeed in finding sources before 1.67. There is a 1.64 but binary only. The problem with those early versions is that the environment was gcc 2.something. I guess it would be rather difficult to rebuilt the environment of 1993 (old unix, C …) on today’s machines.

That is why I wrote TLALOC as a full Ada 83 self hosted compiler. There is not an ounce of non Ada 83 in TLALOC and it targets modern processors (x86-64, AArch-64, riscv-64) under Linux.

I did some tidying on ~vincent_morin/Ada_83_TLALOC - Full MIL-STD-1815 Ada 83 compiler modern implementation with fasmg backend - sourcehut git . The compiler source is 2.5 Mb in 95 text files, everything is in it : frontend with lalr and idl tooling, expander, target coding. It compiles itself. I have the doc to update, but I’m somewhat tired after having bootstrapped it on Orange Pi 3B and Starfive Visionfive 2 after selfhosting on x86 laptop.

If somebody wants to bootstrap something in his corner, having a look at TLALOC is probably easier than remounting a gnat 1.6x . But It is interesting for history though of course.

The problem with those early versions is that the environment was gcc 2.something. I guess it would be rather difficult to rebuilt the environment of 1993 (old unix, C …) on today’s machines.

Yes it takes some work/patching but it is possible to build old versions of GCC in modern environments. Someone even built GCC 1.27 on a fairly recent Ubuntu system. I have been able to build GCC 2.5.7 (the version GNAT 1.67 requires) with some patching although it still needs more work to be usable.

That is why I wrote TLALOC as a full Ada 83 self hosted compiler. There is not an ounce of non Ada 83 in TLALOC and it targets modern processors (x86-64, AArch-64, riscv-64) under Linux.

TLALOC is a very cool and impressive project, but it faces the same problem as GNAT (or any other self-hosted compiler): you need an existing Ada compiler (whether some other Ada compiler or a pre-built installation of TLALOC) in order to build it.

If you use a pre-built binary, you then have to trust that binary to not perform the Trusting Trust attack or, more plausibly, not to do any bad things when run - it is an opaque executable downloaded from the Internet after all.

Of course it almost certainly would not do those things, (I do not think you or AdaCore are untrustworthy!) but some software distributions (e.g. Guix) want to have their entire ecosystems’ source code be auditable and not rely on binary blobs if possible. This prevents them from packaging GNAT and likely would also prevent them from packaging TLALOC unless it can be bootstrapped without using untrusted binary blobs.

Hi !

The egg and chicken problem ! How do you “solve” it ? With a C compiler ?
Of course TLALOC exists thanks to gnat (as gnat exists seemingly thanks to an Alsys compiler).

Now TLALOC has a special feature : the front end compiles to DIANA and then to a text LLIR (the fasmg macros). There is a complete definition of the TLALOC compiler which is written with some sort of assembly language in the fas/FINCs files.
So you could extend TLALOC by writing text LLIR, this would be writing stack machine assembly (I sometimes did it for debug purpose, db 0xC3 for stopping gdb for example).

Now gnat is only used once for compiling the T1 front end/expander exe, after that gnat is not used anymore. T2 is assembled once with fasmg from fas/FINCS texts produced by T1. T2 also produces fas/FINCs text.

When TARGET_CODE is compiled to fas/FINCs texts by T2 (or T1) and once assembled with fasmg it is used in place of fasmg. One could say that a “malicious fasmg” (a replacement of the original one) could insert unwanted binary when assembling fas/FINCs. Doing so in the T1 to T2 assembly will be of benign consequence because the malicious exe would produces only auditable text. It would be more problematic when assembling TARGET_CODE where a malicious insertion could reproduce itself at the binary level in assembled binaries.

This LLIR text assembly text can be audited scrutinized and nothing can be inserted in there without being visible. This cuts the possibility of trusting trust attack at this stage. But the LLIR to binary step should be adequately protected.

This is less of a problem than you might think, provided you do some up-front design work.

The Trusting Trust is actually quite avoidable, though it does entail a little bit more detail (and care) than we might want in implementation; in particular, consider how SPARK plus pragmas Reviewable & Inspection_Point would work together.

There is a sequence and set of builds detailed here which would do just that — and, even better, the proposed bootstrap-path has no dependency on C at all.

Exactly; and that’s also where the structured bootstrap procedure in the appendix points.

There is a sequence and set of builds detailed here which would do just that — and, even better, the proposed bootstrap-path has no dependency on C at all.

That does seem to be a possible way to bootstrap things, but I am not sure that Forth code generated by a compiler would meet the definition of what the bootstrappable.org people consider “auditable source code”, which seems to exclude most autogenerated code even if that code is technically human-readable. E.g. they would reject the C code generated by Bison or Flex as “not auditable” and insist on bootstrapping those tools first instead of trusting or trying to audit the code they generate.

Why not?
Note that (a) the actual distribution-medium there is SeedForth rather than Forth, meaning pre-tokenized, (b) the interpreter is quite simple, and (c) the use of SPARK allows proof that programs do what they say they’ll do, and pragma Reviewable and Inspection_Point allow for (1) auditability of the source, and (2) correspondence of emitted objects to the given source — literally catering to auditing.

For each inspection point, the implementation shall identify a mapping between each inspectable object and the machine resources (such as memory locations or registers) from which the object’s value can be obtained.

and

The implementation shall provide the following information for any compilation unit to which such a pragma applies:

Discussion: The list of requirements can be checked for, even if issues like intelligibility are not addressed.

Where compiler-generated runtime checks remain;

Discussion: A constraint check which is implemented via a check on the upper and lower bound should clearly be indicated. If a check is implicit in the form of machine instructions used (such an overflow checking), this should also be covered by the documentation. It is particularly important to cover those checks which are not obvious from the source code, such as that for stack overflow.

An identification of any construct with a language-defined check that is recognized prior to run time as certain to fail if executed (even if the generation of runtime checks has been suppressed);

Discussion: In this case, if the compiler determines that a check must fail, the user should be informed of this. However, since it is not in general possible to know what the compiler will detect, it is not easy to test for this. In practice, it is thought that compilers claiming conformity to this Annex will perform significant optimizations and therefore will detect such situations. Of course, such events could well indicate a programmer error.

{AI95-00209-01} For each read of a scalar object, an identification of the read as either “known to be initialized”, or “possibly uninitialized”, independent of whether pragma Normalize_Scalars applies;

Discussion: This issue again raises the question as to what the compiler has determined. A lazy implementation could clearly mark all scalars as “possibly uninitialized”, but this would be very unhelpful to the user. It should be possible to analyze a range of scalar uses and note the percentage in each class. Note that an access marked “known to be initialized” does not imply that the value is in range, since the initialization could be from an (erroneous) call of unchecked conversion, or by means external to the Ada program.

Where run-time support routines are implicitly invoked;

Discussion: Validators will need to know the calls invoked in order to check for the correct functionality. For instance, for some safety applications, it may be necessary to ensure that certain sections of code can execute in a particular time.

An object code listing, including:

Machine instructions, with relative offsets;

Discussion: The machine instructions should be in a format that is easily understood, such as the symbolic format of the assembler. The relative offsets are needed in numeric format, to check any alignment restrictions that the architecture might impose.

Where each data object is stored during its lifetime;

Discussion: This requirement implies that if the optimizer assigns a variable to a register, this needs to be evident.

Correspondence with the source program, including an identification of the code produced per declaration and per statement.

Discussion: This correspondence will be quite complex when extensive optimization is performed. In particular, address calculation to access some data structures could be moved from the actual access. However, when all the machine code arising from a statement or declaration is in one basic block, this must be indicated by the implementation.

An identification of each construct for which the implementation detects the possibility of erroneous execution;

Discussion: This requirement is quite vague. In general, it is hard for compilers to detect erroneous execution and therefore the requirement will be rarely invoked. However, if the pragma Suppress is used and the compiler can show that a predefined exception will be raised, then such an identification would be useful.

For each subprogram, block, task, or other construct implemented by reserving and subsequently freeing an area on a run-time stack, an identification of the length of the fixed-size portion of the area and an indication of whether the non-fixed size portion is reserved on the stack or in a dynamically-managed storage region.

Discussion: This requirement is vital for those requiring to show that the storage available to a program is sufficient. This is crucial in those cases in which the internal checks for stack overflow are suppressed (perhaps by pragma Restrictions(No_Exceptions)).

The implementation shall provide the following information for any partition to which the pragma applies:

An object code listing of the entire partition, including initialization and finalization code as well as run-time system components, and with an identification of those instructions and data that will be relocated at load time;

Discussion: The object code listing should enable a validator to estimate upper bounds for the time taken by critical parts of a program. Similarly, by an analysis of the entire partition, it should be possible to ensure that the storage requirements are suitably bounded, assuming that the partition was written in an appropriate manner.

A description of the run-time model relevant to the partition.

Discussion: For example, a description of the storage model is vital, since the Ada language does not explicitly define such a model.

The implementation shall provide control- and data-flow information, both within each compilation unit and across the compilation units of the partition.

Discussion: This requirement is quite vague, since it is unclear what control and data flow information the compiler has produced. It is really a plea not to throw away information that could be useful to the validator. Note that the data flow information is relevant to the detection of “possibly uninitialized” objects referred to above.

Now, how to weave these together?
Consider the following:

Pragma Reviewable;

-- Assuming DIANA as the internal representation:
Function Emit( Input : DIANA.Instance ) return Object_Code is
Begin
   Return Result : Object_Code do
      Initialize( Result );              -- Do object-construction here.
      -- Insert IR-to-OBJ transformation here.
      Optimize  ( Result );              -- Transforms and canonicalizes.
      Pragma Inspection_Point( Result ); -- Emits auditing info.
   End return;
End Emit;

Add to that SPARK proving, and you have the compiler proving the code as well as itself emitting the auditable trail — and because the SeedForth is interpreted, and these can be applied there, too, you circumvent the “Trusting Trust” issue altogether. (This is precisely because the “trusting trust” depends on the self-hosting compiler to detect it compiling itself, by forcing things to another language [SeedForth] we bypass this because it’s not the same language [self-hosting] and it is interpreted [meaning you would have to detect all possible interpreters].)

The TL;DR: forcing the code to an interpreter vastly undercuts the ability to exploit the machinery required for Trusting Trust, and since SeedForth is so small/simple [about 50 ‘words’, and tokenized], it invites writing your own interpreter if you are at all unsure.

When bootstrapping from nothing (e.g. following something like the live-bootstrap project’s sequence of steps), there would be no gnatprove tooling or compiler tooling available at the stage when we build the initial Ada compiler written in Forth. All of those tools are written in Ada, and since we do not have a compiler for Ada at that stage we would not be permitted to use those tools until later stages - we can’t build them yet. We could of course use preinstalled versions of those tools (or build versions that were previously translated to Forth), but then we are essentially back where we started: relying on either an untrusted binary or a large compiler-generated Forth program.

Now, I think it would be technically possible to go through the source code of the compiler written in Forth line by line and audit it, but I suspect it would be a mountain of code that is hard to follow given a compiler generated it, not a person. So it is not impossible but not something most people (even among people who care about bootstrapping) would want to do.

This thread has gotten off topic, though. I was not trying to debate the different ways to bootstrap an Ada compiler; I just wanted to find an early version of GNAT source code that would be of use in one possible path as well as of historical interest.

Hi there!

At some point I was looking for the same thing, but also couldn’t find a version earlier than GNAT 1.67. It would be cool to have earlier versions available.


That being said, at the time I did have bootstrapping in mind and got a bit into fiddling with Ada/Ed (even posted my findings here, last paragraph), but didn’t pursue this any further.

Now, here’s a thought: would it be possible to compile TLALOC using Ada/Ed? This way we’d get a C to Ada83 bootstrapping path that could potentially lead all the way to the latest GNAT.

That would be neat to see! Ada/Ed was at one point a certified Ada 83 compiler so it should be capable in theory.

That was actually the resource that led me to find GNAT 1.67 - thank you! I have been working on my own Ada 83 compiler from scratch with the goal of bootstrapping GNAT. I know someone else is working on modernizing Ada/Ed for a similar purpose with some funding from NLnet but not sure where they are at.

However, Ada/Ed’s code is pretty inscrutable (at least to me) and from my understanding it compiles to some kind of bytecode that is then interpreted. That works, but GNAT’s implementation assumes that complex Ada data structures can be passed unmodified into GCC and reinterpreted as C structures, so I am not sure Ada/Ed can (without extensive modifications) build GNAT in a way that can be easily linked with the rest of GCC. My plan for my compiler is to generate C as the output language to avoid this issue, but that is a long ways off.

@Fabien.C @sttaft Do you know if AdaCore still has the source code for pre-1.67 GNAT releases, specifically the first release that was able to self-host? And would AdaCore be willing to share that source code? (just a tarball would be enough) Thanks!

If you do a “git log” in the main GNAT git repository, it goes back to June 1, 1992. I think that covers it all!

Do you have a link to that repository? Or do you mean the main GCC repository?

The repository I looked at is local to AdaCore. The one at FSF seems to start at 2001, with a full merger between the AdaCore and the FSF repository by 2005. So it seems that the set of sources at the FSF go back to somewhere between 2001 and 2005.

Would AdaCore consider making some of those very early releases publicly available? (e.g. as tarballs)