2015年11月25日

RegExp.$1 と String.prototype.replace の悲惨な関係 (2)

Firefox は String.prototype.replace で関数を実行後に RegExp.$1 などを復元する、と前回の記事に書きましたが、この挙動は bug 1226936 で削除されました。

Firefox 45 からは、Chrome や Safari と同じく関数実行前に値を格納するだけ、となります。

                                    //  Firefox 45 | Chrome | Safari | Edge | IE
/(a)/.exec("a");                    // -----------+--------+--------+------+----
console.log(RegExp.$1);             //      a     |    a   |    a   |   a  |  a
"b".replace(/(b)/, function() {     //            |        |        |      |
  try {                             //            |        |        |      |
    console.log(RegExp.$1);         //      b     |    b   |    b   |   a  |  a
    "c".replace(/(c)/, function() { //            |        |        |      |
      console.log(RegExp.$1);       //      c     |    c   |    c   |   a  |  a
      /(d)/.exec("d");              //            |        |        |      |
      console.log(RegExp.$1);       //      d     |    d   |    d   |   d  |  d
      throw 1;                      //            |        |        |      |
    });                             //            |        |        |      |
  } catch (e) {}                    //            |        |        |      |
  console.log(RegExp.$1);           //      d     |    d   |    d   |   d  |  d
});                                 //            |        |        |      |
console.log(RegExp.$1);             //      d     |    d   |    d   |   b  |  b

ES6 対応もやりやすくなり、ブラウザ間の差異も軽減されていい事づくめですね。めでたしめでたし。

2015年9月7日

RegExp.$1 と String.prototype.replace の悲惨な関係



この記事は全体として非推奨の機能の挙動について扱っています。コードを書く時の参考にしたりしないでください。

この記事は Firefox 44 までの挙動をもとに書いています。詳しくは 続報の記事 を参照してください。

RegExp非推奨プロパティRegExp.$1 なんてのがありますが、これが String.prototype.replace と大変相性がよろしくないというお話です。

そもそもこの RegExp.$1 というのは仕様が存在しないにも関わらずおおよそ全てのブラウザで実装されているというもので、この時点で互換性において悲惨な予感しかしないわけです。

基本的な動作としては、最後に行った RegExp.prototype.exec もしくは同等の操作の結果をグローバルな RegExp のプロパティに保存し、これを参照できるというものです。

/(a)/.exec("a");
console.log(RegExp.$1); // a

これは内部的に RegExp.prototype.exec を呼び出す String.prototype.matchString.prototype.searchString.prototype.replace でも同様です。

"a".replace(/(a)/, "b");
console.log(RegExp.$1); // a

ここで問題になるのが、String.prototype.replace は第二引数 replaceValue として関数が渡せるということです。最初の観測結果としては、String.prototype.replace を呼び出したで、その結果が取り出せましたが、この関数の中ではどうでしょう。

"a".replace(/(a)/, function() {
  console.log(RegExp.$1);
});

結果は以下のように、ブラウザによって異なっています。

Firefox
40.0.3
Chrome
45.0.2454.85
Safari
8.0.8
Edge
20.10240.16384.0
IE
11.0.10240.16384
a a a "" ""

Edge と IE では関数を呼び出す段階では結果が入っていません。あくまでも String.prototype.replace の後で結果を取り出すもの、という扱いのようです。

今度は、この関数の中でさらに RegExp.prototype.exec を呼び出して、String.prototype.replace の後の状態を見てみましょう。

"a".replace(/(a)/, function() {
  /b/.exec("b");
});
console.log(RegExp.$1);

結果は以下のようになりました。

Firefox Chrome Safari Edge IE
a b b a a

Chrome と Safari では関数内で実行した RegExp.prototype.exec の結果のままになっています。つまり、こちらはあくまでも最後に行ったマッチングの結果という扱いのようです。ここまでの動作をまとめると以下のようになります。

  • Firefox: 関数を呼び出す前に格納、呼び出した後に復元
  • Chrome と Safari: 関数を呼び出す前に格納
  • Edge と IE: 関数を呼び出した後で結果を格納

では、さきほどのコードで RegExp.prototype.exec を実行する代わりに例外を投げてみましょう。

try {
  "a".replace(/(a)/, function() {
    throw 1;
  });
} catch (e) {
}
console.log(RegExp.$1);

結果はこうでした。

Firefox Chrome Safari Edge IE
a a a "" ""

Edge と IE では、関数が例外を投げた場合、結果は格納されません。

まとめると以下のような挙動になります。

  • Firefox: 関数を呼び出す前に格納、呼び出した後に、関数の実行結果によらず復元
  • Chrome と Safari: 関数を呼び出す前に格納
  • Edge と IE: 関数を呼び出した後で、関数が正常に終了すれば結果を格納

いろいろ組み合わせて遊んでみましょう。

                                    //  Firefox | Chrome | Safari | Edge | IE
/(a)/.exec("a");                    // ---------+--------+--------+------+----
console.log(RegExp.$1);             //     a    |    a   |    a   |   a  |  a
"b".replace(/(b)/, function() {     //          |        |        |      |
  try {                             //          |        |        |      |
    console.log(RegExp.$1);         //     b    |    b   |    b   |   a  |  a
    "c".replace(/(c)/, function() { //          |        |        |      |
      console.log(RegExp.$1);       //     c    |    c   |    c   |   a  |  a
      /(d)/.exec("d");              //          |        |        |      |
      console.log(RegExp.$1);       //     d    |    d   |    d   |   d  |  d
      throw 1;                      //          |        |        |      |
    });                             //          |        |        |      |
  } catch (e) {}                    //          |        |        |      |
  console.log(RegExp.$1);           //     c    |    d   |    d   |   d  |  d
});                                 //          |        |        |      |
console.log(RegExp.$1);             //     b    |    d   |    d   |   b  |  b

もはや全てのブラウザで同じ値を返す状況の方が少ないです。

この RegExp.$1、もちろん ES6 にも入っていませんが、代わりに String.prototype.replace の挙動の方にかなりの変更が入っています。具体的には、第一引数 searchValue@@replace プロパティによって挙動を変更できます。更に、RegExpExec の中で明示的に exec プロパティを呼ぶという風に定義されているので*1exec プロパティを変更すると RegExp を渡したにも関わらず RegExp.$1 の値に変化が無い、という状況が様々に発生します。

var re = /foo/;
re[Symbol.replace] = function() {
  return "hello";
};
console.log("abc".replace(re, "X"));  // hello

var re2 = /foo/;
re2.exec = function() {
  return { 0: "a", index: 1, length: 1 };
};
console.log("abc".replace(re2, "X")); // aXc

ここで問題になるのが Firefox と Edge、IE の挙動です。このような状況で、RegExp.$1 はどう変化すべきでしょうか。RegExp.prototype.exec も何も実行されていない場合には関数を呼んだ後に格納するものがありません。おそらく RegExp.prototype.exec が実行されたかどうか、もしくは実際に searchValue[@@replace] を実行した後の RegExp.$1 の状況によって挙動を変える、という事になるでしょうか。エンジン間の差がより大きくなりそうです。

まとめ:RegExp.$1 とかはゼッタイに使わないでね


*1 ES5 の時点では "Do the search in the same manner as in String.prototype.match, including the update of searchValue.lastIndex." として、挙動は実装まかせでした。

2015年4月22日

js-ctypes で JIT (2) - 関数呼び出しのコストを抑える

js-ctypes では関数の呼び出しごとに引数、返り値の型のチェックや変換を行うので、その処理をなんとか省くことができれば結構高速に関数を呼ぶことができます。

今回は、ファイルを開いて1文字ずつ読み込む、というなんともヒドいコードを用いて、何度も呼び出されるコードを JIT を使って高速化してみましょう。対象とするコードがこちら。

let libc = ctypes.open(ctypes.libraryName("c"));

function withoutJit(fd, read) {
  let s = "";
  let buf = ctypes.char();
  let bufp = buf.address();
  for (;;) {
    let size = read(fd, bufp, 1);
    if (size <= 0)
      break;
    s += String.fromCharCode(buf.value);
  }
  return s;
}

let O_RDONLY = 0x0000;
let open = libc.declare("open", ctypes.default_abi, ctypes.int,
                        ctypes.char.ptr, ctypes.int);
let close = libc.declare("close", ctypes.default_abi, ctypes.int,
                         ctypes.int);
let read = libc.declare("read", ctypes.default_abi, ctypes.ssize_t,
                        ctypes.int, ctypes.voidptr_t, ctypes.size_t);

let fd = open("input", O_RDONLY);
let started = elapsed();
let s = withoutJit(fd, read);
print("withoutJit: " + (elapsed() - started));
close(fd);

libc.close();

今回、read がファイルのバイト数の分だけ呼び出されますので、そのたびに引数と返り値のチェック、変換が発生します。適当なファイル (3,143,051 バイト) に対して実行した結果がこちら。

withoutJit: 7722383

引数と返り値が少なければ少ないほど必要な処理は減るので、両方ナシにしてしまいましょう。そのためには、生成する JIT のコードに引数と返り値の即値を埋め込みます。

手順としては、まず参考にするマシン語を生成するコードを書きます。外側から放り込む値のところは見やすいように適当なパターンを埋め込んでおきます。今回は、read のアドレス、引数のファイルディスクリプタ、バッファのアドレス、サイズ、そして返り値を受け取る変数のアドレスの5種類です。

#include <unistd.h>

void
dummy(void) {
  int (*f)(int fildes, void *buf, size_t nbyte);
  int* v;
  f = (int (*)(int fildes, void *buf, size_t nbyte))0x7878787878787878ULL;
  v = (int*)0x9a9a9a9a9a9a9a9aULL;
  *v = f(0x1212, (void*)0x3434343434343434ULL, 0x5656565656565656ULL);
}

これをコンパイルすると必要なマシン語が得られます (今回は x86_64 用です)。

$ clang -O3 -c a.cc
$ otool -tVj a.o
a.o:
(__TEXT,__text) section
__Z5dummyv:
0000000000000000        55                      pushq   %rbp
0000000000000001        4889e5                  movq    %rsp, %rbp
0000000000000004        48be3434343434343434    movabsq $0x3434343434343434, %rsi ## imm = 0x3434343434343434
000000000000000e        48ba5656565656565656    movabsq $0x5656565656565656, %rdx ## imm = 0x5656565656565656
0000000000000018        48b87878787878787878    movabsq $0x7878787878787878, %rax ## imm = 0x7878787878787878
0000000000000022        bf12120000              movl    $0x1212, %edi           ## imm = 0x1212
0000000000000027        ffd0                    callq   *%rax
0000000000000029        48b99a9a9a9a9a9a9a9a    movabsq $-0x6565656565656566, %rcx ## imm = 0x9A9A9A9A9A9A9A9A
0000000000000033        8901                    movl    %eax, __Z5dummyv(%rcx)  ## dummy()
0000000000000035        5d                      popq    %rbp
0000000000000036        c3                      retq

これを前回と同じように mmap で確保したバッファに書き込み、呼び出して完成です。コード生成途中に memcpy を沢山呼んでいるのはもうちょっと最適化できるかとは思います。

let libc = ctypes.open(ctypes.libraryName("c"));

const PROT_WRITE = 0x02;
const PROT_EXEC = 0x04;
const MAP_ANON = 0x1000;
const MAP_PRIVATE = 0x0002;
let memcpy = libc.declare("memcpy", ctypes.default_abi,
                          ctypes.voidptr_t,
                          ctypes.voidptr_t, ctypes.voidptr_t, ctypes.size_t);

let mmap = libc.declare("mmap", ctypes.default_abi,
                        ctypes.voidptr_t,
                        ctypes.voidptr_t, ctypes.size_t, ctypes.int, ctypes.int,
                        ctypes.int, ctypes.off_t);
let munmap = libc.declare("munmap", ctypes.default_abi,
                          ctypes.int,
                          ctypes.voidptr_t, ctypes.size_t);

function withJit(fd, read) {
  let code = ctypes.uint8_t.array()([
    /* 00 */  0x55,                        // pushq   %rbp
    /* 01 */  0x48, 0x89, 0xe5,            // movq    %rsp, %rbp
    /* 04 */  0x48, 0xbe, 0,0,0,0,0,0,0,0, // movabsq BUF, %rsi
    /* 0e */  0x48, 0xba, 0,0,0,0,0,0,0,0, // movabsq NBYTE, %rdx
    /* 18 */  0x48, 0xb8, 0,0,0,0,0,0,0,0, // movabsq READ, %rax
    /* 22 */  0xbf, 0,0,0,0,               // movl    FILDES, %edi
    /* 27 */  0xff, 0xd0,                  // callq   *%rax
    /* 29 */  0x48, 0xb9, 0,0,0,0,0,0,0,0, // movabsq LEN, %rcx
    /* 33 */  0x89, 0x01,                  // movl    %eax, (%rcx)
    /* 35 */  0x5d,                        // popq    %rbp
    /* 36 */  0xc3,                        // retq
  ]);
  let fdv = ctypes.int(fd);
  let buf = ctypes.char();
  let bufp = ctypes.char.ptr(buf.address());
  let nbytes = ctypes.size_t(1);
  let result = ctypes.int();
  let resultp = ctypes.int.ptr(result.address());

  memcpy(code.addressOfElement(0x06), bufp.address(), bufp.constructor.size);
  memcpy(code.addressOfElement(0x10), nbytes.address(), nbytes.constructor.size);
  memcpy(code.addressOfElement(0x1a), read.address(), read.constructor.size);
  memcpy(code.addressOfElement(0x23), fdv.address(), fdv.constructor.size);
  memcpy(code.addressOfElement(0x2b), resultp.address(), resultp.constructor.size);

  let funcType = ctypes.FunctionType(ctypes.default_abi, ctypes.void_t);

  let mem = mmap(null, code.length,
                 PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE,
                 -1, 0);
  memcpy(mem, code, code.length);
  let func = ctypes.cast(mem, funcType.ptr);

  let s = "";
  for (;;) {
    func();
    if (result.value <= 0)
      break;
    s += String.fromCharCode(buf.value);
  }
  func = null;
  munmap(mem, code.length);

  return s;
}

let O_RDONLY = 0x0000;
let open = libc.declare("open", ctypes.default_abi, ctypes.int,
                        ctypes.char.ptr, ctypes.int);
let close = libc.declare("close", ctypes.default_abi, ctypes.int,
                         ctypes.int);
let read = libc.declare("read", ctypes.default_abi, ctypes.ssize_t,
                        ctypes.int, ctypes.voidptr_t, ctypes.size_t);

let fd = open("input", O_RDONLY);
let started = elapsed();
let s = withJit(fd, read);
print("withJit: " + (elapsed() - started));
close(fd);

libc.close();

これをさきほどと同じファイルで実行した結果がこちら。約 1/3 ほどの時間になっています。

withJit: 2331485

2015年4月21日

js-ctypes で JIT

Hello, JIT World: The Joy of Simple JITs の記事を参考に js-ctypes の上で JIT してみようの巻(ジョークです)。

とりあえず最初のコードをそれっぽくなんとなく JavaScript + js-ctypes で書き直す。

let libc = ctypes.open(ctypes.libraryName("c"));

let memcpy = libc.declare("memcpy", ctypes.default_abi,
                          ctypes.voidptr_t,
                          ctypes.voidptr_t, ctypes.voidptr_t, ctypes.size_t);

const PROT_WRITE = 0x02;
const PROT_EXEC = 0x04;
const MAP_ANON = 0x1000;
const MAP_PRIVATE = 0x0002;
let mmap = libc.declare("mmap", ctypes.default_abi,
                        ctypes.voidptr_t,
                        ctypes.voidptr_t, ctypes.size_t, ctypes.int, ctypes.int,
                        ctypes.int, ctypes.off_t);
let munmap = libc.declare("munmap", ctypes.default_abi,
                          ctypes.int,
                          ctypes.voidptr_t, ctypes.size_t);

let code = ctypes.uint8_t.array()([
  0xb8, 0x00, 0x00, 0x00, 0x00, // mov eax, 0
  0xc3                          // ret
]);

let num = ctypes.int32_t(0x12345678);
memcpy(code.addressOfElement(1), num.address(), num.constructor.size);

let mem = mmap(null, code.length,
               PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE,
               -1, 0);

memcpy(mem, code, code.length);

let funcType = ctypes.FunctionType(ctypes.default_abi, ctypes.int32_t);
let func = ctypes.cast(mem, funcType.ptr);

print(func().toString(16));

munmap(mem, code.length);

libc.close();

で、実行するとこうなる。

$ js jit.js
12345678

やったね。

2015年1月14日

js-ctypes で Objective-C を使う (6) - ES6 Proxy と ES6 Symbol を使う

前回の記事の時点では Proxy が Firefox 固有の古い方だったので、その辺を書き直したり、objc_msgSend_stret を使うかどうかを自動で判断するようにしたり、返り値の取得方法を Symbol 使ってもうちょっとマシにしたり、あと何か適当に色んなヘッダから型情報とか関数とか引っぱり出したり。

2015/01/15 修正: class_addMethodtypes に C の第一、第二引数を追加。いくつかの関数のエラーを報告、バックエンドのクラスをコアとラッパーに分割。

// objc.jsm

"use strict";

(function(module) {

let { utils: Cu } = Components;
let { ctypes } = Cu.import("resource://gre/modules/ctypes.jsm", {});

/**
 * Types related to Objective-C.
 * @type {Object}
 */
let objcTypes = (function() {
  let types = {};

  let LP64 = (ctypes.voidptr_t.size == 8);

  /* Types in objc.h. */
  types.objc_object = ctypes.StructType("objc_object", [
    { "isa": ctypes.voidptr_t } // Class isa;
  ]);
  types.id = types.objc_object.ptr;

  types.objc_selector = ctypes.StructType("objc_selector");
  types.SEL = types.objc_selector.ptr;

  types.IMP = ctypes.FunctionType(ctypes.default_abi,
                                  types.id,
                                  [types.id,
                                   types.SEL,
                                   "..."]).ptr;

  types.BOOL = ctypes.signed_char;
  types.arith_t = LP64 ? ctypes.long : ctypes.int;
  types.uarith_t = LP64 ? ctypes.unsigned_long : ctypes.unsigned_int;
  types.STR = ctypes.char.ptr;

  /* Types in runtime.h. */
  types.objc_method = ctypes.StructType("objc_method", []);
  types.Method = types.objc_method.ptr;
  types.objc_ivar = ctypes.StructType("objc_ivar", []);
  types.Ivar = types.objc_ivar.ptr;
  types.objc_category = ctypes.StructType("objc_category", []);
  types.Category = types.objc_category.ptr;
  types.objc_property = ctypes.StructType("objc_property", []);
  types.objc_property_t = types.objc_property.ptr;

  types.objc_class = types.objc_object;
  types.Class = types.objc_class.ptr;

  types.Protocol = types.objc_object;

  types.objc_method_description
    = ctypes.StructType("objc_method_description", [
      { "name":  types.SEL },
      { "types": ctypes.char.ptr },
    ]);
  types.objc_property_attribute_t
    = ctypes.StructType("objc_property_attribute_t", [
      { "name":  ctypes.char.ptr },
      { "value": ctypes.char.ptr },
    ]);

  types.objc_AssociationPolicy = ctypes.uintptr_t;

  /* Types in message.h. */
  types.objc_super
    = ctypes.StructType("objc_super", [
      { "receiver":     types.id },
      { "super_class":  types.Class },
    ]);

  /* Some Foundation data types. */
  if (LP64) {
    types.CGFloat = ctypes.float64_t;
    types.NSInteger = ctypes.long;
    types.NSUInteger = ctypes.unsigned_long;
  } else {
    types.CGFloat = ctypes.float32_t;
    types.NSInteger = ctypes.int;
    types.NSUInteger = ctypes.unsigned_int;
  }
  types.NSTimeInterval = ctypes.double;

  types.NSPoint = ctypes.StructType("NSPoint",
                                    [ { "x": types.CGFloat },
                                      { "y": types.CGFloat } ]);
  types.NSPointPointer = types.NSPoint.ptr;
  types.CGPoint = types.NSPoint;

  types.NSSize = ctypes.StructType("NSSize",
                                   [ { "width":  types.CGFloat },
                                     { "height": types.CGFloat } ]);
  types.NSSizePointer = types.NSSize.ptr;
  types.CGSize = types.NSSize;

  types.NSRect = ctypes.StructType("NSRect",
                                   [ { "origin": types.NSPoint },
                                     { "size":  types.NSSize } ]);
  types.NSRectPointer = types.NSRect.ptr;
  types.CGRect = types.NSRect;

  types.CGVector = ctypes.StructType("CGVector",
                                   [ { "dx": types.NSPoint },
                                     { "dy":  types.NSSize } ]);

  types.NSRange = ctypes.StructType("NSRange",
                                    [ { "location" : types.NSUInteger },
                                      { "length"   : types.NSUInteger } ]);
  types.NSRangePointer = types.NSRange.ptr;

  return Object.freeze(types);
})();

/**
 * Backend implementation of Objective-C ctypes.
 */
let objcImpl = Object.seal({
  initialized: false,

  lib: null,
  funcs: null,

  /**
   * Open Objective-C library and load functions.
   */
  init: function() {
    if (this.initialized) {
      return;
    }

    this.lib = ctypes.open(ctypes.libraryName("objc"));
    this.funcs = this._generateFuncs(this.lib);

    this.initialized = true;
  },

  /**
   * Release library.
   */
  release: function() {
    if (this.initialized) {
      this.lib.close();
      this.initialized = false;
    }
  },

  /* ---- classes/types ---- */

  /**
   * Register an Objective-C class with the specified super class.
   *
   * @param   {objcTypes.id} superClassInstance
   *          An instance of the super class.
   * @param   {string} className
   *          A name of the sub class to register.
   * @returns {objcTypes.id}
   *          A pointer to the registered class.
   */
  registerClass: function(superClassInstance, className) {
    let Class = this.funcs.objc_allocateClassPair(superClassInstance,
                                                  className, 0);
    if (!Class) {
      throw "objc_allocateClassPair retuns null";
    }
    this.funcs.objc_registerClassPair(Class);

    return Class;
  },

  /* ---- message ---- */

  /**
   * Send a message to an instance of a class.
   *
   * @param   {function} msgSend
   *          objc_msgSend* function
   * @param   {objcTypes.id} self
   *          A pointer that points to the instance of the class that is
   *          to receive the message.
   * @param   {string} sel
   *          The selector of the method that handles the message.
   * @returns {*}
   *          The return value of the method.
   */
  send: function(msgSend, self, sel, args) {
    return msgSend(self, this.funcs.sel_registerName(sel),
                   ...args);
  },

  /**
   * Serialize selector parts.
   *
   * @param   {Array.<string>} sels
   *          An array contains selector parts.
   * @param   {Array.<*>} args
   *          An array contains arguments or argument types.
   * @returns {string}
   *          A selector name.
   */
  serializeSelector: function(sels, args) {
    let s = sels.join(":");
    if (args.length > 0) {
      s += ":";
    }
    return s;
  },

  /**
   * Encode CType to Objective-C type.
   *
   * @param   {CType} origType
   *          A type.
   * @returns {string}
   *          A string represents Objective-C type.
   */
  encodeType: function(origType) {
    let type = origType;
    let str = "";

    /* Pointer. */
    while ("targetType" in type &&
           type.targetType) {
      /* Special types. */
      if (type == objcTypes.id) {
        str += "@";
        return str;
      }
      if (type == objcTypes.Class) {
        str += "#";
        return str;
      }
      if (type == objcTypes.SEL) {
        str += ":";
        return str;
      }

      str += "^";
      type = type.targetType;
    }

    /* Function. */
    if ("abi" in type) {
      str += "?";
      return str;
    }

    /* Struct. */
    if ("fields" in type) {
      str += "{" + type.name + "=" + type.fields.map(field => {
        let name = Object.keys(field)[0];
        return this.encodeType(field[name]);
      }).join("") + "}";
      return str;
    }

    /* Array. */
    if ("elementType" in type &&
        "length" in type) {
      if (type.length) {
        str += "[" + type.length + this.encodeType(type.elementType) + "]";
      } else {
        str += "^" + this.encodeType(type.elementType);
      }
      return str;
    }

    /* Primitive types. */
    switch (type) {
      case ctypes.int8_t:              str += "c"; break;
      case ctypes.uint8_t:             str += "C"; break;
      case ctypes.int16_t:             str += "s"; break;
      case ctypes.uint16_t:            str += "S"; break;
      case ctypes.int32_t:             str += "i"; break;
      case ctypes.uint32_t:            str += "I"; break;
      case ctypes.int64_t:             str += "l"; break;
      case ctypes.uint64_t:            str += "L"; break;

      case ctypes.float32_t:           str += "f"; break;
      case ctypes.float64_t:           str += "d"; break;

      case ctypes.bool:                str += "B"; break;
      case ctypes.short:               str += "s"; break;
      case ctypes.unsigned_short:      str += "S"; break;
      case ctypes.int:                 str += "i"; break;
      case ctypes.unsigned_int:        str += "I"; break;
      case ctypes.long:                str += "l"; break;
      case ctypes.unsigned_long:       str += "L"; break;
      case ctypes.long_long:           str += "q"; break;
      case ctypes.unsigned_long_long:  str += "Q"; break;

      case ctypes.float:               str += "f"; break;
      case ctypes.double:              str += "d"; break;

      case ctypes.char:                str += "c"; break;
      case ctypes.signed_char:         str += "c"; break;
      case ctypes.unsigned_char:       str += "C"; break;

      case ctypes.size_t:              str += "L"; break;
      case ctypes.ssize_t:             str += "l"; break;
      case ctypes.intptr_t:            str += "l"; break;
      case ctypes.uintptr_t:           str += "L"; break;

      case ctypes.jschar:              str += "s"; break;

      case ctypes.void_t:              str += "v"; break;

      case ctypes.Int64:               str += "q"; break;
      case ctypes.UInt64:              str += "Q"; break;

      default: throw new Error("Unsupported type: " + origType);
    }

    return str;
  },

  /**
   * Encode CType to Objective-C type.
   *
   * @param   {CType} ret
   *          A return value type.
   * @param   {Array.<CType>} args
   *          An array of arguments types.
   * @returns {string}
   *          An string represents Objective-C type.
   */
  encodeMethod: function(ret, args) {
    return [ret, ...args].map(t => this.encodeType(t)).join("");
  },

  /* ---- function ---- */

  /**
   * Generate lazy functions object.
   *
   * @param   {Library} lib
   *          An objc library.
   * @returns {Object}
   *          A lazy functions object.
   */
  _generateFuncs: function(lib) {
    let types = objcTypes;

    let funcs = {};
    let funcProps = [
      /* Functions in objc.h. */
      ["sel_getName", ctypes.default_abi,
       ctypes.char.ptr, types.SEL],
      ["sel_registerName", ctypes.default_abi,
       types.SEL, ctypes.char.ptr],
      ["object_getClassName", ctypes.default_abi,
       ctypes.char.ptr, types.id],
      ["object_getIndexedIvars", ctypes.default_abi,
       ctypes.voidptr_t, types.id],
      ["sel_isMapped", ctypes.default_abi,
       types.BOOL, types.SEL],
      ["sel_getUid", ctypes.default_abi,
       types.SEL, ctypes.char.ptr],

      /* Functions in runtime.h. */
      ["object_copy", ctypes.default_abi,
       types.id, types.id, ctypes.size_t],
      ["object_dispose", ctypes.default_abi,
       types.id, types.id],
      ["object_getClass", ctypes.default_abi,
       types.Class, types.id],
      ["object_setClass", ctypes.default_abi,
       types.Class, types.id, types.Class],
      ["object_getClassName", ctypes.default_abi,
       ctypes.char.ptr, types.id],
      ["object_getIndexedIvars", ctypes.default_abi,
       ctypes.voidptr_t, types.id],
      ["object_getIvar", ctypes.default_abi,
       types.id, types.id, types.Ivar],
      ["object_setIvar", ctypes.default_abi,
       ctypes.void_t, types.id, types.Ivar, types.id],
      ["object_setInstanceVariable", ctypes.default_abi,
       types.Ivar, types.id, ctypes.char.ptr, ctypes.voidptr_t],
      ["object_getInstanceVariable", ctypes.default_abi,
       types.Ivar, types.id, ctypes.char.ptr, ctypes.voidptr_t.ptr],
      ["objc_getClass", ctypes.default_abi,
       types.Class, ctypes.char.ptr],
      ["objc_getMetaClass", ctypes.default_abi,
       types.Class, ctypes.char.ptr],
      ["objc_lookUpClass", ctypes.default_abi,
       types.Class, ctypes.char.ptr],
      ["objc_getRequiredClass", ctypes.default_abi,
       types.Class, ctypes.char.ptr],
      ["objc_getClassList", ctypes.default_abi,
       ctypes.int, types.Class.ptr, ctypes.int],
      ["objc_copyClassList", ctypes.default_abi,
       types.Class.ptr, ctypes.unsigned_int.ptr],
      ["class_getName", ctypes.default_abi,
       ctypes.char.ptr, types.Class],
      ["class_isMetaClass", ctypes.default_abi,
       types.BOOL, types.Class],
      ["class_getSuperclass", ctypes.default_abi,
       types.Class, types.Class],
      ["class_setSuperclass", ctypes.default_abi,
       types.Class, types.Class, types.Class],
      ["class_getVersion", ctypes.default_abi,
       ctypes.int, types.Class],
      ["class_setVersion", ctypes.default_abi,
       ctypes.void_t, types.Class, ctypes.int],
      ["class_getInstanceSize", ctypes.default_abi,
       ctypes.size_t, types.Class],
      ["class_getInstanceVariable", ctypes.default_abi,
       types.Ivar, types.Class, ctypes.char.ptr],
      ["class_getClassVariable", ctypes.default_abi,
       types.Ivar, types.Class, ctypes.char.ptr],
      ["class_copyIvarList", ctypes.default_abi,
       types.Ivar.ptr, types.Class, ctypes.unsigned_int.ptr],
      ["class_getInstanceMethod", ctypes.default_abi,
       types.Method, types.Class, types.SEL],
      ["class_getClassMethod", ctypes.default_abi,
       types.Method, types.Class, types.SEL],
      ["class_getMethodImplementation", ctypes.default_abi,
       types.IMP, types.Class, types.SEL],
      ["class_getMethodImplementation_stret", ctypes.default_abi,
       types.IMP, types.Class, types.SEL],
      ["class_respondsToSelector", ctypes.default_abi,
       types.BOOL, types.Class, types.SEL],
      ["class_copyMethodList", ctypes.default_abi,
       types.Method.ptr, types.Class, ctypes.unsigned_int.ptr],
      ["class_conformsToProtocol", ctypes.default_abi,
       types.BOOL, types.Class, types.Protocol.ptr],
      ["class_copyProtocolList", ctypes.default_abi,
       types.Protocol.ptr.ptr, types.Class, ctypes.unsigned_int.ptr],
      ["class_getProperty", ctypes.default_abi,
       types.objc_property_t, types.Class, ctypes.char.ptr],
      ["class_copyPropertyList", ctypes.default_abi,
       types.objc_property_t.ptr, types.Class, ctypes.unsigned_int.ptr],
      ["class_getIvarLayout", ctypes.default_abi,
       ctypes.uint8_t.ptr, types.Class],
      ["class_getWeakIvarLayout", ctypes.default_abi,
       ctypes.uint8_t.ptr, types.Class],
      ["class_addMethod", ctypes.default_abi,
       types.BOOL, types.Class, types.SEL, types.IMP, ctypes.char.ptr],
      ["class_replaceMethod", ctypes.default_abi,
       types.IMP, types.Class, types.SEL, types.IMP, ctypes.char.ptr],
      ["class_addIvar", ctypes.default_abi,
       types.BOOL, types.Class, ctypes.char.ptr, ctypes.size_t, ctypes.uint8_t, ctypes.char.ptr],
      ["class_addProtocol", ctypes.default_abi,
       types.BOOL, types.Class, types.Protocol.ptr],
      ["class_addProperty", ctypes.default_abi,
       types.BOOL, types.Class, ctypes.char.ptr, types.objc_property_attribute_t.ptr, ctypes.unsigned_int],
      ["class_replaceProperty", ctypes.default_abi,
       ctypes.void_t, types.Class, ctypes.char.ptr, types.objc_property_attribute_t.ptr, ctypes.unsigned_int],
      ["class_setIvarLayout", ctypes.default_abi,
       ctypes.void_t, types.Class, ctypes.uint8_t.ptr],
      ["class_setWeakIvarLayout", ctypes.default_abi,
       ctypes.void_t, types.Class, ctypes.uint8_t.ptr],
      ["objc_getFutureClass", ctypes.default_abi,
       types.Class, ctypes.char.ptr],
      ["objc_setFutureClass", ctypes.default_abi,
       ctypes.void_t, types.Class, ctypes.char.ptr],
      ["class_createInstance", ctypes.default_abi,
       types.id, types.Class, ctypes.size_t],
      ["objc_constructInstance", ctypes.default_abi,
       types.id, types.Class, ctypes.voidptr_t],
      ["objc_destructInstance", ctypes.default_abi,
       ctypes.voidptr_t, types.id],
      ["objc_allocateClassPair", ctypes.default_abi,
       types.Class, types.Class, ctypes.char.ptr, ctypes.size_t],
      ["objc_registerClassPair", ctypes.default_abi,
       ctypes.void_t, types.Class],
      ["objc_duplicateClass", ctypes.default_abi,
       types.Class, types.Class, ctypes.char.ptr, ctypes.size_t],
      ["objc_disposeClassPair", ctypes.default_abi,
       ctypes.void_t, types.Class],
      ["method_getName", ctypes.default_abi,
       types.SEL, types.Method],
      ["method_getImplementation", ctypes.default_abi,
       types.IMP, types.Method],
      ["method_getTypeEncoding", ctypes.default_abi,
       ctypes.char.ptr, types.Method],
      ["method_getNumberOfArguments", ctypes.default_abi,
       ctypes.unsigned_int, types.Method],
      ["method_copyReturnType", ctypes.default_abi,
       ctypes.char.ptr, types.Method],
      ["method_copyArgumentType", ctypes.default_abi,
       ctypes.char.ptr, types.Method, ctypes.unsigned_int],
      ["method_getReturnType", ctypes.default_abi,
       ctypes.void_t, types.Method, ctypes.char.ptr, ctypes.size_t],
      ["method_getArgumentType", ctypes.default_abi,
       ctypes.void_t, types.Method, ctypes.unsigned_int, ctypes.char.ptr, ctypes.size_t],
      ["method_getDescription", ctypes.default_abi,
       types.objc_method_description.ptr, types.Method],
      ["method_setImplementation", ctypes.default_abi,
       types.IMP, types.Method, types.IMP],
      ["method_exchangeImplementations", ctypes.default_abi,
       ctypes.void_t, types.Method, types.Method],
      ["ivar_getName", ctypes.default_abi,
       ctypes.char.ptr, types.Ivar],
      ["ivar_getTypeEncoding", ctypes.default_abi,
       ctypes.char.ptr, types.Ivar],
      //  ["ivar_getOffset", ctypes.default_abi,
      //   types.ptrdiff_t,  types.Ivar],
      ["property_getName", ctypes.default_abi,
       ctypes.char.ptr, types.objc_property_t],
      ["property_getAttributes", ctypes.default_abi,
       ctypes.char.ptr, types.objc_property_t],
      ["property_copyAttributeList", ctypes.default_abi,
       types.objc_property_attribute_t.ptr, types.objc_property_t, ctypes.unsigned_int.ptr],
      ["property_copyAttributeValue", ctypes.default_abi,
       ctypes.char.ptr, types.objc_property_t, ctypes.char.ptr],
      ["objc_getProtocol", ctypes.default_abi,
       types.Protocol.ptr, ctypes.char.ptr],
      ["objc_copyProtocolList", ctypes.default_abi,
       types.Protocol.ptr.ptr, ctypes.unsigned_int.ptr],
      ["protocol_conformsToProtocol", ctypes.default_abi,
       types.BOOL, types.Protocol.ptr, types.Protocol.ptr],
      ["protocol_isEqual", ctypes.default_abi,
       types.BOOL, types.Protocol.ptr, types.Protocol.ptr],
      ["protocol_getName", ctypes.default_abi,
       ctypes.char.ptr, types.Protocol.ptr],
      ["protocol_getMethodDescription", ctypes.default_abi,
       types.objc_method_description, types.Protocol.ptr, types.SEL, types.BOOL, types.BOOL],
      ["protocol_copyMethodDescriptionList", ctypes.default_abi,
       types.objc_method_description.ptr, types.Protocol.ptr, types.BOOL, types.BOOL, ctypes.unsigned_int.ptr],
      ["protocol_getProperty", ctypes.default_abi,
       types.objc_property_t, types.Protocol.ptr, ctypes.char.ptr, types.BOOL, types.BOOL],
      ["protocol_copyPropertyList", ctypes.default_abi,
       types.objc_property_t.ptr, types.Protocol.ptr, ctypes.unsigned_int.ptr],
      ["protocol_copyProtocolList", ctypes.default_abi,
       types.Protocol.ptr.ptr, types.Protocol.ptr, ctypes.unsigned_int.ptr],
      ["objc_allocateProtocol", ctypes.default_abi,
       types.Protocol.ptr, ctypes.char.ptr],
      ["objc_registerProtocol", ctypes.default_abi,
       ctypes.void_t, types.Protocol.ptr],
      ["protocol_addMethodDescription", ctypes.default_abi,
       ctypes.void_t, types.Protocol.ptr, types.SEL, ctypes.char.ptr, types.BOOL, types.BOOL],
      ["protocol_addProtocol", ctypes.default_abi,
       ctypes.void_t, types.Protocol.ptr, types.Protocol.ptr],
      ["protocol_addProperty", ctypes.default_abi,
       ctypes.void_t, types.Protocol.ptr, ctypes.char.ptr, types.objc_property_attribute_t.ptr, ctypes.unsigned_int, types.BOOL, types.BOOL],
      ["objc_copyImageNames", ctypes.default_abi,
       ctypes.char.ptr.ptr, ctypes.unsigned_int.ptr],
      ["class_getImageName", ctypes.default_abi,
       ctypes.char.ptr, types.Class],
      ["objc_copyClassNamesForImage", ctypes.default_abi,
       ctypes.char.ptr.ptr, ctypes.char.ptr, ctypes.unsigned_int.ptr],
      ["sel_getName", ctypes.default_abi,
       ctypes.char.ptr, types.SEL],
      ["sel_getUid", ctypes.default_abi,
       types.SEL, ctypes.char.ptr],
      ["sel_registerName", ctypes.default_abi,
       types.SEL, ctypes.char.ptr],
      ["sel_isEqual", ctypes.default_abi,
       types.BOOL, types.SEL, types.SEL],
      ["objc_enumerationMutation", ctypes.default_abi,
       ctypes.void_t, types.id],
      ["objc_setEnumerationMutationHandler", ctypes.default_abi,
       ctypes.void_t, ctypes.FunctionType(ctypes.default_abi, ctypes.void_t, [types.id]).ptr],
      ["objc_setForwardHandler", ctypes.default_abi,
       ctypes.void_t, ctypes.voidptr_t, ctypes.voidptr_t],
      ["imp_implementationWithBlock", ctypes.default_abi,
       types.IMP, types.id],
      ["imp_getBlock", ctypes.default_abi,
       types.id, types.IMP],
      ["imp_removeBlock", ctypes.default_abi,
       types.BOOL, types.IMP],
      ["objc_loadWeak", ctypes.default_abi,
       types.id, types.id.ptr],
      ["objc_storeWeak", ctypes.default_abi,
       types.id, types.id.ptr, types.id],
      ["objc_setAssociatedObject", ctypes.default_abi,
       ctypes.void_t, types.id, ctypes.voidptr_t, types.id, types.objc_AssociationPolicy],
      ["objc_getAssociatedObject", ctypes.default_abi,
       types.id, types.id, ctypes.voidptr_t],
      ["objc_removeAssociatedObjects", ctypes.default_abi,
       ctypes.void_t, types.id],
      ["class_lookupMethod", ctypes.default_abi,
       types.IMP, types.Class, types.SEL],
      ["class_respondsToMethod", ctypes.default_abi,
       types.BOOL, types.Class, types.SEL],
      ["_objc_flush_caches", ctypes.default_abi,
       ctypes.void_t, types.Class],
      ["object_copyFromZone", ctypes.default_abi,
       types.id, types.id, ctypes.size_t, ctypes.voidptr_t],

      ["class_createInstanceFromZone", ctypes.default_abi,
       types.id, types.Class, ctypes.size_t, ctypes.voidptr_t],

      /* Functions in message.h. */
      ["objc_msgSend", ctypes.default_abi,
       types.id, types.id, types.SEL, "..."],
      ["objc_msgSendSuper", ctypes.default_abi,
       types.id, types.objc_super.ptr, types.SEL, "..."],
      ["objc_msgSend_stret", ctypes.default_abi,
       ctypes.void_t, ctypes.voidptr_t, types.id, types.SEL, "..."],
      ["objc_msgSendSuper_stret", ctypes.default_abi,
       ctypes.void_t, ctypes.voidptr_t, types.objc_super.ptr, types.SEL, "..."],
      ["objc_msgSend_fpret", ctypes.default_abi,
       ctypes.double, types.id, types.SEL, "..."],

      ["method_invoke", ctypes.default_abi,
       types.id, types.id, types.Method, "..."],
      ["method_invoke_stret", ctypes.default_abi,
       ctypes.void_t, ctypes.voidptr_t, types.id, types.Method, "..."],

      ["_objc_msgForward", ctypes.default_abi,
       types.id, types.id, types.Method, "..."],
      ["_objc_msgForward_stret", ctypes.default_abi,
       ctypes.void_t, ctypes.voidptr_t, types.id, types.Method, "..."],
    ];
    for (let tmp of funcProps) {
      let name = tmp[0];
      let args = tmp;
      Object.defineProperty(funcs, name, {
        get: function () {
          delete funcs[name];
          return funcs[name] = lib.declare(...args);
        },
        configurable: true,
        enumerable: true
      });
    }

    return funcs;
  }
});

/**
 * Backend implementation of wrapping Objective-C data.
 */
let objcWrapper = Object.seal({
  initialized: false,

  get_unwrapped: null,
  as_unwrapped: null,
  as_float: null,
  ret_functions: null,

  /**
   * Initialize wrapper functions.
   */
  init: function() {
    if (objcWrapper.initialized) {
      return;
    }

    this.get_unwrapped = Symbol.for("objc_get_unwrapped");
    this.as_unwrapped = Symbol.for("objc_as_unwrapped");
    this.as_float = Symbol.for("objc_as_float");
    this.ret_functions = new Map();
    this.ret_functions.set(this.as_unwrapped, objcImpl.funcs.objc_msgSend);
    this.ret_functions.set(this.as_float, objcImpl.funcs.objc_msgSend_fpret);
  },


  /**
   * Release wrapper functions.
   */
  release: function() {
    if (this.initialized) {
      this.ret_functions.clear();
      this.initialized = false;
    }
  },

  /**
   * Register a msgSend* function for the specified return value type.
   *
   * @param   {CType} type
   *          A return value type.
   * @returns {Symbol}
   *          An unique symbol for the return value type.
   */
  registerFunctionForType: function(type) {
    if (type == ctypes.float || type == ctypes.double ||
        type == ctypes.float32_t || type == ctypes.float64_t) {
      return this.as_float;
    }

    let sym = Symbol.for("objc_as_" + type.toString);

    if (type.size > ctypes.voidptr_t.size * 2) {
      this.ret_functions.set(sym, function(...args) {
        let ret = type();
        objcImpl.funcs.objc_msgSend_stret(ret.address(), ...args);
        return ret;
      });
    } else {
      let func = objcImpl.lib.declare("objc_msgSend",
                                      ctypes.default_abi,
                                      type,
                                      objcTypes.id,
                                      objcTypes.SEL,
                                      "...");
      this.ret_functions.set(sym, func);
    }

    return sym;
  },

  /* ---- wrap ---- */

  /**
   * Wrap the specified raw Objective-C object into Proxy object.
   *
   * @param   {objcTypes.id} obj
   *          A pointer that points Objective-C class/instance.
   * @param   {Array} sels
   *          An array contains selector names.
   * @returns {Proxy.<function>}
   *          A wrapped Objective-C Proxy.
   */
  wrap: function(obj, sels=[]) {
    return new Proxy(
      /**
       * Send a message to an instance of a class.
       *
       * @param   {...} args
       *          A variable argument list containing the arguments to the
       *          method.
       * @returns {Proxy}
       *          A wrapped Objective-C Proxy of the return value of the method.
       */
      function(...args) {
        if (sels.length) {
          let name = sels[sels.length - 1];
          if (typeof name == "symbol") {
            sels.pop();

            if (!this.ret_functions.has(name)) {
              throw new Error("Unknown symbol: " + name);
            }

            let func = this.ret_functions.get(name);
            return objcImpl.send(func,
                                 obj, objcImpl.serializeSelector(sels, args),
                                 args);
          }
        }

        let ret = objcImpl.send(objcImpl.funcs.objc_msgSend,
                                obj, objcImpl.serializeSelector(sels, args),
                                args);
        return this.wrap(ret);
      }.bind(this), {
        /**
         * Define a method of a custom Class.
         *
         * @param   {*} _
         *          Not used.
         * @param   {string} sel
         *          A part of selector.
         * @param   {Object}
         *          Return value type, arguments types, and the function of the
         *          method.
         */
        set: function(_, sel, methodProps) {
          /* Due to the restriction of function Proxy, some property name cannot
           * be used (e.g. length), so use it with leading "____", which  will
           * be removed here. */
          if (typeof sel == "string") {
            sel = sel.replace(/____/, "");
          }
          let {ret, args, impl} = methodProps;
          let selAll = objcImpl.serializeSelector(sels.concat(sel), args);
          let argsTypeAll = [objcTypes.id, objcTypes.SEL, ...args];
          let funcType = ctypes.FunctionType(ctypes.default_abi,
                                             ret, argsTypeAll);
          if (!objcImpl.funcs.class_addMethod(obj,
                                              objcImpl.funcs.sel_registerName(selAll),
                                              ctypes.cast(funcType.ptr(impl), objcTypes.IMP),
                                              objcImpl.encodeMethod(ret, argsTypeAll))) {
            throw "class_addMethod returns NO";
          }
        }.bind(this),

        /**
         * Return a wrapped object for the specified method.
         *
         * @param   {*} _
         *          Not used.
         * @param   {string} sel
         *          A part of selector.
         * @returns {Proxy.<function>}
         *          A wrapped Objective-C Proxy for the method.
         */
        get: function(_, sel) {
          /* JavaScript runtime will sometimes call valueOf/toString,
           * and it will result in sending those unexistent message,
           * and cause crash. */
          if (sels.length == 0
              && (sel == "valueOf" ||
                  sel == "toString" ||
                  sel == "toSource")) {
            throw new Error(sel + " is called.");
          }

          if (typeof sel == "string") {
            sel = sel.replace(/____/, "");
          }

          if (sel == this.get_unwrapped) {
            return obj;
          }

          return this.wrap(obj, sels.concat(sel));
        }.bind(this)
      });
  }
});

/**
 * Frontend implementation of Objective-C ctypes.
 */
let objc = Object.freeze({
  /**
   * Open Objective-C library and load functions.
   */
  init: function() {
    objcImpl.init();
    objcWrapper.init();
  },

  /**
   * Release library.
   */
  release: function() {
    objcImpl.release();
    objcWrapper.release();
  },

  /* ---- classes/types ---- */

  /**
   * Type specifier.
   * @type {Proxy.<function>}
   *
   * @param   {CType} type
   *          A return value type.
   * @returns {Symbol}
   *          An unique symbol for the return value type.
   */
  as: new Proxy(function(type) {
    return objcWrapper.registerFunctionForType(type);
  }, {
    /**
     * Return an unique symbol for the specified simple type.
     *
     * @param   {*} _
     *          Not used.
     * @param   {string} name
     *          A property name of objcTypes or ctypes for the return value
     *          type.
     * @returns {Symbol}
     *          A unique symbol for the return value type.
     */
    get: function(_, name) {
      if (name == "unwrapped") {
        return objcWrapper.as_unwrapped;
      }

      let type = null;
      if (name in objcTypes) {
        type = objcTypes[name];
      } else if (name in ctypes) {
        type = ctypes[name];
      } else {
        throw new Error("unknown type: " + name);
      }

      return objcWrapper.registerFunctionForType(type);
    },
  }),

  /**
   * Proxy object to get an Objective-C Class.
   * @type {Proxy.<object>}
   */
  classes: new Proxy({}, {
    /**
     * Return an Objective-C class for the specified name.
     *
     * @param   {*} _
     *          Not used.
     * @param   {string} className
     *          A name of the Objective-C class.
     * @returns {Proxy.<function>}
     *          A wrapped Objective-C Proxy for the class.
     */
    get: function(_, className) {
      return objcWrapper.wrap(objcImpl.funcs.objc_getClass(className));
    },

    /**
     * Determine the existence of the Objective-C class with the specified name.
     *
     * @param   {*} _
     *          Not used.
     * @param   {string} className
     *          A name of the Objective-C class.
     * @returns {boolean}
     *          The existence of the class.
     */
    has: function(_, className) {
      return !objcImpl.funcs.objc_getClass(className).isNull();
    }
  }),

  /**
   * Proxy object to register a custom Objective-C Class.
   * @type {Proxy.<object>}
   */
  newClass: new Proxy({}, {
    /**
     * Return an Objective-C class for the specified name.
     *
     * @param   {*} _
     *          Not used.
     * @param   {string} className
     *          A name of the Objective-C class.
     * @returns {function}
     *          A function which takes a super class instance and
     *          returns a wrapped Objective-C Proxy for the class.
     */
    get: function(_, className) {
      /**
       * Return an Objective-C class for the specified name and superClass.
       *
       * @param   {objc.types.id} superClassInstance
       *          An instance of the super class.
       * @returns {function}
       *          A wrapped Objective-C Proxy for the class.
       */
      return function(superClassInstance) {
        return objcWrapper.wrap(objcImpl.registerClass(superClassInstance,
                                                       className));
      };
    }
  }),

  /**
   * Types related to Objective-C.
   * @type {Object}
   */
  types: objcTypes,

  /**
   * Register a selector.
   *
   * @param   {String} name
   *          A name of selector
   * @returns {objc.types.SEL}
   *          A selector
   */
  sel: function(name) {
    return objcImpl.funcs.sel_registerName(name);
  },

  /* ---- wrap ---- */

  /**
   * Wrap raw Objective-C object into Proxy object.
   *
   * @param   {objc.types.id} obj
   *          A pointer that points Objective-C class/instance.
   * @param   {Array} sels
   *          An array contains selector names.
   * @returns {Proxy}
   *          A wrapped Objective-C Proxy.
   */
  wrap: function(obj, sels=[]) {
    return objcWrapper.wrap(obj, sels);
  },

  /**
   * Unwrap Objective-C Proxy object to raw ctypes pointer.
   *
   * @param   {Proxy} obj
   *          Objective-C Proxy object.
   * @returns {objc.types.id}
   *          A pointer that points Objective-C class/instance.
   */
  unwrap: function(obj) {
    return obj[objcWrapper.get_unwrapped];
  },

  /* ---- misc ---- */

  /**
   * Call specified function in the temporary pool.
   *
   * @param   {function} f
   *          function to call in the pool
   * @returns {*}
   *          returned value from f
   */
  autoreleasepool: function(f) {
    let pool = this.classes.NSAutoreleasePool.
        alloc().
        init();
    try {
      return f();
    } finally {
      pool.release();
    }
  },

  /**
   * Make NSString instance
   *
   * @param   {string} str
   *          UTF8 encoded string
   * @returns {objc.types.id}
   *          NSString instance
   */
  NSString: function(str) {
    return this.classes.NSString.
      alloc().
      initWithUTF8String(ctypes.char.array()(str)).
      autorelease[this.as.unwrapped]();
  }
});

module.EXPORTED_SYMBOLS = ["objc"];
module.objc = objc;

})(this);

こちらがテストコード。

let { utils: Cu } = Components;
let { ctypes } = Cu.import("resource://gre/modules/ctypes.jsm", {});
let { objc } = Cu.import("path_to_objc.jsm", {});

objc.init();

// Call speech API.
objc.autoreleasepool(function() {
  let voice = "com.apple.speech.synthesis.voice.kyoko.premium";
  let synth = objc.classes.NSSpeechSynthesizer.
      alloc().
      initWithVoice(objc.NSString(voice)).
      autorelease();

  let NSUTF8StringEncoding = ctypes.int32_t(4);
  let text = objc.classes.NSString.
      alloc().
      initWithCString.encoding(ctypes.char.array()("a"),
                               NSUTF8StringEncoding).
      autorelease();

  synth.startSpeakingString(objc.unwrap(text));

  while (synth.isSpeaking[objc.as.int32_t]()) {}
});

// Create custom class and add methods, call them.
objc.autoreleasepool(function() {
  let TestJSClass = objc.newClass.TestJSClass(objc.unwrap(objc.classes.NSObject));
  TestJSClass.pi = {
    ret: ctypes.double,
    args: [],
    impl: function(self, sel) {
      return Math.PI;
    }
  };
  TestJSClass.twice = {
    ret: ctypes.int32_t,
    args: [ctypes.int32_t],
    impl: function(self, sel, a) {
      return a * 2;
    }
  };
  TestJSClass.divide.by = {
    ret: ctypes.int32_t,
    args: [ctypes.int32_t, ctypes.int32_t],
    impl: function(self, sel, a, b) {
      return Math.floor(a / b);
    }
  };

  TestJSClass.rect = {
    ret: objc.types.NSRect,
    args: [objc.types.CGFloat, objc.types.CGFloat,
           objc.types.CGFloat, objc.types.CGFloat],
    impl: function(self, sel, x, y, width, height) {
      return objc.types.NSRect(objc.types.NSPoint(x, y),
                               objc.types.NSSize(width, height));
    }
  };

  let test = objc.classes.TestJSClass.
      alloc().
      init().
      autorelease();

  print(test.pi[objc.as.double]());
  print(test.twice[objc.as.int32_t](ctypes.int32_t(123)));
  print(test.divide.by[objc.as.int32_t](ctypes.int32_t(100),
                                        ctypes.int32_t(20)));
  print(test.rect[objc.as.NSRect](objc.types.CGFloat(12),
                                  objc.types.CGFloat(23),
                                  objc.types.CGFloat(34),
                                  objc.types.CGFloat(45)));
});

// Get mouse location.
objc.autoreleasepool(function() {
  let loc = objc.classes.NSEvent.mouseLocation[objc.as.NSPoint]();
  print(loc);
});

objc.release();