Back to index

LispMe coding techniques

This document is about some less familiar coding practices used in LispMe.

Creating launcher icons

It's not just the icons, LispMe creates complete Pilot applications (though rather minimalistic ones) which in turn start LispMe with a special launch code when tapped. These applications consist of the minimal set of resources possible:

Res typeRes iddescription
code000168000 application code
Talt1000an alert displayed when LispMe is not installed
tAIB1000big icon for launcher
tAIB1001small icon for launcher (Palm III and later)
tAIN1000application name for launcher. This name also identifies the LispMe program database.

Other resources (e.g. rloc) are not necessary for such a tiny application.

The launcher code is compiled and linked just like any other C application with the GNU C compiler but no .prc file is built from it, instead it is put into the LispMe .prc but using the id number 9001. So this code fragment is part of LispMe, but will never be called directly by PalmOS. But when LispMe creates a launcher icon, it copies this resource to the newly created resource database, this time using the special id 1, making it the code executed when tapping the icon.

As no C runtime library (crt0.o) is needed and to reduce the code size further, the object is linked with the option -nostartfiles and handles the necessary system calls itself.

One problem is that the LispMe program database and the launcher app should have the same name, but they can't, as every database name must be unique. You can't use a hashing scheme to create an internal name for the launcher app, the mapping must be reversible. So I simply inverted the highest bit of the session name (= LispMe program database name) to create the launcher app's name.

Using NewFloatMgr

GCC doesn't handle NewFloatMgr.h correctly (in version 0.5.0). On the other hand, using arithmetic operators like + in a C program use the emulated operations from the C runtime library, not the Pilot system traps, which creates bloated PRC files.

The following code snippet shows how to access the system traps with GCC.

#define _DONT_USE_FP_TRAPS_ 1
#include <Common.h>
#include <System/SysAll.h>
#include <SysTraps.h>
#include <System/NewFloatMgr.h>

void SysTrapFlpLToF(FlpDouble*, Long) SYS_TRAP(sysTrapFlpEmDispatch);

/* convert a long to double */
double longToDouble(long l)
{
  FlpCompDouble fcd;
  asm("moveq.l %0,%%d2" : : "i" (sysFloatEm_d_itod) : "d2");
  SysTrapFlpLToF(&fcd.fd, l);
  return fcd.d;
}

Long SysTrapFlpFToL(FlpDouble) SYS_TRAP(sysTrapFlpEmDispatch);

/* convert a double to long */
long doubleToLong(double d)
{
  FlpCompDouble fcd;
  fcd.d = d;
  asm("moveq.l %0,%%d2" : : "i" (sysFloatEm_d_dtoi) : "d2");
  return SysTrapFlpFToL(fcd.fd);
}

void SysTrapBinOp(FlpDouble*, FlpDouble, FlpDouble) SYS_TRAP(sysTrapFlpEmDispatch);

/* the same interface is used for all basic arithmetic operations */
double genericDoubleOp(double a, double b, long opcode)
{
  FlpCompDouble fcda, fcdb, fcds;
  fcda.d = a; fcdb.d = b;
  asm("move.l %0,%%d2" : : "g" (opcode) : "d2");
  SysTrapBinOp(&fcds.fd, fcda.fd, fcdb.fd);
  return fcds.d;
}

/* basic arithmetic operations */
#define addDouble(a,b) genericDoubleOp(a,b,sysFloatEm_d_add)
#define subDouble(a,b) genericDoubleOp(a,b,sysFloatEm_d_sub)
#define mulDouble(a,b) genericDoubleOp(a,b,sysFloatEm_d_mul)
#define divDouble(a,b) genericDoubleOp(a,b,sysFloatEm_d_div)

SDWord SysTrapCompare(FlpDouble, FlpDouble) SYS_TRAP(sysTrapFlpEmDispatch);

/* compare 2 doubles for equality */
Boolean eqDouble(double a, double b)
{
  FlpCompDouble fcda, fcdb;
  fcda.d = a; fcdb.d = b;
  asm("moveq.l %0,%%d2" : : "i" (sysFloatEm_d_feq) : "d2");
  return SysTrapCompare(fcda.fd, fcdb.fd);
}

/* compare 2 doubles for less or equal */
Boolean leqDouble(double a, double b)
{
  FlpCompDouble fcda, fcdb;
  fcda.d = a; fcdb.d = b;
  asm("moveq.l %0,%%d2" : : "i" (sysFloatEm_d_fle) : "d2");
  return SysTrapCompare(fcda.fd, fcdb.fd);
}

You should get the idea how to extend this for other operations. By the way, you can use floating point constants in your code, they are correctly converted into their IEEE bit pattern by GCC!

Printing and scanning IEEE floating point

Though you could use SysTrapFlpFToA or SysTrapFlpAToF, I don't recommend so, as the ROM routines Unfortunately, not even the otherwise excellent MathLib supports floating point IO, so I had to write my own for LispMe.

Here's my printing function for doubles:

/**********************************************************************/
/* Formatting parameters                                              */
/**********************************************************************/
#define NUM_DIGITS   15
#define MIN_FLOAT    4
#define ROUND_FACTOR 1.0000000000000005 /* NUM_DIGITS zeros */

/**********************************************************************/
/* FP conversion constants                                            */
/**********************************************************************/
static double pow1[] =
{
  1e256, 1e128, 1e064,
  1e032, 1e016, 1e008,
  1e004, 1e002, 1e001
};

static double pow2[] =
{
  1e-256, 1e-128, 1e-064,
  1e-032, 1e-016, 1e-008,
  1e-004, 1e-002, 1e-001
};

void printDouble(double x, Char* s)
{
  FlpCompDouble fcd;
  short e,e1,i;
  double *pd, *pd1;
  char sign = '\0';
  short dec = 0;

  /*------------------------------------------------------------------*/
  /* Round to desired precision                                       */
  /* (this doesn't always provide a correct last digit!)              */
  /*------------------------------------------------------------------*/
  x = mulDouble(x, ROUND_FACTOR);

  /*------------------------------------------------------------------*/
  /* check for NAN, +INF, -INF, 0                                     */
  /*------------------------------------------------------------------*/
  fcd.d = x;
  if ((fcd.ul[0] & 0x7ff00000) == 0x7ff00000)
    if (fcd.fdb.manH == 0 && fcd.fdb.manL == 0)
      if (fcd.fdb.sign)
        StrCopy(s, "[-inf]");
      else
        StrCopy(s, "[inf]");
    else
      StrCopy(s, "[nan]");
  else if (FlpIsZero(fcd))
    StrCopy(s, "0");
  else
  {
    /*----------------------------------------------------------------*/
    /* Make positive and store sign                                   */
    /*----------------------------------------------------------------*/
    if (FlpGetSign(fcd))
    {
      *s++ = '-';
      FlpSetPositive(fcd);
    }

    if ((unsigned)fcd.fdb.exp < 0x3ff) /* meaning x < 1.0 */
    {
      /*--------------------------------------------------------------*/
      /* Build negative exponent                                      */
      /*--------------------------------------------------------------*/
      for (e=1,e1=256,pd=pow1,pd1=pow2; e1; e1>>=1, ++pd, ++pd1)
        if (!leqDouble(*pd1, fcd.d))
        {
          e += e1;
          fcd.d = mulDouble(fcd.d, *pd);
        }
      fcd.d = mulDouble(fcd.d, 10.0);

      /*--------------------------------------------------------------*/
      /* Only print big exponents                                     */
      /*--------------------------------------------------------------*/
      if (e <= MIN_FLOAT)
      {
        *s++ = '0';
        *s++ = '.';
        dec = -1;
        while (--e)
          *s++ = '0';
      }
      else
        sign = '-';
    }
    else
    {
      /*--------------------------------------------------------------*/
      /* Build positive exponent                                      */
      /*--------------------------------------------------------------*/
      for (e=0,e1=256,pd=pow1,pd1=pow2; e1; e1>>=1, ++pd, ++pd1)
        if (leqDouble(*pd, fcd.d))
        {
          e += e1;
          fcd.d = mulDouble(fcd.d, *pd1);
        }
      if (e < NUM_DIGITS)
        dec = e;
      else
        sign = '+';
    }

    /*----------------------------------------------------------------*/
    /* Extract decimal digits of mantissa                             */
    /*----------------------------------------------------------------*/
    for (i=0;i<NUM_DIGITS;++i,--dec)
    {
      Long d = doubleToLong(fcd.d);
      *s++ = d + '0';
      if (!dec)
        *s++ = '.';
      fcd.d = subDouble(fcd.d, longToDouble(d));
      fcd.d = mulDouble(fcd.d, 10.0);
    }

    /*----------------------------------------------------------------*/
    /* Remove trailing zeros and decimal point                        */
    /*----------------------------------------------------------------*/
    while (s[-1] == '0')
      *--s = '\0';
    if (s[-1] == '.')
      *--s = '\0';

    /*----------------------------------------------------------------*/
    /* Append exponent                                                */
    /*----------------------------------------------------------------*/
    if (sign)
    {
      *s++ = 'e';
      *s++ = sign;
      StrIToA(s, e);
    }
    else
      *s = '\0';
  }
}

The scanning function is too deeply interwoven with the rest of LispMe's scanner, I'll extract it when I've got more time!

Using more than 32k code segment

The new prc-tools 2.0 support multi-segmented applications where each segment can be upto 32k and intersegment jumps are only a little slower than intrasegment jumps. So the technique described below is no more necessary and starting with V2.7 LispMe is now a multi-segment program itself. However, if you don't want to migrate from 0.5 to 2.0 now, the following might still be useful for you. Please note, that in fact this trick doesn't work anymore with 2.0!

It has often been said, that this is not possible, but this is wrong. In fact, there're two ways to overcome the 32k limit. The first one is using shared libraries, which has some problems (e.g. accessing global data) and the second one is described here.

Your PilotMain() is not the first function called when your app starts. The (normal) entry point is a function start(), which is buried in the object module crt0.o. (This module is automatically put at the beginning of the code section by the linker) start() relocates the data segment and calls some "hooks" (I don't know, what these hooks exactly do, but it seems that you need them).

The PalmOS starts an app by simply jumping to adress 0, where the start() function is put by the linker.

Now here comes the problem: start() is at the very beginning of the code segment, then your code follows and the hooks are at the very end, so to call the hooks from start(), they must be within 32k (the range of the relative branch instructions of the 68000), so normally your code mustbe <32k.

/start()
<32kyour code
\hooks
Now the trick is to put start() in the middle of your objects and provide an entry function myStart() at adress 0, which simply calls start().

When start() is within 32k of myStart() and the hooks are within 32k of start(), you have TWO 32k ranges to put your code. Of course you must ensure (by shuffling your functions and objects) that calls between your functions don't exceed the 32k branch limit.

/myStart()
<32kyour code
\
/
start()
<32kmore of your code
\hooks
That's the theory, to make this work you have to do the following:
  1. Increase the coderes size in the file pilot.ld. Here's mine:
    MEMORY 
            {
            coderes   : ORIGIN = 0x10000, LENGTH = 65535
            datares   : ORIGIN = 0x0, LENGTH = 32767
            }
    
    SECTIONS
    {
            .text :
            {
              *(.text)
              . = ALIGN(4);
              bhook_start = .;
              *(bhook)
              bhook_end = .;
              . = ALIGN(4);
              ehook_start = .;
              *(ehook)
              ehook_end = .;
            } > coderes
            .data :
            {
              data_start = .;
              *(.data)
            } > datares
            .bss :
            {
            bss_start = .;
              *(.bss)
              *(COMMON)
            } > datares
            end = ALIGN( 4 );
            edata = ALIGN( 4 );
    }
    
  2. Compile the following file (start.c):
    extern unsigned long start();
    unsigned long myStart()
    {
      return start();
    }
    
  3. Link with a command like this:
    $(CC) $(LDFLAGS) -nostartfiles start.o some objects $(CRT0) more objects $(LIBS) -oyourprog

    The option -nostartfiles tells the linker not to automatically use crt0.o as the first object.