monolithic kernel

HSP3ソースコンバーターを試してみた

HSP3のコンパイル結果(.axファイル)から他の言語のソースコードを生成できるHSP3ソースコンバーターを試してみました。バージョン0.3時点ではC++を対象として開発が進められているようです。

更新履歴を見た感じかなり前から開発が進められていたようで、現時点でも普通に動いてます。

動作確認

適当にサンプルを書いて変換してみました。以下が元のソースコードです。

#cmpopt varname 1

v = 0
button "TEST", *on_button
*main
  if v == 10 {
    end
  }
  stop

*on_button
  gosub *sub
  goto *main

*sub
  mes "Hello, World! : " + v
  v++
  return

こちらが変換後のコードです。みてのとおり人間が読み書きするようなコードにはなっておらず、あくまでC++のコンパイラを活用するためのものという感じですね。まぁHSPで書いたコードがC++のコードになってもさほどうれしくないでしょうから、妥当な仕様だとは思います。

////hsp3cnv generated source//[c:\users\mono\desktop\cnvtest\start.ax]//#include "hsp3r.h"
#define _HSP3CNV_DATE "2011/03/07"
#define _HSP3CNV_TIME "01:42:24"
#define _HSP3CNV_MAXVAR 1
#define _HSP3CNV_MAXHPI 0
#define _HSP3CNV_VERSION 0x301
#define _HSP3CNV_BOOTOPT 0

/*-----------------------------------------------------------*/
static PVal *Var_v;

/*-----------------------------------------------------------*/
void __HspEntry( void ) {
    // Var initialize
    Var_v = &mem_var[0];

    // v = 0
    PushInt(0);
    VarSet(Var_v, 0);

    // "TEST", *L0000
    PushLabel(0);
    PushStr("TEST");
    Extcmd(0, 2);
    TaskSwitch(1);
}

static void L0001( void ) {
    // if v=10
    PushVar(Var_v, 0);
    PushInt(10);
    CalcEqI();
    if (HspIf()) {
        TaskSwitch(5);
        return;
    }
    // end
    Prgcmd(16, 0);
    return;
    TaskSwitch(5);
}

static void L0005( void ) {
    TaskSwitch(2);
}

static void L0002( void ) {
    // stop
    Prgcmd(17, 0);
    return;
    // goto *L0002
    TaskSwitch(2);
    return;
    TaskSwitch(0);
}

static void L0000( void ) {
    // gosub
    PushLabel(3);
    PushLabel(6);
    Prgcmd(1, 2);
    return;
}

static void L0006( void ) {
    // goto *L0001
    TaskSwitch(1);
    return;
    TaskSwitch(3);
}

static void L0003( void ) {
    // mes "Hello, World! : "+v
    PushStr("Hello, World! : ");
    PushVar(Var_v, 0);
    CalcAddI();
    Extcmd(15, 1);

    // v ++
    VarInc(Var_v, 0);

    // return
    Prgcmd(2, 0);
    return;
    TaskSwitch(4);
}

static void L0004( void ) {
    // stop
    Prgcmd(17, 0);
    return;

    // goto
    Prgcmd(0, 0);
    return;
}

//-End of Source-------------------------------------------
CHSP3_TASK __HspTaskFunc[] = {
    (CHSP3_TASK) L0000,
    (CHSP3_TASK) L0001,
    (CHSP3_TASK) L0002,
    (CHSP3_TASK) L0003,
    (CHSP3_TASK) L0004,
    (CHSP3_TASK) L0005,
    (CHSP3_TASK) L0006,
};

/*-----------------------------------------------------------*/

void __HspInit( Hsp3r *hsp3 ) {
    hsp3->Reset( _HSP3CNV_MAXVAR, _HSP3CNV_MAXHPI );
    hsp3->SetDataName( 0 );
    hsp3->SetFInfo( 0, 0 );
    hsp3->SetLInfo( 0, 0 );
    hsp3->SetMInfo( 0, 0 );
}
/*-----------------------------------------------------------*/

Push、Popが多数あるのでどうやらスタックマシンっぽいですね。HSP本体と実装を共有することを考えてaxファイルの内部命令を対応するC++のコードにそのまま置き換えているのかな、という感じがします。

gotoはC++のものを使わずにラベルごとに関数を分けて状態変数でジャンプしているようです。理由として考えられるのは、ラベルがgoto用なのかgosub用なのかを判断するのは困難なため、gotoのない言語への移植を容易にするため、あたりでしょうか。

ベンチマーク

実行速度が2倍から5倍になるとのことだったので、ベンチマークも行ってみました。コンパイルした環境はVisual Studio 2010で、測定にはCore i7 920を搭載したマシンを利用しました。

#cmpopt varname 1
#uselib "winmm"
#cfunc timeGetTime "timeGetTime"
#module
#defcfunc fib int n
  if (n & 0xfffffffe) == 0 {
    return 1
  }
  return fib(n - 2) + fib(n - 1)
#global

t = timeGetTime()
repeat 10
  x += fib(30)
loop
mes timeGetTime() - t

結果はHSP版8215msに対してC++版は3066msで、およそ2.7倍の速度になっているようです。ネイティブコードが生成されるというともっと大幅に高速化されそうなイメージがありますが、すべての演算がランタイムの関数で行われるという仕組みになっているため、劇的な変化は起きないようです。

おわりに

HSP to Cコンバータの話が持ち上がった頃は、高速化を目的としていたような覚えがあるのですが、コンピュータが十分に高速になった今、実行速度が問題になることはほとんどないように思います。もちろん高速化はうれしいことではあるのですが、個人的にはそれ以上におにたま氏がもう1つの目的として挙げているHSPのコードを変換して他のさまざまな環境・デバイス上で動作させられるようにするという方に惹かれました。他のコンパイラやプラットフォームを活用するためのツールとしてのHSPという方向性はかなり面白いと思います。そんなわけで、HSPの可能性を大きく広げるHSPソースコンバーターから目が離せませんね!