Home
Calerga
Contact
Support
Google Group
 
Products
Sysquake
SQ Remote
SQ for LaTeX
LyME
LME for PPC
 
Documentation
Download
Applications
Press Releases
Events
News
 

Technical Note 4 - Compiling functions for LyME with CodeWarrior

This technical note describes how CodeWarior (a commercial compiler by Metrowerks) can be used to compile C functions to machine code, and how to run the result in LyME.

Warning: calling machine code from LyME can easily crash the Palm OS device and require a soft or hard reset. Be careful, double-check what you are doing, and backup your handheld! Using the Palm Emulator (available at http://www.palmos.com) is recommended.

Single function

1. Create a new project

In Metrowerks CodeWarrior for Palm OS, choose a Palm OS 3.5 or 4 stationery.

2. Write a function

Replace the source code of the main .c file with the following:

#include <SystemMgr.h>

static short voltage;

void currentBatteryVoltageCentivolt()
{
  voltage = SysBatteryInfo(false, NULL, NULL, NULL, NULL, NULL, NULL);
}

The function gets the current battery voltage in centivolts, using Palm OS function SysBatteryInfo. This function is actually a system call which is implemented with a trap opcode; this is why it is important to include the header SystemMgr.h where it is declared as a macro. The result is stored in a static (global) variable, which is indexed on register A5. This is exactly what we need, because data exchange between the function and LME is done via a block of memory pointed by A5.

3. Compile the source code

Choose Compile in the Project menu, and fix any error reported by CodeWarrior.

4. Get the machine code

Choose Disassemble in the Project menu. A new window should contain the following text:

Names:
    1: voltage
    2: currentBatteryVoltageCentivolt

Hunk:	Kind=HUNK_LOCAL_UDATA  Name="voltage"(1)  Size=2

Hunk:	Kind=HUNK_GLOBAL_CODE  Name="currentBatteryVoltageCentivolt"(2)  Size=64

void currentBatteryVoltageCentivolt()
{
00000000: 4E56 0000          link      a6,#0
  voltage = SysBatteryInfo(false, NULL, NULL, NULL, NULL, NULL, NULL);
00000004: 42A7               clr.l     -(a7)
00000006: 42A7               clr.l     -(a7)
00000008: 42A7               clr.l     -(a7)
0000000A: 42A7               clr.l     -(a7)
0000000C: 42A7               clr.l     -(a7)
0000000E: 42A7               clr.l     -(a7)
00000010: 4227               clr.b     -(a7)
00000012: 4E4F               trap      #15
00000014: A324               dc.w      0xa324         ; Invalid opcode
00000016: 3B40 0000          move.w    d0,voltage

0000001A: 4E5E               unlk      a6
0000001C: 4E75               rts
0000001E: 9E63 7572 7265     dc.b      0x9e,'currentBatteryVoltageCentivolt',0x00
          6E74 4261 7474 
          6572 7956 6F6C 
          7461 6765 4365 
          6E74 6976 6F6C 
          7400           
0000003E: 0000           
}

XRef:	Kind=HUNK_XREF_DATA16BIT  Name="voltage"(1)  #Pairs=1
		Offset=$00000018 Value=$00000000

Note that depending on the compiler options and possibly the compiler version, the output may differ.

The most important part is the second column which contains the compiled code in hexadecimal, beginning with 4E56 0000. Each block of 4 characters is a 16-bit word. The right column shows the opcodes. Depending on the disassembler options, the original source code is also displayed, like here.

The function ends with rts. What follows is information for the debugger. It does not interfer with the code, but it can be removed.

5. Extract machine code

Copy the compiled code which corresponds to the function:

4E56000042A742A742A742A742A742A742274E4FA3243B4000004E5E4E75

Warning: the output produced by the disassembler is not linked; relative jumps inside the function and access to local variables (arguments or automatic, placed in registers or on the stack by the compiler) are resolved correctly; but function calls and access to more than a single global variable would fail and most probably crash the Palm OS device. We will see later how to get the output of the linker.

6. Add LME code

Machine code must be placed in a binarydata object:

code = binarydata(['4E56000042A742A742A742A742A742A7',...
                   '42274E4FA3243B4000004E5E4E75']);

We have split the string on multiple lines for page layout reasons, but there is no obligation to do so.

Enough place must be allocated for the global data pointed by register A5. Here, we need one 16-bit word:

data = binarydata(0);

Function feval is used to execute the machine-code function. It returns the data block which was modified by the function.

data = feval(code, data);

The data block can be converted to floating-point numbers with function double. A better way, especially when it contains more than one piece of data, is accessing the 16-bit words it contains with indexing, with the same syntax as with matrices. Note that the index represents the offset with respect to the beginning of the data; the first word is at index 0.

voltage = data(0) * 0.01

All these statements can be placed inside an LME function:

function voltage = currentBatteryVoltage
  code = binarydata(['4E56000042A742A742A742A742A742A7',...
                     '42274E4FA3243B4000004E5E4E75']);
  data = binarydata(0);
  data = feval(code, data);
  voltage = data(0) * 0.01

Multiple functions

When multiple functions are compiled, or when more than a single global variable (static or extern) is declared, the linker must be used to resolve the addresses. The code cannot be grabbed from the output of the disassembler. There are several ways to do it; we will describe only one of them.

1. Set the project type to resource

Choose Settings in the Project menu, click 68K Target in the left column, and select Palm OS Code Resource in the pop-up menu. In the Entry Point field, type the name of the function which is called from LME.

2. Build the project

In the Project menu, select Make.

3. Get the linked code from the resource file

Using a resource editor (like ResEdit on Macintosh computers) or an hexadecimal file editor, open the resource file created by CodeWarrior and copy the code. For the example above, it should be

487A000406970000
00064E754E560000
42A742A742A742A7
42A742A742274E4F
A324394000004E5E
4E759E6375727265
6E74426174746572
79566F6C74616765
43656E7469766F6C
740000000000

One can see that the code is much larger. It begins with

00000000: 487A 0004          pea       *+6
00000004: 0697 0000 0006     addi.l    #6,(a7)
0000000A: 4E75               rts

which jumps to our function. Using optimization settings and removing MacsBug symbol (the function names which follow their code) and stack frames (instructions which set register A6 to point to the beginning of local variables) reduces the code size without any harm (trailing 0000 can also be removed):

487A000406970000
00064E7542A742A7
42A742A742A742A7
42274E4FA3243940
00004FEF001A4E75

4. Add glue code

The execution model used by Palm OS is different with code resource and application code. In applications, register A5 points to the beginning of global data, while in code resource, register A4 does (this can be checked by disassembling the source code). Since feval puts the data address into A5, we must add code to copy A5 to A4. We don't have to worry about inserting code before the beginning of the code, because all code addresses are relative. We don't have either to preserve the contents of A4.

The opcode of the instruction move.l a5, a4 is 284D. The code becomes

284D
487A000406970000
00064E7542A742A7
42A742A742A742A7
42274E4FA3243940
00004FEF001A4E75

5. Execute the code in LyME

Continue like you did without the linker. The LME function is

function voltage = currentBatteryVoltage
  code = binarydata(['284D487A00040697000000064E7542A7', ...
                     '42A742A742A742A742A742274E4FA324', ...
                     '394000004FEF001A4E75']);
  data = binarydata(0);
  data = feval(code, data);
  voltage = data(0) * 0.01
Copyright 1998-2007, Calerga.
All rights reserved.