I begun to port Nim to a custom operating system (here mentioned as "myOS"). 
The initial goal was to be able to compile a small hello world program and run 
it on the custom OS. In order make this happen I had to do the following steps.

In the file **compiler/platform.nim** I had to add an entry for the OS.
    
    
    type
      TSystemOS* = enum # Also add OS in initialization section and alias
                        # conditionals to condsyms (end of module).
        osNone, osDos, osWindows, osOs2, osLinux, osMorphos, osSkyos, osSolaris,
        osIrix, osNetbsd, osFreebsd, osOpenbsd, osDragonfly, osCrossos, osAix, 
osPalmos, osQnx,
        osAmiga, osAtari, osNetware, osMacos, osMacosx, osIos, osHaiku, 
osAndroid, osVxWorks
        osGenode, osJS, osNimVM, osStandalone, osNintendoSwitch, osFreeRTOS, 
osZephyr, **osMyOS**, osAny
    
    ...
    
    const
      OS*: array[succ(low(TSystemOS))..high(TSystemOS), TInfoOS] = [
         ...
         
         (name: "Zephyr", parDir: "..", dllFrmt: "lib$1.so", altDirSep: "/",
          objExt: ".o", newLine: "\x0A", pathSep: ":", dirSep: "/",
          scriptExt: ".sh", curDir: ".", exeExt: "", extSep: ".",
          props: {ospPosix}),
    #added entry
         (name: "myOS", parDir: "..", dllFrmt: "lib$1.so", altDirSep: "/",
          objExt: ".o", newLine: "\x0A", pathSep: ":", dirSep: "/",
          scriptExt: ".sh", curDir: ".", exeExt: ".exe", extSep: ".",
          props: {}),
    #-----------
         (name: "Any", parDir: "..", dllFrmt: "lib$1.so", altDirSep: "/",
          objExt: ".o", newLine: "\x0A", pathSep: ":", dirSep: "/",
          scriptExt: ".sh", curDir: ".", exeExt: "", extSep: ".",
          props: {}),
         ]
    
    
    
    Run

This solution is really nice, that many of the OS dependent settings are in one 
place. Imagine if this would be scattered around in several files, you wouldn't 
be able to find it. What would perhaps be even more convenient is to have a 
configuration file that the compiler parses, but this is good enough. One 
question I have is if this information is for the host compiler only or is this 
also used for the actual binary target?

In **lib/system/platforms.nim** you also need to add an entry for the OS.
    
    
    OsPlatform* {.pure.} = enum ## the OS this program will run on.
        none, dos, windows, os2, linux, morphos, skyos, solaris,
        irix, netbsd, freebsd, openbsd, aix, palmos, qnx, amiga,
        atari, netware, macos, macosx, haiku, android, js, standalone, 
nintendoswitch, **myos**
    
    ...
    
    const
      targetOS* = ...
                  elif defined(nintendoswitch): OsPlatform.nintendoswitch
                  elif defined(myos): OsPlatform.myos
                  else: OsPlatform.none
        ## the OS this program will run on.
    
    
    
    Run

A small change in **lib/system/dyncalls.nim** was needed. We can wait with the 
dynamic library support for now.
    
    
    elif defined(nintendoswitch) or defined(freertos) or defined(zephyr) or 
defined(myos):
      proc nimUnloadLibrary(lib: LibHandle) =
        cstderr.rawWrite("nimUnLoadLibrary not implemented")
        cstderr.rawWrite("\n")
        rawQuit(1)
    
    
    Run

To support memory allocation changes in **lib/system/osalloc.nim** was needed
    
    
    proc osDeallocPages(p: pointer, size: int) {.inline.} =
        if bumpPointer-size == cast[int](p):
          dec bumpPointer, size
    
    elif defined(myos) and not defined(StandaloneHeapSize):
        include myos/alloc # osAllocPages, osTryAllocPages, osDeallocPages
    
    else:
      {.error: "Port memory manager to your platform".}
    
    
    Run

First the people who develop Genode really showed how it should be done. 
Instead of inserting everything in the same file, just include another 
implementation file which is much cleaner. I decided to copy this way of doing 
it. The implementation of osAllocPages, osTryAllocPages and osDeallocPages was 
very easy by calling the custom OS interface and it doesn't need to be more 
complicated than that. This is a very low bar to reach to enable the memory 
management.

The file **config/nim.cfg** was changed in order to give custom parameters for 
the C compiler.
    
    
    ...
    
    arm.myos.clang.exe = "clang"
    
    ...
    
    @if myos:
      clang.options.always = "--target=arm-none-eabi  ..............."
      clang.cpp.options.always = "--target=arm-none-eabi  ............"
    @end
    
    
    
    Run

I didn't specify any linker because I use cmake to link the binary files. This 
was because I have already an infrastructure to link to different types of 
targets as well it opens up for mixed Nim,C,C++ projects. Nim is a bit weird 
that it outputs several C and object files with for humans unpredictable file 
names. Also build systems don't like this because they want to work with files 
that they know about before hand. In order overcome this I set Nim to compile a 
static library with a specific name. Now cmake has a file name it can trigger 
on.

This was basically all that was needed to produce a basic binary. Good thing 
that nim uses standard file write with stdout when you call echo so that worked 
right out of the box since I have a C library that partially works. Compared to 
other languages I have worked with this porting was very quick and in this case 
Nim didn't drag in anything from standard library so that I had to port 
everything in one go. Often other languages use a runtime where you have to 
port (or stub) _everything_ in order to get it to compile.

Now this is where it starts to become difficult, even with Nim. I decided to 
try out passing command line parameters. By doing so you need to "import os". 
This imports a complete ball of files. Many of these files are like 
oscommon.nim, ossymlink.nim which contains code for file system functionality. 
Do you need the file system in order to read command line parameters?

Here it would be more beneficial if the standard library had a more pay as you 
go approach. You don't include things you don't need which should be possible 
with Nim as the standard library is a bunch of source files. Operating systems 
have a wide variety what they support beyond basic memory management. They 
might have a file system but do not implement any access rights management. 
They implement basic file system functionality but don't have things like 
moving, copying files implemented because they don't care about that. Network 
support is not always there etc. To have a more pay as you go approach makes it 
easier to port as you can do it gradually.

Reply via email to