Concurrent Clean : ABCマシン(12)

関数オブジェクトを作るのに、関数に対して一対一でクラスを作ることを最初考えたけれど、よく考えたら、こういう形にすればよいです。

interface CleanModule {
  CleanVM dispatch(CleanVM vm, int func_id) throws CleanException;
}

class Main implements CleanModule {
  static CleanVM f0(CleanVM vm) throws CleanException { ... }
  static CleanVM f1(CleanVM vm) throws CleanException { ... }
  static CleanVM f2(CleanVM vm) throws CleanException { ... }

  static final int f0 = 0;
  static final int f1 = 1;
  static final int f2 = 2;

  static final Main it = new Main();

  CleanVM dispatch(Clean vm, int func_id) throws CleanException {
    switch (func_id) {
      case f0: return f0(vm);
      case f1: return f1(vm);
      case f2: return f2(vm);
      default: throw new CleanException();
    }
  }
}
      • -

場合わけの定数は、上に追記したようにstatic final intで記述しておけばよいです。
ABCコードでは、関数名は「e_モジュール名_s関数名」のように展開されているので、ここから「モジュール名 = クラス名」となるように対応付けて変換してやることができます。

      • -

いま考えているのは、末尾呼び出しのjmp命令をどう取り扱えばよいのかということです。stackをつまないでjmpするということがJVMで素直に書くことができない。
CleanVMクラスにメソッドを作って、関数呼び出しを間接呼び出しにすればできるのですが、それだと多少オーバーヘッドを食うので、もっと効率のよい方法はないかと思っているのですが・・・
今のところ、次のようなコードなら、ある程度オーバーヘッドを抑えることができるのではないかと思っています。

class CleanVM {
  boolean tailcall = false;
  CleanModule tailcallmodl = null;
  int tailcallfunc = 0;

  CleanVM tailcall(CleanVM vm) {
    while (tailcall) {
      tailcall = false;
      tailcallmodl.dispatch(vm, tailcallfunc);
    }
  }

のようなフィールドを用意して、末尾呼び出しのこーどを次のように書く。

  vm.tailcall = true;
  vm.tailcallmodl = NextModule.it;
  vm.tailcallfunc = NextModule.nextfunc;
  return vm;
}

関数の呼び出し側では、次のようなコードを埋め込んでおく。

  vm = SomeModule.somefunc(vm);
  if (vm.tailcall) { vm = vm.tailcall(vm);}
      • -

あるいは、これなら、vmを関数型に引き継がないで、副作用を使うことで、

interface CleanModule {
  boolean dispatch(CleanVM vm, int func_id) throws CleanException;
}

として、関数の返値に末尾呼び出しがあるかどうかのフラグを立ててやるほうが効率的かも。呼び出しはこうなります。

  if (SomeModule.somefunc(vm)) { vm.tailcall(vm);}
      • -

よく考えたら、tailcallメソッドを用意するまでもない。これで十分。

  if (SomeModule.somefunc(vm)) { while(vm.tailcallmodl.dispatch(vm, vm.tailcallfunc)); }