Programming for Cibyl

From SpelWiki

This page describes guidelines for how to port and write efficient programs for Cibyl.

Contents

Profiling

Profiling a Cibyl application with the J2ME emulator profiler
Enlarge
Profiling a Cibyl application with the J2ME emulator profiler

You will know this from teaching: Before you start chasing performance problems and bugs, run the profiler to see where the cycles are spent. Recent versions of Cibyl generate output which is possible to parse by the J2ME emulator profiler (enable it in ktoolbar).

Java functionality

Calling Java functionality

Java methods are called trough a C API, which is defined on the Cibyl API page. The first parameter is normally the this pointer for non-static methods (and non-constructors).

Exception handling

Java exceptions in Cibyl are handled similarly to what native Java does. Cibyl defines a NOPH_try / NOPH_catch pair, which does almost the same thing as the Java try / catch. The main difference is that the exception is not handled in a block following the catch statement, but through a function pointer. A typical example of this use is the following:

static void exception_handler_fatal(NOPH_Exception_t exception, void *arg)
{
  NOPH_Throwable_printStackTrace(exception);
  NOPH_delete(exception);
  exit(1);
}
...
{
  NOPH_try(exception_handler_fatal, NULL) {
    record_store = NOPH_RecordStore_openRecordStore(buf, FALSE);
    variable = 0; /* Will not be executed if openRecordStore threw an exception */
  } NOPH_catch();
 ...
 }

which will catch the exception, print it out and then return. You pass the function pointer and the argument in the NOPH_try statement. There is a builtin exception handler, NOPH_setter_exception_handler which will set the argument you pass to it (the address of an int) to 1 if the exception occurs. You can throw exceptions by using the NOPH_throw call, which takes an exception object as argument. I.e., something like

{
 ...
  NOPH_throw( NOPH_Exception_new_string("Informative string describing this exception") );
}

This entry on Simon's blog describes the implementation.

Event handling

J2ME ui events are handled through C callback functions. For example, the Canvas::keyPressed() event can be handled by

static void keyPressed(int keyCode)
{
  printf("Key pressed:  %d\n", keyCode);
}
 
int main (int argc, char *argv[])
{
  NOPH_Canvas_registerKeyPressedCallback(keyPressed);
  ...
  while(1) {
      /* Something has to be called for the events to be delivered */
      NOPH_Thread_sleep(100);
    }

This entry on Simon's blog describes the implementation.

Code

Language

Cibyl is constructed to support high-level languages, and it is recommended to avoid writing directly in assembly. While it is possible to write code in MIPS assembly, it is not recommended since some features depend on how GCC compiles code for higher-level languages.

Signedness

Use signed values. Java does not have an unsigned integer datatype, and unsigned values can sometimes be more inefficent because of that.

Data types

The 32-bit signed int is generally the most efficient type in Cibyl, although what matters is really the generated code. Integer types are more efficient than floating point.

For floating point support, the float type will give better performance than the double type. The long double type is not supported.

Function handling

  • Avoid function pointers: these have to go through a jump table whereas normal calls are done directly just calls of normal static Java methods
  • Method calls have a certain overhead, so if inlining can be used it is normally beneficial

Memory handling

Alignment and size

4-byte accesses are the most efficient. You should try to choose your types accordingly and avoid 1- and 2-byte types. In many cases, the main performance culprit will be e.g., drawing, and then the types used have only a minor influence.

Try to keep data aligned on natural borders, i.e., 4-byte words should be aligned on a 4-byte border and so on. This avoids the expensive lwl and lwr instructions. The compiler will most of the time take care of this automatically.

Dynamic allocation

A new malloc implementation has improved performance a lot, so malloc/free can no longer be considered performance problems.

Library support

ANSI C

The ANSI C library supports file reading with fopen, fread and friends, but performance has not been a priority when implementing these. If you want better performance, it's a good idea to use exported Java classes instead. Link with -lc to get this.

libjava

libjava contains implementations of the InputStream and OutputStream Java classes for the C FILE operations. Link with -ljava

libmidp

libmidp contains J2ME-specific implementations of the the Connector class for the C FILE operations. Link with -lmidp

libjsr075

Using the JSR075 package allows you to read and write files on the phone using the Java FileConnection class wrapped around the C FILE operations.

The Java FileConnection API requires a preprocessing step when generating the Java wrappers. To do this, invoke cibyl-generate-java-wrappers with the -D JSR075 option. This can be done automatically by adding

CIBYL_GENERATE_JAVA_WRAPPERS_OPTS="-D JSR075"

to your project Makefile (in the project base directory) and linking with -ljsr075

Other libraries

There are also some other libraries shipped with Cibyl, libcibar and libs9.

libcibar is a library to implement file operations in an in-memory-loaded file. This allows some file operations to complete much faster than otherwise, since the file operations are done by copying to/from memory. The cibyl-generate-cibar tool is used to generate a .cibar file from a directory. The name comes from Cibyl-AR, since the functionality of the tool is somewhat similar to the AR tool.

libs9 is an implementation of a T9-like database. This is used to use T9 input with user-specified dictionaries. It is (will be) used by the Sarien port to Cibyl for text entry while using the in-game dictionary. The name comes from "Simon9" or "Sarien9".

Configuration

The file java/CibylConfig.java contains some configuration variables for Cibyl. This is further described on the Cibyl directory structure page.

You can change the configuration options by editing the CibylConfig.java file (which is normally copied to src/ from the Cibyl base directory). Copy this file to your application directory and then copy it to src/ when compiling. If you use the Makefile system, you can add a rule in your Makefile:

src/CibylConfig.java: CibylConfig.java
        cp $< $@

Useful tools

  • objdump: A disassembler can be useful to check the code properties
  • Dissy: My graphical frontend to objdump which allows easier navigation through disassembled code