5 Things You Might Not Know about Developing Self-Hosted Code


Self-hosted code is JavaScript code that SpiderMonkey uses to implement some of its intrinsic functions for JavaScript. Because it is written in JavaScript, it gets all the benefits of our JITs, like inlining and inline caches.

Even if you are just getting started with self-hosted code, you probably already know that it isn’t quite the same as your typical, day-to-day JavaScript. You’ve probably already been pointed at the SMDOC, but here are a couple tips to make developing self-hosted code a little easier.

1. When you change self-hosted code, you need to build

When you make changes to SpiderMonkey’s self-hosted JavaScript code, you will not automatically see your changes take effect in Firefox or the JS Shell.

SpiderMonkey’s self-hosted code is split up into multiple files and functions to make it easier for developers to understand, but at runtime, SpiderMonkey loads it all from a single, compressed data stream. This means that all those files are gathered together into a single script file and compressed at build time.

To see your changes take effect, you must remember to build!

2. dbg()

Self-hosted JavaScript code is hidden from the JS Debugger, and it can be challenging to debug JS using a C++ debugger. You might want to try logging messages to console.log() to help you debug your code, but that is not available in self-hosted code!

In debug builds, you can print out messages and objects using dbg(), which takes a single argument to print to stderr.

3. Specification step comments

If you are stuck trying to figure out how to implement a step in the JS specification or a proposal, you can see if SpiderMonkey has implemented a similar step elsewhere and base your implementation off that. We try to diligently comment our implementations with references to the specification, so there’s a good chance you can find what you are looking for.

For example, if you need to use the specification function CreateDataPropertyOrThrow(), you can search for it (SearchFox is a great tool for this) and discover that it is implemented in self-hosted code using DefineDataProperty().

4. getSelfHostedValue()

If you want to explore how a self-hosted function works directly, you can use the JS Shell helper function getSelfHostedValue().

We use this method to write many of our tests. For example, unicode-extension-sequences.js checks the implementation of the self-hosted functions startOfUnicodeExtensions() and endOfUnicodeExtensions().

You can also use getSelfHostedValue() to get C++ intrinsic functions, like how toLength.js tests ToLength().

5. You can define your own self-hosted functions

You can write your own self-hosted functions and make them available in the JS Shell and XPC shell. For example, you could write a self-hosted function to print a formatted error message:

  function report(msg) {
      dbg("|ERROR| " + msg + "|");
  }

Then, while you are setting up globals for your JS runtime, call JS_DefineFunctions(cx, obj, funcs):

  static const JSFunctionSpec funcs[] = {
      JS_SELF_HOSTED_FN("report", "report", 1, 0),
      JS_FS_END,
  };

  if (!JS_DefineFunctions(cx, globalObject, funcs)) {
    return false;
  }

The JS_SELF_HOSTED_FN() macro takes the following parameters:

  1. name - The name you want your function to have in JS.
  2. selfHostedName - The name of the self-hosted function.
  3. nargs - Number of formal JS arguments to the self-hosted function.
  4. flags - This is almost always 0, but could be any combination of JSPROP_*.

Now, when you build the JS Shell or XPC Shell, you can call your function:

js> report("BOOM!");          
Iterator.js#6: |ERROR| BOOM!|