Implementing ZFP runtime

I was looking at The predefined profiles in order to understand the difference between Ada runtimes and profiles. I would like to ask a few basic questions:

  1. Am I correct in thinking that a particular Ada runtime could support multiple profiles like Ravenscar and Jorvik but most of the time each runtime is designed for one specific profile?
  2. What about ZFP runtime, does it support any profiles, or profiles are more about restricting Ada tasking, hence it is not relevant to ZFP runtime which does not support any tasking?
  3. I would like to understand how ZFP runtime is integrated into Ada so I could implement my own version of this runtime. The link above refers to a list of predefined packages for ZFP, so is it the case of simply copying those packages from GNAT full runtime or is there more to implementing a generic ZFP? Is there any documentation that describes how this should be done?

I’ve been looking at the existing open source projects:

However I’m trying to find documentation or howto guides that would allow me to understand how ZFP runtime should be designed. Something similar to https://wiki.osdev.org/Ada_Bare_Bones but maybe more up to date.

WHY do you need one more runtime? What you want to achieve?

ZFP is just a minimal subset of runtime’s packages. It is almost cross-platform. There is no need to develop anything, just take it and use.

Ravenscar/Jorvik is a set of restrictions applicable to much more complete runtime, mostly to tasking part.

light-tasking and embedded runtimes includes bare-board schedulers, like, for example, FreeRTOS. They provide particular implementation of runtime to run on particular platform. Both supports restricted set of runtime only.

full runtime is expected to run on top of more complicated OS, using features provided by OS (scheduling, memory management). Application might restrict use of language features when necessary.

I’m trying to achieve the equivalent of freestanding C implementation, i.e. absolutely minimal Ada runtime. This is less to do with machine resources and more to do with learning how to implement a custom runtime.

The Ada/SPARC bare_runtime seems to be more than just ZFP and contains a lot of functionality which I may not need at this time.

If I build a freestanding C cross-compiler, it also includes stdbool.h, stddef.h, stdint.h, etc., which is enough to get started with bare metal programming. However, a similar Ada cross-compiler is missing this functionality and requires a runtime. I don’t need dynamic memory allocation or tasking, but at the very least I need packages for fixed width integers and interfacing with the C language. Without a ZFP runtime, compiling Ada code fails with:

fatal error, run-time library not installed correctly
cannot locate file system.ads
compilation abandoned

I’m experimenting with Ada in the context of developing a small bootloader for SPARC V9. The open firmware provides various interfaces for reading and writing from/to the console and allocating memory buffers. I would like to implement Ada bindings for the firmware interfaces, which would then allow me to implement the bootloader mostly in Ada.

Go to osdev.org and see how to do a bare minimum runtime.

Do you need to be able just to compile code or want to use gprbuild?

For the first one just add system.ads to your sources. Look for formal definition in RM and for examples in bb-runtimes. It is enough to compile simple code using gcc. Compiler will complain about missing support units when necessary.

PS. Freestanding C compiler includes libgcc, it is like ZFP for Ada.

Yes I have been looking at these:

https://wiki.osdev.org/Ada_Bare_Bones
https://wiki.osdev.org/Ada_Runtime_Library

and various other resources. I’m trying to uncover a bit more information on how runtimes are designed and which packages are mandatory for ZFP. There are bits of info here and there in GNAT documentation, but it is missing a lot of details.

Yes, exactly.
With a full Ada runtime, the profiles are guaranteed to work; the reverse is not true.
For example, having a runtime that does not have tasking can be used by a program with pragma Restrictions (No_Tasking); as well as a full runtime, while trying to compile a program that uses tasking would be an error — remember: ā€˜profiles’ are just collections of sets of pragma restrictions with appropriate default values.

I don’t need gprbuild for my own runtime, as I can use a simple Makefile. The idea is to start with absolute minimum like system.ads plus a few other files, then over time extend it and add more functionality.

This is sadly something that isn’t documented well. The Ada RM only tells you ā€œwhatā€ the Runtime has to do but not ā€œhowā€ to do it. (For some of specifics there are multiple ways to do it and gnat is opinionated in that regard)

For now you could use this repo GitHub - reznikmm/adalib: Standard Ada library specification as defined in Reference Manual Ā· GitHub as a good starting point. There are all the ada runtime specs defined without any implementation. And this paper is far as it comes to a explanation on how the runtime works. https://www.adacore.com/uploads/books/gnat-book.pdf (not all chapters are relevant but it’s quite good none the less!)

Be warned! All Ada programs need a runtime and the one you are using (without explicitly stating) is already in your compiler. (Think of libc but for ada. It’s called ā€œlibadaā€ and included in gnat)

The ironclad kernel has it’s own rts that you can adapt (It’s even in Ada/SPARK!) Commit. <format> can be used to further edit the. It’s quite small and also a good starting point and it uses gprbuild which can be integrated later on to other ada compilation steps.


I’m actually looking into writing a guide on how to implement your own runtime! It’s not going to explain how to implement everything but at least the basics so that people like you can build on top of it.

Yes.

ZFP does not support any profiles ā€œper seā€, it’s just the bare minimum to make Ada programs working, but you don’t get OS, no I/O, nothing. ZFP implements (but this not the correct term, see later) basic functionalities to handle machine addresses, asm language and few other things.

It’s a waste of time to re-implement a ZFP, at least in GNAT. You will end up writing exactly what’s already been written. But you could do it, since it’s very instructive.

ZFP is composed of a set of files that are almost entirely definitions of types/objects and ā€œintrinsicsā€ interfaces to the underlying handling logic of the compiler. A well-designed ZFP, when compiled, produces virtually no code at all (hence the term ā€œzeroā€), because the compiler generates on the fly (by means of inline instructions) the high-level constructs. As an example, an Unchecked_Conversion is virtually an empty thing when the size of the objects match, and the bit pattern of the original object is taken as the bit pattern of the destination object. No library code.

Many RTSes claim to be ZFP, but technically this is not always true. In SweetAda the ZFP produces a nearly zero-sized library, except for few bytes that handle the initialization procedures and elaboration flags of some types, mostly present in Interfaces.C.Extensions, yet there are no library functions callable. So if you wipe out this unit, you have a zero-footprint runtime for real.

Thanks for the link, I’ve not seen this document before and it contains a lot of interesting low-level details.

I looked at the source code some time ago and got a bit confused by separate zfs, sfp, and mfp subdirectories, but I think now I understand they are different runtimes, which share some code from the common subdirectory.

With Ada there appears to be no formal definition of what a runtime should be, so for example a small footprint runtime could contain anything you like.

Yes.

The ā€œcommonā€ directory contains absolute necessary files, imported nearly always. The zfp picks up those files plus some others (in the zfp own directory). The same for sfp, which is much larger. The configuration.in located in the chosen runtime is processed by the Makefile and files are eventually filtered/edited for parameter adjustments, and you are free to pick up whatever you want. If you want to create your own runtime, I suggest to start with a copy of zfp (obviously with another name) and add (or delete) files at your will. mfp is just sfp + finalization (not working) just to import some external libraries that trigger references about entities during the bind phase.

Pay attention that parameters come also from cpu ā€œtargetsā€ directories, together with the system.ads, and override the previous ones.

Then you choose the runtime and the profile in the platform configuration.in, naming it appropriately.

The ā€œprofileā€ is another story, it imposes restrictions on the whole source code. The gnat.adc.in is a template that lets you to associate restrictions-vs-runtimes, configurable as well. Obviously your runtime name shall go in the comment field in order to be recognized and trigger an activation. You can even choose a zfp runtime with sfp restrictions, or whatever you want. The fact that a runtime has the same name of a set of restrictions, is just because it turns out to be simple to associate their functionalities.

If you want to experiment on some files, keep a backup of your changes before cloning again (or play dirty games with git), because of course they will be deleted.

Yes.

ā€œsmallā€ is a relative concept.