Porting CM3 to a new Platform

Porting CM3 to a new platform (hardware architecture / operating system combination) essentially consists of the following tasks:

  1. Defining the new platform for the compiler frontend (cm3) and providing default configuration files
  2. Generating a code generator (compiler backend) for the new platform, either by using gcc or writing an integrated backend
  3. Supplying all the needed runtime support in the standard libraries m3core (much) and libm3 (only a few things)

Since the compiler is written almost entirely in Modula-3, you then face the task of compiling, assembling, and linking the code including all your fine new additions for the new target platform. You generally have two choices here:

  1. If you already have a running Modula-3 compiler on the target platform (e.g. PM3 or the original DEC SRC release), you just put your code onto the target platform, make it compilable by the other compiler, and boot the CM3 system with three compilation stages. This procedure is described in more detail in Booting CM3 5.1 with PM3 or SRC M3.
  2. Most times you won't be so lucky to find a working Modula-3 compiler on your target platform. Then your only choice is cross-compiling on an already supported platform, which means building a cross-compiler for the new platform, compiling all code to assembler statements on the existing platform, compiling the few C sources on the new platform (we'll assume the existence of a C compiler), and assembling and linking all the generated assembler source on the new platform. This should produce an at least somewhat working cm3 executable, which can then be used to complete the bootstrap process.

In the following sections we'll look at all the things needed to be done in turn. We'll assume that you use the gcc backend for code generation, and that your platform is already supported by gcc. If not, you will have to write the necessary gcc extensions, too; but this would be beyond the scope of this article.

  1. Defining a new platform for the compiler frontend
  2. Supplying the needed runtime support
  3. Building a cross compiler
  4. Producing assembler code on the host platform
  5. Assembling and linking the front end on the target platform
  6. Building a code generator on the target platform
  7. Recompiling everything on the target platform


Defining a new platform for the compiler frontend

The first thing you do is choosing a name for the new platform, like FreeBSD4 or LINUXLIBC6. We'll assume GREATOS in the following description. You then edit the following files:

m3-sys/m3middle/src/Target.{i3,m3}
Add the new target name GREATOS to the Systems array. In the Init procedure, append a section for GREATOS in the case statement. Copy an entry that you deem similar to the new target platform, and carefully check all the values.

m3-sys/m3front/src/builtinInfo/InfoModule.m3
Add GREATOS to Platform_names. (This should no longer be necessary in current versions, as the platform list is now imported from Target.i3.)

m3-sys/cm3/src/config/GREATOS
This is the default configuration file for the new platform. It gets copied to cm3/bin/cm3.cfg.default in the binary installation archives. Simply copy one from a similar system, change the TARGET setting to GREATOS, and adapt the following values and procedures:

m3-sys/cminstall/src/config/GREATOS
This is the configuration template that is used by the binary installation program. You can skip this for now, but should provide one later, containing the same information as in the one above, but with additional location and decision information.


Supplying the needed runtime support

Create the directories listed below. Start again by copying the files for some similar system, then check all the contents for the new platform GREATOS.

m3-libs/m3core/src/C/GREATOS

The interfaces in this directory define simple access to POSIX standard input/output, signal, and setjmp/longjmp. Check all the type definitions and sizes against the corresponding C declarations in your system header files in /usr/include and /usr/include/sys. It is very important that the jmp_buf size is correct and that setjmp/longjmp save all necessary state, including floating point information, as this is used for thread switching.

m3-libs/m3core/src/Csupport/GREATOS

Simply define the correct dtoa interface. Use a symbol from your system libraries or the supplied code from another platform.

m3-libs/m3core/src/runtime/GREATOS

This directory contains the basic system dependent runtime code for your platform. Start with the RTMachine.i3 interface and continue with the signal and thread bits. You will probably disable the VM protection needed by the incremental and generational garbage collector for the time being by setting VMHeap = FALSE. VM protection needs page protection support from the underlying operating system, and a set of wrappers for all system calls. (I'll write up something on this topic later.) You can add this feature later. Comment all things not needed in the accompanying m3makefile.

m3-libs/m3core/src/unix/great-os

This directory contains the Unix / POSIX interfaces needed by the core library. You do not need to write and check everything right now, but quite a lot of this stuff is needed. Start with Utypes.i3, Ustat.i3, Uerror.i3, Uexec.i3, Usignal.i3, Uuio.i3, Udir.i3, Unix.i3. I think this should be enough for a simple working compiler :-)

If you've got so far, you've almost finished your task. There are still some simple adjustments to be made. Edit the following files:

m3-libs/m3core/src/float/m3makefile

Add an entry to the _FloatPieces array for your GREATOS.

m3-libs/m3core/src/runtime/m3makefile

Add an entry for the exception implementation to EXCEPTION_IMPL. You will probably just want to use ex_frame for now.

m3-libs/m3core/src/time/POSIX/m3makefile

Define the date implementation your GREATOS uses. Just choose between the existing ones; one of them should fit your needs.

m3-libs/m3core/src/unix/m3makefile

Add an entry to _UnixPieces for GREATOS that defines the source directories for your system interfaces.

m3-libs/m3core/src/runtime/common/Compiler.tmpl

Add GREATOS to the platform definition.

m3-libs/libm3/src/random/m3makefile

Choose the random implementation for floating point numbers by adding an entry in _RandomPieces.

m3-libs/libm3/src/os/POSIX/OSConfigPosix

Add entries for architecture and operating system name for GREATOS.


Building a cross compiler

Add an entry for GREATOS to Platform_info in m3-sys/m3cc/src/m3makefile. This defines the GNU platform description used for your new target machine. You should now be able to build a cross compiler on your host system with the command

  cm3 -build -DM3CC_HOST=HOST_SYSTEM -DM3CC_TARGET=GREATOS
    

You may also need to add some more magic stuff depending on your new target architecture (look for DS3100 and ALPHA_OSF for examples). The generated cross-compiler will be in HOST_SYSTEM/cm3cg. Copy this executable to the bin directory of your installation (usually /usr/local/cm3/bin) as cm3cg-GREATOS. Do not accidentally overwrite the existing code generator for HOST_SYSTEM!

There's now also a script to automate the building of the cross-compiler.

  % ./build-cross-backend.sh -h

  usage build-cross-backend.sh:

    build-cross-backend.sh [-f] M3_CROSS_TARGET

      builds a cm3cg backend for target platform M3_CROSS_TARGET

    options:

      -f     force distclean before compilation

    

You now should have a working compiler backend for GREATOS, but you still need a compiler frontend that knows about GREATOS. Recompile the following packages in this order:

  1. m3-sys/m3middle
  2. m3-sys/m3linker
  3. m3-sys/m3front
  4. m3-sys/m3quake
  5. m3-sys/cm3
  6. m3-libs/m3core
  7. m3-libs/libm3

You can use the scripts in cm3/scripts to perform the compilations. Backup the existing compiler in /usr/local/cm3/bin/cm3-old, and install the new compiler as /usr/local/cm3/bin/cm3.

Note: You can only compile some platform specific code in m3core and libm3 with a compiler that includes exactly the same list of target platforms in Target.i3, InfoModule.m3 and Compiler.i3; otherwise version stamps will get messed up. So you have to make your port-specific changes, build and ship the compiler packages m3middle, m3linker, m3front, m3quake, cm3, install the compiler, and then build the libraries m3core and libm3 and the compiler packages again (do-cm3-core.sh).


Producing assembler code on the host platform

If you have built and installed the cross compiler frontend and backend, you need to edit /usr/local/cm3/bin/cm3.cfg. Change the definition for m3back to ... cm3cg-GREATOS and set M3_BOOTSTRAP = TRUE at the end of the file. Also change the TARGET to GREATOS at the top.

You should now be able to compile all the packages from the previous step again, this time generating assembler code in the GREATOS subdirectories.

The following script will archive the cross-compiled assembler code:

  % ./pack-crossbuild.sh -h

  usage pack-crossbuild.sh:

    pack-crossbuild.sh cross_target

    will archive the following cross-compiled packages:

     m3core             libm3              m3middle           m3linker
     m3front            m3quake            cm3                m3scanner
     m3tools            m3cgcat            m3cggen            m3bundle
     bitvector          digraph            parseparams        realgeometry
     set                slisp              sortedtableextras  table-list
     tempfiles

    The archives will be saved in a cm3/CROSS_TARGET.
    

Another possibility is to use rsync to copy the generated code, as is done in copy-bootarchives.sh:

  #!/bin/sh
  # $Id: porting.html,v 1.15 2007/06/22 15:48:05 hosking Exp $

  if [ -n "$ROOT" -a -d "$ROOT" ] ; then
    sysinfo="$ROOT/scripts/sysinfo.sh"
  else
    root=`pwd`
    while [ -n "$root" -a ! -f "$root/scripts/sysinfo.sh" ] ; do
      root=`dirname $root`
    done
    sysinfo="$root/scripts/sysinfo.sh"
    if [ ! -f "$sysinfo" ] ; then
      echo "scripts/sysinfo.sh not found" 1>&2
      exit 1
    fi
    export root
  fi

  . "$sysinfo"
  . "$ROOT/scripts/pkginfo.sh"

  RSYNC=${RSYNC:-rsync}
  DEST=${DEST:-lamancha.opendarwin.org:work/cm3}

  if [ -z "$1" ] ; then
    echo "please specify a cross compilation target platform" 1>&2
    exit 1
  fi

  CROSS_TARGET="$1"
  shift
  if [ -n "$1" ] ; then
    PKGS="$@"
  else
    PKGS=""
  fi

  P=""
  P="${P} m3-libs/m3core"
  P="${P} m3-libs/libm3"
  P="${P} m3-sys/m3middle"
  [ "${M3OSTYPE}" = "WIN32" ] && P="${P} m3-sys/m3objfile"
  P="${P} m3-sys/m3linker"
  [ "${GCC_BACKEND}" != yes ] && P="${P} m3-sys/m3back"
  [ "${GCC_BACKEND}" != yes ] && P="${P} m3-sys/m3staloneback"
  P="${P} m3-sys/m3front"
  P="${P} m3-sys/m3quake"
  P="${P} m3-sys/cm3"
  P="${P} m3-tools/m3bundle"

  if [ -n "${PKGS}" ] ; then
    res=""
    for s in ${PKGS}; do
      for p in ${P}; do
        case ${p} in
          *${s}*) res="${res} ${p}";; # echo "res = ${res}";;
        esac
      done
    done
    P="${res}"
  fi

  for p in ${P}; do
    echo ${RSYNC} -avz ${ROOT}/${p}/${CROSS_TARGET}/ ${DEST}/${CROSS_TARGET}/${p}/
  ${CROSS_TARGET}/
    ${RSYNC} -avz ${ROOT}/${p}/${CROSS_TARGET}/ ${DEST}/${CROSS_TARGET}/${p}/${CRO
  SS_TARGET}/
  done
    

Assembling and linking the front end on the target platform

Copy the m3-libs and m3-sys hierarchies (or just the packages named above) including the generated assembler code to the new platform. Use the system assembler, C compiler, archiver, and linker to build all the libraries in the GREATOS sub-directory. As I don't know about these tools before, I cannot give more detailed instructions about the exact commands needed.

The script ppc-cross-build.sh may be used as a template for automating most of the recurring tasks of the cross-compilation bootstrap:

  #!/bin/sh

  # This script is an example of how to automate a cross-compilation for
  # bootstrapping cm3 on a new target platform. Values are set for
  # cross-compilation from FreeBSD4 to PPC_DARWIN running on 
  # lamancha.opendarwin.org.

  TARGET=${TARGET:-PPC_DARWIN}
  WORK=${WORK:-/Users/wagner/work}
  CM3DEST=${CM3DEST:-/Users/wagner/local/cm3}
  CM3DESTBIN=${CM3DEST}/bin
  CM3DESTHOST=${CM3DESTHOST:-lamancha.opendarwin.org}

  ./boot-cm3-core.sh ${TARGET} || exit 1
  ./copy-bootarchives.sh ${TARGET} || exit 1
  ssh ${CM3DESTHOST} \
    cd ${WORK}/cm3/scripts '&&' \
    ./boot-cm3-build-on-target.sh ${TARGET} \; \
    [ -f ${CM3DESTBIN}/cm3 ] '&&' mv ${CM3DESTBIN}/cm3 ${CM3DESTBIN}/cm3.bak \;\
    cp ${WORK}/cm3/${TARGET}/m3-sys/cm3/${TARGET}/cm3 ${CM3DESTBIN}
    

It also uses boot-cm3-build-on-target.sh which performs the necessary build steps on the target machine if parameterized appropriately.

Create an empty cm3 directory structure on your target system (cm3/scripts/create-skel.sh). Install the newly linked cm3 executable and the default configuration file cm3.cfg in the bin directory of this structure. Add this directory to your PATH.


Building a code generator on the target platform

Use

  cm3 -build -DM3CC_HOST=GREATOS -DM3CC_TARGET=GREATOS
    

to build the code generator and install it as /usr/local/cm3/bin/cm3cg.


Recompiling everything on the target platform

You should now have a working compiler and proceed to build itself with it. You can use the scripts cm3/scripts/do-cm3-min.sh and cm3/scripts/boot-cm3-with-m3.sh, or just cm3/scripts/do-pkg.sh with all the package names, i.e.

  ./do-pkg.sh build m3core libm3 m3middle m3linker m3front m3quake cm3
  ./do-pkg.sh buildship m3core libm3 m3middle m3linker m3front m3quake cm3
    

Only do the second step if the build succeeded. You can now start to clean everything up, add all the missing bits (system interfaces, vm protection, etc.), and build all the rest of the CM3 packages.


m3-support{at}elego.de