Anyone interested in resurrecting an open source strict Ada 83 (mil-std 1815A) compiler?

The IR isn’t exposed at all right? Using the --ir flag seems to be for dumping LLVM IR. I have to experiment with it more, but it seems interesting as a bootstrap compiler for sure.

Yes, it would be interesting if there were multiple backends; assembly is great, but something like QBE or LLVM would be helpful as well. I was just talking to @tomekw on a different site about QBE and Ada.

TBH I was thinking about playing with QBE backend and writing a frontend for Ada, but I’m not a cat and I have only one life :sweat_smile:

I don’t know; but he did say he used DIANA for inspiration, so it’s architected as Source → DIANA-like IR → LLVM IR. He may have a Dump DIANA-IR function, or may not.

I’m not familiar with QBE.
The route I’m using in my plans is:
Source → DIANA [→ A second, more low-level IR] → SeedForth, where SeedForth is a minimal, tokenized Forth, and doing the SeedForth interpreter in Ada/SPARK, thus giving a proven ‘VM’. Which means that SeedForth could be viewed as a bytecode.

Take the SeedForth interpreter and compile it with the previous and you now have a verified SeedForth-interpreter in SeedForth. Which means that to re-host, all you have to do is implement the SeedForth words with the new architecture’s implementation. (This can be done with an array indexed into by the token, and executed.)

-- Operation could be an enumeration, to have better mnemonics.
Type Operation        is range 1.. 64 with Size => 8; -- Assuming 64 native words; 1-byte size.
Type Execution_Length is range 0..255 with Size => 8;
Type Execution_Operation is record
  Actual_Length  : Execution_Length;
  Execution_Data : array(1..Execution'Length'Last)
end record;

-- You would overwrite this table with the machine-code for the new
-- architecture to port; that's all you have to do!
Intrinsic_Word : Constant Array(Operation) of Execution_Operation:=
( <<Insert Length/Machine-Code Pairs Here>> );

After that, you put your Ada compiler through itself to get the SeedForth, run that in the interpreter, and Bam!, you’re on a new archetecture. (Albeit, interpreted.)

What is the main draw for QBE?

  1. Simplicity: easier to learn and understand.
  2. Culture: I’m anti-LLM.

Right, the stated goal is to be “70% of industrial compilers in 10% of the code.” Basically you need a C compiler and Make to build it, but you can generate code for a fair number of platforms without a huge bootstrap and building process (it takes seconds to build on most of my machines, for example). It has some downsides and design choices (like pointers are just integers with specific operations), but it’s pretty nice and has a pretty decent community around it that uses it.

Hi.all

I once considered using QBE, or a QBE like approach. But experience of the real backend implementation led me to fasmg.

A backend is like the frontend, a layered system. From DIANA which represents the Ada program, you have to solve two categories of problems : 1) memory model and data layout, 2) executable operations and compute base.

In the TLALOC expander, the fasmg namespace system is the main structuring tool for data memory layout, it supports Ada scoping and typing definitions enclosing. The compute base of TLALOC is a stack machine whose definition is in term of fasmg macros. It is more complex than I expected at first (real world…). Before I started this crazy enterprise, I also had a Forth like simple vision ; effective implementation demonstrates it is more complicated. A Forth executor should be below the LLIR as present in TLALOC, that is at the compute base level because Forth has no memory layout definition support.
QBE also has a similar problem of data structuring rusticity, a QBE or llvm layer pertains to computing optimisation and binary production.
In TLALOC, fasmg macros allow for direct binary execution of the stack machine operations. This allows “easy” porting to other processors similar to x86 (aarch, riscv).

The fact that fasmg allows for solving two problems with one tool leads to believe that the backend is one monolithic phase, it is not the case. There is an expansion phase and an assembly phase.
Also macros could produce a secondary LLIR computation oriented and adapted to optimization. That would put an optimization phase and a binary emission phase (and perhaps a link phase) in place of the present static assembly.

The present system adopted in TLALOC is very convenient to develop the expander which is a much more complex piece of software than I imagined. Imagination…

Actually, you don’t need a C compiler if you don’t link to libc. You only need an assembler ar and linker

I’ve been doing some hacking on the AdaDoom3/Ada83 compiler and It’s been super fun. Having the project be maximally simple really helps me get into it.

I rebuilt my no-build library in Ada 83 and was fixing the bugs that fell out along the way. It still throws a bug or two my way each day but I imagine with a bit of time fixing it up like this it could even be daily drive-able.

Talking to AdaDoom3 IRL - once the compiler is mature enough the plan is to convert the source to Ada 83 and the C version just lives in /bootstrap or /stage1 or some similar sub directory. That way you can compile on complete new platforms (they always have a C compiler)

Hi.all !

Ada83/AdaDoom3 compiler is an impressive AI agents supported piece of work, but it is a completely different implementation approach compared to TLALOC.

My aim is to have a clearly structured compiler which can be audited by humans at every stage without AI help. Also it has to output verifiable and once again human readable LLIR. The binary generated must be also auditable and free of doubt with regards to its execution.

TLALOC is intended to depend from no huge external pieces of software you do not understand or you do not exactly know what it does. It should have it own runtime and tasking system.

TLALOC is written in Ada 83, so its development uses its own Ada exceptions security for its own verifications (I have bugs in the expander which have been evidenced by CONSTRAINT_ERROR in the bootstrapped compiler).
The choice of fasmg with its namespaces service is an additional level of safety, some expander flaws are detected in the assembly process.

I would like TLALOC adheres as much as I can do it to security and safety which was Ada’s mark of fabric. Writing an expander that really does what is expressed in Ada 83 is not at all easy.

Ada83/AdaDoom3 somewhat frightens me. Not speaking of LLVM ! There is only one source file, that is not simplicity, can you audit this file and have some hope to examine it yourself for verified behaviour ? Namespaces do not exist in LLVM, the compiler has to do its name mangling, I fear LLVM input will be delicate to audit. It uses OS threads, this is a natural choice, but also another dependency. Nonetheless it is a lovely project, but not the same implementation choices as mine.

@VMo I’m interested to know how TLALOC handles name resolution. Does it use a two pass algorithm (first a bottom-up pass to create sets of possible definitions for each name expression, then a top-down pass that uses context to disambiguate) like the Ada– compiler?

I’ve been tinkering with my own Ada83 compiler and wanted to see how other compilers approach it since it seems very complex to properly implement.

See also: A One-Pass Algorithm for Overloading Resolution in Ada.

Hi.all !

Let us call T1 the executable from TLALOC sources compiled by gnat and T2 the TLALOC executable from TLALOC sources compiled by T1.

T2 for the first time passed all phases (par_phase to write_lib) on _standrd.ads producing a FINC macro file identical to that obtained from T1.

Bootstrap is underway. Step by step the target is to compile all TLALOC sources with T2 to obtain a viable T3, a compiler executable entirely produced without gnat.

This is great to see! Are you going to change how the compiler is called (from the command line) for T2 and beyond once that stabilizes?

Hi csb6 !

TLALOC does not strictly use two complete passes over the syntax tree.

In semantic phase (/src/sem_phase/idl.sem_phase.adb) there is one main walk of the DIANA tree (/src/sem_phase/idl.sem_phase.nod_walk.adb) . Lexical regions are entered and left during that walk, so the visibility environment is built incrementally as declarations become visible.

Main concerned files are :

  1. /src/sem_phase/idl-sem_phase.adb
  2. /src/sem_phase/idl-sem_phase-nod_walk.adb
  3. /src/sem_phase/idl-sem_phase-exp_type.adb
  4. /src/sem_phase/idl-sem_phase-expreso.adb
  5. /src/sem_phase/idl-sem_phase-vis_util.adb
  6. /src/sem_phase/idl-sem_phase-red_subp.adb
  7. /src/sem_phase/idl-sem_phase-set_util.adb

Inside that unique walk, there are bottom-up and top-down movements.

TLALOC represents ambiguity with two set types:

  • DEFSET_TYPE: possible definitions of a name;
  • TYPESET_TYPE: possible types/interpretations of an expression.

The first stage is bottom-up.

For a name, FIND_VISIBILITY constructs a DEFSET containing the declarations that are still visible possibilities. It handles lexical nesting, use visibility, hiding, homographs and overloadable declarations.

EVAL_EXP_TYPES then recursively evaluates an expression and produces a set of possible types. For simple names this is derived from the DEFSET; for compound expressions it recursively evaluates their operands.

Function calls are a good example. EVAL_SUBP_CALL first obtains all visible definitions of the function name. REDUCE_APPLY_NAMES recursively calls EVAL_EXP_TYPES on every actual parameter and compares each resulting TYPESET with the formal parameter types of every candidate overload. Candidates whose profiles cannot accept the actuals are removed.

The result types of the remaining overloads form the TYPESET of the call.

The semantic context then further reduces these sets through routines such as REQUIRE_TYPE, REQUIRE_SAME_TYPES, REQUIRE_BOOLEAN_TYPE, etc.

Then the resolving is top-down.

Once the enclosing construct determines the required type, it calls roughly:

EVAL_EXP_TYPES( EXP, TYPES );
REQUIRE_TYPE( EXPECTED_TYPE, EXP, TYPES );
EXP := RESOLVE_EXP( EXP, TYPES );

RESOLVE_EXP requires a unique remaining type and then recursively resolves the expression using that type as an expected type.

For an overloaded function call, RESOLVE_FUNCTION_CALL fetches the previously computed candidate DEFSET, uses the expected result type to reduce it further, requires a unique definition, and binds the name to that definition.

Once the particular subprogram has been selected, RESOLVE_SUBP_PARAMETERS goes back through the actual parameters and resolves each one using the type of the corresponding formal:

RESOLVE_EXP( ACTUAL, FORMAL_TYPE)

So there really is a top-down propagation of contextual type information after the bottom-up construction of possible interpretations.

TLALOC even temporarily stores the candidate sets in the DIANA nodes themselves: STASH_DEFSET/FETCH_DEFSET use SM_DEFN, and STASH_TYPESET/FETCH_TYPESET use SM_EXP_TYPE. During final resolution those temporary sets are replaced by the selected definition and actual expression type.

So TLALOC uses one semantic tree walk, containing a local recursive two-direction resolution algorithm: first collect possible interpretations bottom-up, then reduce them using context and propagate the selected types top-down.

It is not a direct “pass 1 then pass 2 each over the whole tree”, because contextual restrictions can also be applied while recursively evaluating particular constructs. But the underlying idea is similar to the two-pass scheme.

For overload resolution in particular the rough flow is:

visibility -> DEFSET -> evaluate actuals -> TYPESETs -> filter overload profiles -> possible result TYPESET -> apply enclosing expected type -> select overload -> resolve actuals with formal types.

That explicit preservation of sets of interpretations is the main technique TLALOC uses to cope with Ada 83’s context-dependent overloading.

Hi.all !

Two years after the first post on this topic, TLALOC T2 boostrapped (T1 compiled) compiler produced from sources by TLALOC T1 has successfully compiled 10 predefined Ada 83 packages and produced FINC macro files compared strictly identical to those of TLALOC T1 (gnat compiled).
Next step is compile all TLALOC compiler sources with T2 to obtain T3, a full home produced executable.

Hi.all !

proud to announce a birthday !

TLALOC T3 = TLALOC T2
Brest August 12th 2026 13h30 French time

  • TLALOC sources frontend and expander have been compiled by gnat producing exec T1.

  • T1 compiled all 63 pieces of TLALOC into generation 1 FINC files assembled into T2 by fasmg (1’44" processor time).

  • T2 compiled all 63 pieces again into generation 2 FINC files compared byte identical to generation 1. 63 pieces of FINC assembled into T3 executable identical to T2.

Ada 83 / TLALOC is the first open source Ada 83 compiler written in Ada 83 compiling itself.

Beaucoup de travail mais ça fait plaisir !

Last updated sources on ~vincent_morin/Ada_83_TLALOC - Full MIL-STD-1815 Ada 83 compiler modern implementation with fasmg backend - sourcehut git

Also updated on framagit and github (heavy historical repos).

If someone wanted to add a new back end, where’s the place to start?

Félicitations Vincent :slight_smile:

Hi Lucretia !

There are several possible paths depending on what you want to do. I set aside rewriting a complete expander (though it would be easier now that there is a working example, it would remain a heavy job, and would need updating on the path of adding some unimplemented features).

The feet of the TLALOC expander are completely in codi_x86_64.finc. This file is a fasmg macros file defining the target stack machine. It thus defines the (primary) LLIR. When assembly is done, binary is directly produced for the target processor/OS the codi is written for. The exec is about 3 times the gnat exec because there is no optimization and each basic stack machine instruction has several processor instructions. But execution times are very acceptable.

The easiest path is thus producing another codi_xxx.finc and assemble ordinary FINC files with this new footing. Nothing has to be modified in the compiler proper, it is just the stack machine instructions binary translation, and system calls adaptation. Also, if the expander is enhanced, the adaptations will follow naturally.

In fact some time ago I already tested a codi_arm64.finc for aarch/Linux (in the repos it is in the /src/expander/fasmg/ directory. It is presently out of sync with codi_x86_64.finc which has been enhanced and now a stable reference.

The syncing of codi_arm64.finc is a few days with AI, it is an easy translation if you stay on Linux.
At the time I did it, it allowed to run an ELF on an Orange Pi 3b with aarch64 processor.

I also produced a codi_riscv64 in the same directory which is out of sync also and has not been tested (Until now I did not succeed in installing Debian on the Starfive I bought…).

Then if you want to target another operating system than Linux, things can complicate a bit because you will have to find a way to access character and file io (if you use them in the programs you compile). If you do not use ios, or just a simple UART and no files, the codi path is still usable and bare metal could in principle be done.

Another more complicated path would be producing secondary LLIR codes with primary LLIR fasmg macros so that an optimizer can be run before it produces the final binary. This is much more work and needs reflexion. But it is something I had in sight.

I also insist on the fact that TLALOC, though fairly complete because it compiles itself is not a complete Ada 83 conformant compiler. Some specific services not needed to bootstrap are not implemented (for example general representation clauses, the compiler does use some representation clauses, but only within quadwords. Also tasking is inexistent and generics are enough to bootstrap but not absolutely general. So much work remains to be done to have a totally complete and conformant compiler. But a good part of the way has been done so that many useful programs should be possible. Another point to keep in mind : TLALOC compiles correct Ada 83 but is not guaranteed to always detect incorrect Ada. Of course main errors are signaled, but I know that some incorrect semantic can pass (but crash in fasmg so they do not silently pass). In its present state TLALOC is a tool for specialists. Nonetheless it is a unique piece of open software and worth being taken care of.