Monday, April 4, 2011

Porting to JS 1.8.5 with GPSEE 0.3: Part I, Native Constructors

Introduction

SpiderMonkey 1.8.5 was released last week; with it came a whole host of JS API changes. These changes will affect any non-trivial native (C, C++) modules written for GPSEE. This post starts the first post of a multi-part series detailing how to easily handle these API changes.

While these API changes are a little painful from the embedder's point of view, they are certainly worth it for anybody who is interested in fast JavaScript. Here is a graph showing performance improvements over time; we started GPSEE shortly before the release of JS 1.7:

Note: JS 1.8.5 support requires GPSEE 0.3, which has not been merged with mainline code yet. The unstable (and currently unusable) branch is available at http://code.google.com/r/wes-js185/.

Porting a Native Constructor

GPSEE 0.3 defines three macros for this task:
  • GPSEE_SLOW_CONSTRUCTOR(MyConstructor): emit a reference (function pointer) to the implementation of the constructor
  • GPSEE_IS_SLOW_CONSTRUCTING(cx): Inside your constructor, this is is equivalent to the JS 1.8.0 call JS_IsConstructing(cx).  It has the value JS_TRUE when your function was invoked as a constructor (i.e. with the new keyword).
  • GPSEE_DECL_SLOW_CONSTRUCTOR(MyConstructor): This macro call replaces the function prototype, and must be followed immediately by your constructor's function body.  This also emits the fast-to-slow shim function.
These macros always emit static functions. If you need to declare a constructor which is not static (not recommended), you can use the GPSEE_SLOW_CONSTRUCTOR(MyConstructor) macro to take its address and make it visible under another name.

You can use these macros with any version of JSAPI; they will do the right thing. (Note: JSAPI backwards compatibility via GPSEE is not well-tested at this point in time. If you are supporting an embedding which can build on JS 1.8.5 and an older version, please let us know!)

Here is what we had to do to "port" the Binary constructor:

-static JSBool Binary(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
+GPSEE_DECL_SLOW_CONSTRUCTOR(Binary)
 {
   /* Binary() called as function. */  
-  if (JS_IsConstructing(cx) != JS_TRUE)
+  if (GPSEE_IS_SLOW_CONSTRUCTING(cx) != JS_TRUE)
     return gpsee_throw(cx, CLASS_ID ".constructor.notFunction: Cannot call constructor as a function!");
@@ later in the same file
   JSObject *proto =
       JS_InitClass(cx,             /* JS context from which to derive runtime information */
            obj,                    /* Object to use for initializing class (constructor arg?) */
            NULL,                   /* parent_proto - Prototype object for the class */
            &binary_class,          /* clasp - Class struct to init. Defs class for use by other API funs */
-           Binary,                 /* constructor function - Scope matches obj */
+           GPSEE_SLOW_CONSTRUCTOR(Binary), /* constructor function - Scope matches obj */



Piece o' cake, eh?