Introduction
I previously described how to build a SPARC V9 cross-compiler for the C programming language in this article: SPARC V9 Boot Process – Part 2: sysboot. The resulting cross-compiler was used to compile a SPARC V9 bootloader written predominantly in C.
For complex software development, Ada is a far more suitable programming language than C. My next goal is to rewrite the bootloader in Ada, which requires building an Ada cross-compiler along with several additional development tools.
My build platform is Linux on ARM aarch64 and my target platform is bare metal sparc64. Building an Ada compiler requires an existing Ada compiler on the host system for bootstrapping. Ada compilers are available for many operating systems and can usually be installed through the operating system's package management tools.
$ /bin/gnat --version
GNAT 12.2.0
Copyright (C) 1996-2022, Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
I have a native GNAT Ada compiler 12.2.0 that can be used to build a new Ada cross-compiler. The steps required to achieve this are described below.
1. Download Source Code
The first step is to download the required development tools. These consist of Binutils, GCC, XML Ada, and GPRbuild.
# Create various build directories
BUILD_ROOT="${HOME:?}/gcc_build" &&
TAR_DIR="${BUILD_ROOT:?}/tar" &&
SRC_DIR="${BUILD_ROOT:?}/src" &&
OBJ_DIR="${BUILD_ROOT:?}/obj"
mkdir -p ${TAR_DIR:?} ${SRC_DIR:?} ${OBJ_DIR:?}
# Download the latest Binutils, GCC, XML Ada, and GPRbuild
BINUTILS_VER="2.46.0" &&
GCC_VER="15.2.0" &&
GPRBUILD_VER="25.0.0" &&
GPRCONFIG_KB_VER="25.0.0" &&
XMLADA_VER="25.0.0" &&
cd ${TAR_DIR:?} &&
wget https://ftp.gnu.org/gnu/binutils/binutils-${BINUTILS_VER:?}.tar.gz &&
wget https://ftp.gnu.org/gnu/gcc/gcc-${GCC_VER:?}/gcc-${GCC_VER:?}.tar.gz &&
wget -O gprbuild-${GPRBUILD_VER:?}.tar.gz \
https://github.com/AdaCore/gprbuild/archive/refs/tags/v${GPRBUILD_VER:?}.tar.gz &&
wget -O gprconfig_kb-${GPRCONFIG_KB_VER:?}.tar.gz \
https://github.com/AdaCore/gprconfig_kb/archive/refs/tags/v${GPRCONFIG_KB_VER:?}.tar.gz &&
wget -O xmlada-${GPRCONFIG_KB_VER:?}.tar.gz \
https://github.com/AdaCore/xmlada/archive/refs/tags/v${XMLADA_VER:?}.tar.gz
# Unpack all downloaded files
cd ${SRC_DIR:?} &&
for i in $(ls ${TAR_DIR:?}); do tar -xf ${TAR_DIR:?}/$i & done; wait
# Before building GCC we need to download a few prerequisites
cd ${SRC_DIR:?}/gcc-${GCC_VER:?} &&
/bin/sh contrib/download_prerequisites
2. Build Native Development Tools
The GCC documentation strongly recommends using a native Ada compiler that matches the version of the cross-compiler being built. Using a different version may lead to unexpected build failures. Therefore, in this step, I build a native Ada compiler that will later be used to bootstrap the corresponding Ada cross-compiler.
The native Ada compiler can also be used to build GPRbuild, a build system that integrates closely with Ada development tools while supporting multiple programming languages. Although the use of GPRbuild is optional and some developers may prefer traditional Makefiles, it can be particularly beneficial for managing large and complex projects. For this reason, instructions for building GPRbuild are included here.
# Set various build options
MAKE_JOBS=4 &&
PREFIX="${HOME:?}/gcc-${GCC_VER:?}_native" &&
PATH="${PREFIX:?}/bin:/bin:/usr/bin:/sbin:/usr/sbin" &&
CC="gcc" &&
CXX="g++" &&
unset LDFLAGS &&
export PATH CC CXX
# Build and install Binutils with static standard libs
pkg="binutils-${BINUTILS_VER:?}"; cd ${OBJ_DIR:?} &&
test ! -d ${pkg:?} && mkdir ${pkg:?}; cd ${pkg:?} &&
${SRC_DIR:?}/${pkg:?}/configure --prefix=${PREFIX:?} --libdir=${PREFIX:?}/lib \
--enable-shared --enable-64-bit-bfd \
--with-static-standard-libraries --disable-nls &&
gmake -j ${MAKE_JOBS:?} && gmake install &&
cd ${OBJ_DIR:?} && rm -rf ${pkg:?}
# Build and install GCC
pkg="gcc-${GCC_VER:?}"; cd ${OBJ_DIR:?} &&
test ! -d ${pkg:?} && mkdir ${pkg:?}; cd ${pkg:?} &&
mkdir -p ${PREFIX:?}/lib && ln -sf lib ${PREFIX:?}/lib64 &&
${SRC_DIR:?}/${pkg:?}/configure --prefix=${PREFIX:?} --libdir=${PREFIX:?}/lib \
--enable-languages=c,ada --enable-shared \
--disable-nls --disable-multilib &&
gmake --output-sync=target -j ${MAKE_JOBS:?} && gmake install &&
cd ${OBJ_DIR:?} && rm -rf ${pkg:?}
# Build and install temporary bootstrap gprbuild
# This will be used to build xmlada and final gprbuild
pkg="gprbuild-${GPRBUILD_VER:?}"; cd ${OBJ_DIR:?} &&
test ! -d ${pkg:?} && cp -a ${SRC_DIR:?}/${pkg:?} ./ && cd ${pkg:?} &&
GNATMAKEFLAGS="-j${MAKE_JOBS:?}" ${SRC_DIR:?}/${pkg:?}/bootstrap.sh \
--prefix=${PREFIX:?}/gprbuild_bootstrap \
--with-xmlada=${SRC_DIR:?}/xmlada-${GPRCONFIG_KB_VER:?} \
--with-kb=${SRC_DIR:?}/gprconfig_kb-${GPRCONFIG_KB_VER:?} &&
cd ${OBJ_DIR:?} && rm -rf ${pkg:?}
# Build and install xmlada
pkg="xmlada-${XMLADA_VER:?}"; cd ${OBJ_DIR:?} &&
test ! -d ${pkg:?} && cp -a ${SRC_DIR:?}/${pkg:?} ./ && cd ${pkg:?} &&
PATH=${PREFIX:?}/gprbuild_bootstrap/bin:${PATH} \
${SRC_DIR:?}/${pkg:?}/configure --prefix=${PREFIX:?} --libdir=${PREFIX:?}/lib &&
PATH=${PREFIX:?}/gprbuild_bootstrap/bin:${PATH} \
gmake -j ${MAKE_JOBS:?} && gmake install &&
cd ${OBJ_DIR:?} && rm -rf ${pkg:?}
# Build and install final gprbuild
pkg="gprbuild-${GPRBUILD_VER:?}"; cd ${OBJ_DIR:?} &&
test ! -d ${pkg:?} && mkdir ${pkg:?}; cd ${pkg:?} &&
PATH=${PREFIX:?}/gprbuild_bootstrap/bin:${PATH}
gmake -f ${SRC_DIR:?}/${pkg:?}/Makefile \
prefix=${PREFIX:?} SOURCE_DIR=${SRC_DIR:?}/${pkg:?} setup &&
PATH=${PREFIX:?}/gprbuild_bootstrap/bin:${PATH} \
gmake -f ${SRC_DIR:?}/${pkg:?}/Makefile -j ${MAKE_JOBS:?} &&
PATH=${PREFIX:?}/gprbuild_bootstrap/bin:${PATH} \
gmake -f ${SRC_DIR:?}/${pkg:?}/Makefile install &&
cd ${OBJ_DIR:?} && rm -rf ${pkg:?} &&
rm -rf ${PREFIX:?}/gprbuild_bootstrap
3. Build Target Development Cross Tools
In this step, the native Ada compiler built in the previous step, is used to build an Ada cross-compiler targeting the sparc64-unknown-elf architecture. Because this is a bare-metal target, the resulting compiler does not include an Ada runtime library. Without a runtime, the compiler is limited in the types of Ada programs it can build, as many language features depend on runtime support.
A future article will describe how to build a minimal Ada runtime and integrate it with the cross-compiler to support bare-metal Ada development projects.
# Set various build options
MAKE_JOBS=4 &&
BUILD="aarch64-unknown-linux-gnu" &&
HOST="${BUILD:?}" &&
TARGET="sparc64-unknown-elf" &&
PREFIX="${HOME:?}/gcc-${GCC_VER:?}_${TARGET:?}" &&
PATH="${PREFIX:?}/bin:${HOME:?}/gcc-${GCC_VER:?}_native/bin:/bin:/usr/bin:/sbin:/usr/sbin" &&
CC="gcc" &&
CXX="g++" &&
unset LDFLAGS &&
export PATH CC CXX
# Build and install Binutils with static standard libs
pkg="binutils-${BINUTILS_VER:?}"; cd ${OBJ_DIR:?} &&
test ! -d ${pkg:?} && mkdir ${pkg:?}; cd ${pkg:?} &&
${SRC_DIR:?}/${pkg:?}/configure --prefix=${PREFIX:?} --libdir=${PREFIX:?}/lib \
--build="${BUILD:?}" --host="${HOST:?}" --target="${TARGET:?}" \
--enable-shared --enable-64-bit-bfd \
--with-static-standard-libraries --disable-nls &&
gmake -j ${MAKE_JOBS:?} && gmake install &&
cd ${OBJ_DIR:?} && rm -rf ${pkg:?}
# Build and install GCC
pkg="gcc-${GCC_VER:?}"; cd ${OBJ_DIR:?} &&
test ! -d ${pkg:?} && mkdir ${pkg:?}; cd ${pkg:?} &&
mkdir -p ${PREFIX:?}/lib && ln -sf lib ${PREFIX:?}/lib64 &&
${SRC_DIR:?}/${pkg:?}/configure --prefix=${PREFIX:?} --libdir=${PREFIX:?}/lib \
--build="${BUILD:?}" --host="${HOST:?}" --target="${TARGET:?}" --with-cpu=v9 \
--enable-languages=c,ada --enable-shared \
--disable-nls --disable-multilib --disable-libssp --disable-libada &&
gmake --output-sync=target -j ${MAKE_JOBS:?} &&
gmake -C gcc --output-sync=target -j ${MAKE_JOBS:?} cross-gnattools &&
gmake -C gcc --output-sync=target -j ${MAKE_JOBS:?} ada.all.cross &&
gmake install &&
cd ${OBJ_DIR:?} && rm -rf ${pkg:?}
After completing the above steps, we obtain the following development tools:
${HOME}/gcc-15.2.0_native - This directory contains native C and Ada compilers and GPRbuild framework.
${HOME}/gcc-15.2.0_sparc64-unknown-elf - This directory contains sparc64 C and Ada cross-compilers.
If we attempt to compile a simple Ada program, the compiler will report that the runtime is missing:
$ cat > test_ada.adb << 'EOF'
procedure Test_Ada is
begin
null;
end Test_Ada;
EOF
${HOME}/gcc-15.2.0_sparc64-unknown-elf/bin/sparc64-unknown-elf-gcc -c test_ada.adb
fatal error, run-time library not installed correctly
cannot locate file system.ads
compilation abandoned
We can simulate a minimal Ada runtime and test the compiler:
$ mkdir -p test_ada_runtime/adainclude
$ mkdir -p test_ada_runtime/adalib
$ cat > test_ada_runtime/adainclude/system.ads << 'EOF'
package System is
pragma Pure;
-- Note that we take advantage of the implementation permission to make
-- this unit Pure instead of Preelaborable; see RM 13.7.1(15). In Ada
-- 2005, this is Pure in any case (AI-362).
pragma No_Elaboration_Code_All;
-- Allow the use of that restriction in units that WITH this unit
type Name is (SYSTEM_NAME_GNAT);
System_Name : constant Name := SYSTEM_NAME_GNAT;
-- System-Dependent Named Numbers
Min_Int : constant := -2 ** (Standard'Max_Integer_Size - 1);
Max_Int : constant := 2 ** (Standard'Max_Integer_Size - 1) - 1;
Max_Binary_Modulus : constant := 2 ** Standard'Max_Integer_Size;
Max_Nonbinary_Modulus : constant := 2 ** Integer'Size - 1;
Max_Base_Digits : constant := Long_Long_Float'Digits;
Max_Digits : constant := Long_Long_Float'Digits;
Max_Mantissa : constant := Standard'Max_Integer_Size - 1;
Fine_Delta : constant := 2.0 ** (-Max_Mantissa);
Tick : constant := 0.0;
-- Storage-related Declarations
type Address is private;
pragma Preelaborable_Initialization (Address);
Null_Address : constant Address;
Storage_Unit : constant := 8;
Word_Size : constant := Standard'Word_Size;
Memory_Size : constant := 2 ** Word_Size;
-- Address comparison
function "<" (Left, Right : Address) return Boolean;
function "<=" (Left, Right : Address) return Boolean;
function ">" (Left, Right : Address) return Boolean;
function ">=" (Left, Right : Address) return Boolean;
function "=" (Left, Right : Address) return Boolean;
pragma Import (Intrinsic, "<");
pragma Import (Intrinsic, "<=");
pragma Import (Intrinsic, ">");
pragma Import (Intrinsic, ">=");
pragma Import (Intrinsic, "=");
-- Other System-Dependent Declarations
type Bit_Order is (High_Order_First, Low_Order_First);
Default_Bit_Order : constant Bit_Order :=
Bit_Order'Val (Standard'Default_Bit_Order);
pragma Warnings (Off, Default_Bit_Order); -- kill constant condition warning
-- Priority-related Declarations (RM D.1)
Max_Priority : constant Positive := 30;
Max_Interrupt_Priority : constant Positive := 31;
subtype Any_Priority is Integer range 0 .. 31;
subtype Priority is Any_Priority range 0 .. 30;
subtype Interrupt_Priority is Any_Priority range 31 .. 31;
Default_Priority : constant Priority := 15;
private
type Address is mod Memory_Size;
for Address'Size use Standard'Address_Size;
Null_Address : constant Address := 0;
--------------------------------------
-- System Implementation Parameters --
--------------------------------------
-- These parameters provide information about the target that is used
-- by the compiler. They are in the private part of System, where they
-- can be accessed using the special circuitry in the Targparm unit
-- whose source should be consulted for more detailed descriptions
-- of the individual switch values.
Atomic_Sync_Default : constant Boolean := False;
Backend_Divide_Checks : constant Boolean := False;
Backend_Overflow_Checks : constant Boolean := True;
Command_Line_Args : constant Boolean := False;
Configurable_Run_Time : constant Boolean := True;
Denorm : constant Boolean := True;
Duration_32_Bits : constant Boolean := True;
Exit_Status_Supported : constant Boolean := False;
Fractional_Fixed_Ops : constant Boolean := False;
Frontend_Layout : constant Boolean := False;
Machine_Overflows : constant Boolean := False;
Machine_Rounds : constant Boolean := True;
Preallocated_Stacks : constant Boolean := False;
Signed_Zeros : constant Boolean := True;
Stack_Check_Default : constant Boolean := False;
Stack_Check_Probes : constant Boolean := False;
Stack_Check_Limits : constant Boolean := False;
Support_Aggregates : constant Boolean := True;
Support_Composite_Assign : constant Boolean := True;
Support_Composite_Compare : constant Boolean := True;
Support_Long_Shifts : constant Boolean := True;
Always_Compatible_Rep : constant Boolean := True;
Suppress_Standard_Library : constant Boolean := True;
Use_Ada_Main_Program_Name : constant Boolean := False;
Frontend_Exceptions : constant Boolean := False;
ZCX_By_Default : constant Boolean := True;
end System;
EOF
${HOME}/gcc-15.2.0_sparc64-unknown-elf/bin/sparc64-unknown-elf-gcc \
-c test_ada.adb --RTS=test_ada_runtime
$ file test_ada.o
test_ada.o: ELF 64-bit MSB relocatable, SPARC V9, relaxed memory ordering, version 1 (SYSV), not stripped
