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
|