Over the last couple of weeks we have been working on a new JavaScript SDK, Rocky.js.

The end goal of Rocky.js is to allow you to run JavaScript code directly on your watch. But in order to iterate quickly and collect feedback from the community, we first decided to offer a version of Rocky.js that runs in the browser. To that end, we transpiled bits of the very firmware that runs on Pebble watches worldwide into JavaScript and replicated our watch SDK’s behavior with high fidelity.

Keep reading for more details on how we ended up setting it up and what we learned along the way.

My other firmware is a web app

“I found this neat piece of software that lets me write web apps in C”

The obvious path to running our SDK in the browser would be to reimplement our APIs in JavaScript. However, our goal was to replicate our API’s behavior one-to-one down to the smaller quirks and endearing rendering bugs. Such a high level of fidelity has the additional benefit that we can build tools on top of it that both javascript and C developers can use to prototype their software in the browser, build better documentation, and publish web demos of their applications.

Plus, we’d already built the whole thing once in C, wouldn’t it be nice to just re-use all that work? That’s where Emscripten comes in.

Emscripten is an LLVM-based C-to-asm.js transpiler with a few years of development under its belt and a proven track record. People have gotten all kinds of complicated software running in their browsers using Emscripten, from the Unreal Engine to Windows 95.

To make building C/C++ programs easy, the Emscripten folks have built emcc and emar, as drop-in replacements for your favorite C compiler and archiving tool.

If you use a standard build system such as cmake or make, you can simply invoke Emscripten’s equivalent emmake or emcmake and every tool will be substituted properly. We use waf, which means we had to set all this up ourselves. We’ve made part of the build script available as a Gist.

Mistakes we made so you don’t have to:

We learned the hard way that other archivers, such as llvm-ar seemingly work fine, unless you have duplicate filenames in your code base, in which case one of them will just get dropped with a simple warning.

Going beyond the blank screen

One of the more obvious consequences of running in the browser is that instead of copying data from our framebuffer to a display peripheral, it needs to go onto an HTML Canvas.

Typically, Emscripten programs rely on the SDL port bundled alongside it to render to the screen. We could have written a small amount of SDL code to copy our framebuffer to an SDL framebuffer, but it seemed like a complicated solution to a simple problem.

Instead, we added an API on the C side which returned a pointer to the framebuffer data, which we called early on in our JavaScript code and whenever application code asked for the display to be painted, we copied the data from our framebuffer to our canvas’s ImageData object.

The data does not map exactly. The display on the Pebble watch provides 6-bit color, and thus we use a byte to describe each pixel. The data layout is as follows:

8       6       4       2       0
┌ ─ ┬ ─ ┬───┬───┬───┬───┬───┬───┐
  alpha │  red  │ green │ blue  │
└ ─ ┴ ─ ┴───┴───┴───┴───┴───┴───┘

Canvas, on the other hand, encodes each pixel value with 32 bits:

32              24              16               8               0
 ┌ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
       alpha     │      red      │     green     │     blue      │
 └ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘

We do a little bit of bit twiddling in JavaScript to transform one to the other:

var pebblePixel = pebblePixels[pebbleOffset];
var r = ((pebblePixel >> 4) & 0x3) * 85;
var g = ((pebblePixel >> 2) & 0x3) * 85;
var b = ((pebblePixel >> 0) & 0x3) * 85;
canvasPixels[canvasOffset + 0] = r;
canvasPixels[canvasOffset + 1] = g;
canvasPixels[canvasOffset + 2] = b;
canvasPixels[canvasOffset + 3] = 255;

The resulting image data is fed back to the canvas using the putImageData API and voila, we have Pebble graphics drawing to the canvas.

Circles, lines, colors, oh my!

The portion of the firmware that we compiled did not provide a main function, as it did not make sense to boot the full OS, so we decided to build a simple app for Pebble that we compiled alongside the firmware to provide us with an entry point and verify things were working as expected.

int main(void) {
  GContext *ctx = emx_graphics_get_gcontext();
  graphics_context_set_stroke_color(ctx, GColorBrightGreen);
  graphics_context_set_stroke_width(ctx, 2);

  graphics_draw_line(ctx, (GPoint){0, 0}, (GPoint){100, 100});
  graphics_draw_line(ctx, (GPoint){0, 10}, (GPoint){100, 10});
  graphics_draw_line(ctx, (GPoint){0, 20}, (GPoint){100, 20});
  graphics_draw_line(ctx, (GPoint){0, 30}, (GPoint){100, 30});
  graphics_draw_circle(ctx, (GPoint){50, 50}, 20);

  app_event_loop();

  return 0;
}

You got us, this is almost a valid app, but it does call a little bit of glue code that provides us with a GContext.

And, just like that, we had a pixel-perfect app running in our browser!

Engineers designing things The aforementioned app running in Google Chrome

console.log(‘segfault’)

Pretty quickly, we realized something was amiss. While our simple app worked fine, some more complicated APIs behaved in unpredictable ways, often doing nothing at all.

After a bit of printf debugging, we noticed we hit error paths whenever we tried to read from or write to a packed struct - that is, a struct where no padding is added and thus the fields are not word aligned.

The microcontroller in the Pebble watch supports unaligned accesses natively, which means that they require the same number of instructions than aligned accesses. To save on storage space and transmission bandwidth, our serialized data structures dispense with padding bytes and are in many cases unaligned.

A quick journey through the Emscripten documentation led us to realize that Emscripten assumes loads and stores are aligned, and offers few facilities to mark structs as unaligned, short of changing the types of every field in those structs.

We resolved to post-process the Emscripten output to add support for unaligned accesses. This was made simple by two things:

  • When invoked with the SAFE_HEAP=1 option, emcc will funnel all memory accesses to SAFE_HEAP_STORE and SAFE_HEAP_LOAD functions, which gave us an easy place to inject our code.
  • emcc provides the --js-transform flag which can be used to post-process the output before it is optimized. The argument will call a provided script and pass it the path of the compiled JavaScript.

We’ve put our js-transform script in a Gist so you can get a better idea of how it works.

Our implementation is simple (one might call it naïve). For example, the unaligned store is implemented as follows:

// here bytes is the number of bytes to store,
// and dest is the address to store them at
if (dest % bytes !== 0) {
  // unaligned access
  for (var i = 0; i < bytes; i++) {
    HEAPU8[dest + i >> 0] = (dest >> (8 * i)) & 0xff;
  }
}

The people want to write JavaScript

Writing a C application and running it in the browser is fun, but what we really want is to let developers write it all in Javascript. We’re building a JavaScript SDK for our watch after all. As a first step, we decided to roughly replicate parts of our C SDK in JavaScript, so that you may start prototyping your applications for Pebble in your browser.

We replicated APIs such as graphics_draw_rect(GContext *ctx, GRect rect) (docs) in JavaScript, with some small syntactical adjustements.

In the above case, we get graphics_draw_rect(ctx, rect), and to play nice with dynamic types we accept a range of inputs for rect:

  • Objects: rect = { x: 1 y: 2 w: 3 h: 4}
  • Arrays: rect = [1, 2, 3, 4]

Unlike most other Emscripten projects, this required a good bit of JavaScript code calling into C code, and vice versa. This presented some interesting challenges.

In the simple case, we didn’t have to do much more than call Emscripten’s cwrap with the right arguments. For example, the graphic_context_set_stroke_width API maps neatly only Emscripten’s types and was implemented in one line:

// C: void graphics_context_set_stroke_width(GContext *ctx, uint8_t stroke_width)
Rocky.graphics_context_set_stroke_width =
      Module.cwrap('graphics_context_set_stroke_width', 'void',
                      ['number', 'number']);

Mistakes we made so you don’t have to:

Emscripten removes dead code, which include any function that implement an API not called elsewhere in the C app. Thankfully, Emscripten will print a helpful error message telling us we might have forgotten to export functions whenever we call an undefined symbol. This is done with emcc’s EXPORTED_FUNCTIONS option which accepts the list of API to export. We opted to put them into a json file passed to EXPORTED_FUNCTIONS using the @ operator. All the functions that are called by other C code are fine.

Passing structs

In other cases, we weren’t so lucky. Emscripten’s cwrap only supports three types:

  • "number", which can be used to represent pointers, integers and booleans
  • "string", which allocates a string on the stack and passes a pointer to it. Don’t do what we did and try to keep that reference around without copying the data: it will get deallocated as soon as the function returns.
  • "array", which works much like a "string", but for byte arrays (note: other array types are supported).

Anything more complicated, and you’ve got to marshal the data back and forth between C-land and JS-land yourself.

In the case of function arguments, the simple solution is to explode our C structures into their constituent parts and pass those individually. An intermediate API needs to exist between the C and the JavaScript that accepts those constituent parts as arguments. We generally prefixed those APIs with emx_ (short for EMscripten eXport).

Since we want the JS API to accept JS objects or arrays, we first need to pull apart the objects into individual fields on the JS side:

Rocky.graphics_draw_rect = function(ctx, rect) {
  rect = Rocky.GRect(rect); // convert rect into its canonical form
  return emx_graphics_draw_rect(ctx, rect.x, rect.y, rect.w, rect.h);
}

On the C side, we pack those arguments back into structs:

void emx_graphics_draw_rect(GContext *ctx,
                            int16_t rect_origin_x, int16_t rect_origin_y,
                            int16_t rect_size_w, int16_t rect_size_h) {
  graphics_draw_rect(ctx,
                     &((GRect){ {rect_origin_x, rect_origin_y},
                                {rect_size_w, rect_size_h} })
                     );
}

This works well enough for our basic types such as GRect and GPoint, and has the nice property that it can easily be generated automatically. Unfortunately, it gets cumbersome quickly.

Returning structs

Returning complicated data structures is made more difficult by the fact that C functions can only ever return one thing at a time.

In the case of small structs that we would have normally passed by value, we assigned them to a static variable and returned a pointer to it.

GRect *emx_grect_crop(int16_t r_x, int16_t r_y, int16_t r_w, int16_t r_h, int32_t crop_size_px) {
  static GRect result = {0};
  result = grect_crop(GRect(r_x, r_y, r_w, r_h), crop_size_px);
  return &result;
}

This only works because those functions do not need to be reentrant:

  1. They are not directly or indirectly recursive.
  2. JavaScript offers single thread of execution.

Had reentrency been required, we would have implemented a stack.

Memory management

Bridging the memory management models of C and JavaScript gave us quite a bit of trouble. We wanted to associate a JavaScript object representing, for example, a bitmap with dynamically allocated data that represented that object on the C side. Our existing APIs in this case do not make much sense in a JavaScript context.

Developers on our platform are used to allocating and freeing their data structures explicitly. To that end, most of our types come with a _create and a _destroy API. It is common to see the following:

static GBitmap *b = NULL;

void init() {
  b = gbitmap_create(...);
}

void paint(GContext *ctx) {
  graphics_draw_bitmap_in_rect(ctx, b, (GRect){0, 0, 20, 20});
}

void deinit() {
  gbitmap_destroy(b);
}

JavaScript on the other hand features garbage collection, and JavaScript developers expect not to have to manage their memory manually.

Fundamentally, the life-cycles of JavaScript objects and C data are mismatched. The diagram below makes this clear:

JS Code                                               JS Object             C Heap


                                                allocate ┌──┐
backgroundImage = gbitmap_create(imageData);    ─────────▶  │
                                                         │  │                ┌──┐
                                                         │  ■──────allocate──▶  │
                                                         │  │                │  │
                                                         │  │                │  │
                                                  use    │  │                │  │
graphics_draw_gbitmap(backgroundImage);         ─────────▶  │                │  │
                                                         │  ■────────use─────▶  │
                                                         │  │                │  │
                                                         │  │                │  │
backgroundImage = <another value>;                       │  │                │  │
                                                         │  │                │  │
─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│  ├ ─ ─ ─ ─ ─ ─ ─ ─│  ├ ─
                                                         │  │                │██│
                                               deallocate│  │                │██│
* Garbage collector runs *                      ─────────▶  │         LEAK!! │██│
                                                         └──┘                │██│
                                                                             │██│
                                                                             └──┘

Figuring out when to free the memory is the difficult part. Since JavaScript does not have destructors, or any way to register a hook to be called when an object is being garbage collected, references to the malloc-ed data would have been garbage collected from under us and memory would have leaked.

Instead of trying to sync the lifecycles, we make the C data short-lived. This requires that the JavaScript objects contain all data needed to recreate the C structs and allocate and deallocate them on demand every time an API is called on the object. Because a picture is worth a thousand words, here’s our new lifecycle diagram:

JS Code                                               JS Object             C Heap


                                                allocate ┌──┐
backgroundImage = gbitmap_create(imageData);    ─────────▶  │
                                                         │  │
                                                         │  │
                                                         │  │
                                                         │  │
                                                  use    │  │
graphics_draw_gbitmap(backgroundImage);         ─────────▶  │                ┌──┐
                                                         │  ■──────allocate──▶  │
                                                         │  ■────────use─────▶  │
                                                         │  ■─────deallocate─▶  │
backgroundImage = <another value>;                       │  │                └──┘
                                                         │  │
─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─│  ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
                                                         │  │
* Garbage collector runs *                     deallocate│  │
                                                ─────────▶  │
                                                         └──┘

Implementing our GPath API for example, the JavaScript object has the following properties:

path = {
  rotation: 0,
  offset: {x: 0, y: 0},
  points: [{x1, y1}, {x2, y2}, ...]
}

The JavaScript function that creates that object also adds two methods to it: - captureCPointer, responsible for allocating and initializing a C struct based on the JavaScript object’s data - releaseCPointer, responsible for deallocating that struct

The JS implementation of gpath_draw_filled wraps the call to the underlying C API with these capture and release calls:

Rocky.gpath_draw_filled = function(ctx, gpath) {
  var cPtr = path.captureCPointer();
  try {
    Module.ccall('gpath_draw_filled', 'void', ['number', 'number'], [ctx, cPtr]);
  } finally {
    path.releaseCPointer(cPtr);
  }
};

This is inefficient, as it requires copying the same data over to the C heap every time we want to use the object, but it is a simple way to guarantee memory is not leaked. We count on the browser to match the performance of our 100MHz watch despite the performance hit. It also precludes the C code from holding on to the data. Our Layer APIs rely on the bitmaps and text data passed to them to be around whenever they are painted and are not compatible with this technique.

Until next time!

With all of that, we’ve got an SDK together that lets people build simple apps in their browser. As of version 0.3.0, we’ve used all the techniques described in this post to implement most Pebble APIs that do not overlap with web standards. You can find the list on the Rocky.js documentation.

People have already built all kinds of cool projects with Rocky.js. Check out this JavaScript version of Isotime, a popular watchface for Pebble.

Isotime by Chris Lewis on jsbin.com

Caution: C developer writing JavaScript code

For the sake of keeping this post reasonably-sized, we haven’t yet touched upon Resources among other things. Those will be topics for future blog post! Tune in next month for more!

In the meantime, check out Rocky.js on GitHub, sign up for our mailing list and get in touch with Heiko and me on Twitter.