コードのasm.js対応確認方法とその1例

いちごパック > asm.jsの解説 > コードのasm.js対応確認方法とその1例

確認の手順

asm.jsでは、最適化関数を実装したつもりでも、 1か所でもミスがあると最適化されません。 Javascriptとしてコンパイルエラーになれば動作しませんが、 そうでない場合は通常のJavascriptとして動作します。 したがって、asm.js対応コードを書いた場合は正しく最適化されるか確認しておくと良いでしょう。
最適化されているか確認するには、 まずWebブラウザとしてMozilla Firefoxをインストールします。
Firefoxを起動したら、ツールバーにあるメニューボタンでメニューを開き、 「開発ツール」を選ぶと「ウェブ開発」の項目一覧が出てきますので、 「ウェブコンソール」を選びます(あるいは「デバッガ」を選んで「コンソール」タブを開きます)。
Javascriptのソースコードがasm.jsとして正しい場合、「Successfully compiled」と表示されます。 asm.jsとして正しくないコードで"use asm";が見つかった場合は、行番号とエラーが出てきます。 その場合は「Successfully compiled」になるまで、 Firefoxのウェブコンソールに表示されたエラーを見ながら修正していくと良いでしょう。

関数の最適化とそのエラー対策の1例

asm.jsに対応していない整数値配列の合計値を求める関数について、 asm.jsに対応させる手順を示しながら、 ウェブコンソールに表示されたエラーの解決方法について説明します。

最適化したい関数

今回扱うJavascriptは、次のhtmlファイルとします。 Javascript部分以外に最低限のhtmlを与えていますので、 Firefox(やその他のブラウザ)で表示できます。
<html><head></head><body>result: <div id="result">XXX</div>
<script type="text/javascript">

function int32sum(int32memory,ichigobegin,ichigoend) {
  var ichigoloop = 0;
  var ichigoret = 0;

  for ( ichigoloop = ichigobegin; ichigoloop < ichigoend; ichigoloop++ ) {
    ichigoret += int32memory[ichigoloop];
  }
  return ichigoret;
}

var int32memory = new Int32Array(100);
var int32begin = 0;
var int32end = 100;
for ( var ichigoloop = int32begin;
      ichigoloop < int32end; ichigoloop++ ) {
   int32memory[ichigoloop] = ichigoloop+1;
}

var str = int32sum(int32memory,0,100);
document.getElementById("result").innerHTML = str;

</script></body></html>
メモリに1〜100を書いてそれらの合計を求めていますので、結果は5050になります。 この時点では、asm.jsには全く対応していません。

最適化関数の包み込み

はじめに、 関数の書き方で説明した形になるように、 最適化したい関数を3つの引数を持つ関数で包みます。
<html><head></head><body>result: <div id="result">XXX</div>
<script type="text/javascript">

function buildScript(libs,extfuncs,memory) {
  "use asm";

  function int32sum(int32memory,ichigobegin,ichigoend) {
    var ichigoloop = 0;
    var ichigoret = 0;

    for ( ichigoloop = ichigobegin; ichigoloop < ichigoend; ichigoloop++ ) {
      ichigoret += int32memory[ichigoloop];
    }
    return ichigoret;
  }

  return { int32sum: int32sum };
}


var int32memory = new Int32Array(100);
var int32begin = 0;
var int32end = 100;
for ( var ichigoloop = int32begin;
      ichigoloop < int32end; ichigoloop++ ) {
   int32memory[ichigoloop] = ichigoloop+1;
}

var asmcodes = buildScript(self,null,null);
var str = asmcodes.int32sum(int32memory,0,100);
document.getElementById("result").innerHTML = str;

</script></body></html>
"use asm";を指定することで、asm.js対応のコードとして扱われるようになります。

引数の型指定

先のコードをFirefoxで動かすと、ウェブコンソールには次のエラーが表示されました。
TypeError: asm.js type error: expecting argument type
declaration for 'int32memory' of the form 'arg = arg|0'
or 'arg = +arg' or 'arg = fround(arg)'
1 portasm_2.html:8:4
asm.jsでは、関数の書き方で説明したように、 引数の型はint('arg = arg|0')、float('arg = fround(arg)')、double('arg = +arg')のいずれかと決められており、 メモリ配列はとることができません。 このメモリ配列は、外側のメモリ引数として与える必要があります。
<html><head></head><body>result: <div id="result">XXX</div>
<script type="text/javascript">

function buildScript(libs,extfuncs,int32memory) {
  "use asm";

  function int32sum(ichigobegin,ichigoend) {
    var ichigoloop = 0;
    var ichigoret = 0;

    for ( ichigoloop = ichigobegin; ichigoloop < ichigoend; ichigoloop++ ) {
      ichigoret += int32memory[ichigoloop];
    }
    return ichigoret;
  }

  return { int32sum: int32sum };
}


var int32memory = new Int32Array(100);
var int32begin = 0;
var int32end = 100;
for ( var ichigoloop = int32begin;
      ichigoloop < int32end; ichigoloop++ ) {
   int32memory[ichigoloop] = ichigoloop+1;
}

var asmcodes = buildScript(self,null,int32memory);
var str = asmcodes.int32sum(0,100);
document.getElementById("result").innerHTML = str;

</script></body></html>
このコードをFirefoxで動かすと、ウェブコンソールには次のエラーが表示されました。
TypeError: asm.js type error: expecting argument type declaration
for 'ichigobegin' of the form 'arg +AD0- arg+AHw-0' or 'arg +AD0- +-arg' or
'arg +AD0- fround(arg)'
1 portasm+AF8-3.html:8:4
残りの引数は整数のつもりですが、こちらも型を指定する必要があります。
<html><head></head><body>result: <div id="result">XXX</div>
<script type="text/javascript">

function buildScript(libs,extfuncs,int32memory) {
  "use asm";

  function int32sum(ichigobegin,ichigoend) {
    ichigobegin = ichigobegin|0;
    ichigoend = ichigoend|0;
    var ichigoloop = 0;
    var ichigoret = 0;

    for ( ichigoloop = ichigobegin; ichigoloop < ichigoend; ichigoloop++ ) {
      ichigoret += int32memory[ichigoloop];
    }
    return ichigoret;
  }

  return { int32sum: int32sum };
}


var int32memory = new Int32Array(100);
var int32begin = 0;
var int32end = 100;
for ( var ichigoloop = int32begin;
      ichigoloop < int32end; ichigoloop++ ) {
   int32memory[ichigoloop] = ichigoloop+1;
}

var asmcodes = buildScript(self,null,int32memory);
var str = asmcodes.int32sum(0,100);
document.getElementById("result").innerHTML = str;

</script></body></html>

引数の型指定

先のコードをFirefoxで動かすと、ウェブコンソールには次のエラーが表示されました。
TypeError: asm.js type error: arguments to a comparison must both
be signed, unsigned, floats or doubles+ADs- int and int are given
1 portasm+AF8-4.html:13:36
このエラーは、
    for ( ichigoloop = ichigobegin; ichigoloop < ichigoend; ichigoloop++ ) {
この部分で、(数値)<(数値)の比較演算はsigned、unsigned、float、doubleのいずれかを与える必要がありますが、 intとintが与えられているためエラーとなっています。 このコードは、例えば次の形に修正できます。
    for ( ichigoloop = ichigobegin; (ichigoloop|0) < (ichigoend|0); ichigoloop++ ) {

未対応の式

先のコードをFirefoxで動かすと、ウェブコンソールには次のエラーが表示されました。
TypeError: asm.js type error: unsupported expression
1 portasm+AF8-5.html:14:6
次のコードで、asm.jsが対応していない式(expression)が見つかったためエラーになっています。
      ichigoret += int32memory[ichigoloop];
この場合、asm.jsが対応していない式とは、+=を使った計算部分です。 この部分は、次のように書き直せます。
      ichigoret = ichigoret + int32memory[ichigoloop];

配列へのアクセス

先のコードをFirefoxで動かすと、ウェブコンソールには次のエラーが表示されました。
TypeError: asm.js type error: base of array access must be
a typed array view name
1 portasm_6.html:14:30
次のコードで、配列が型つきビューでないというエラーになっています。
      ichigoret = ichigoret + int32memory[ichigoloop];
わかりにくいエラーですが、包み込み関数の第3引数にArrayBufferを与え、 その型つきビューを確保するというルールに従わなかったなかった場合のエラーメッセージのようです。 対策としては、全体をArrayBufferとそのビューを確保する形で修正します。 型つきビューInt32Arrayへの参照は、libsとして引数に渡したものを使って生成します。
<html><head></head><body>result: <div id="result">XXX</div>
<script type="text/javascript">

function buildScript(libs,extfuncs,heapmemory) {
  "use asm";
  var int32memory = new libs.Int32Array(heapmemory);

  function int32sum(ichigobegin,ichigoend) {
    ichigobegin = ichigobegin|0;
    ichigoend = ichigoend|0;
    var ichigoloop = 0;
    var ichigoret = 0;

    for ( ichigoloop = ichigobegin; (ichigoloop|0) < (ichigoend|0); ichigoloop++ ) {
      ichigoret = ichigoret + int32memory[ichigoloop];
    }
    return ichigoret;
  }

  return { int32sum: int32sum };
}


var heapmemory = new ArrayBuffer(100*4);
var int32memory = new Int32Array(heapmemory);
var int32begin = 0;
var int32end = 100;
for ( var ichigoloop = int32begin;
      ichigoloop < int32end; ichigoloop++ ) {
   int32memory[ichigoloop] = ichigoloop+1;
}

var asmcodes = buildScript(self,null,heapmemory);
var str = asmcodes.int32sum(0,100);
document.getElementById("result").innerHTML = str;

</script></body></html>
このコードをFirefoxで動かすと、ウェブコンソールには次のエラーが表示されました。
TypeError: asm.js type error: index expression isn't shifted;
must be an Int8/Uint8 access
1 portasm_7.html:15:42
このエラーは、データがInt8ArrayやUint8Array以外の場合は、 次の部分で、配列のインデックスが右ビットシフトする必要があるというエラーです。
      ichigoret = ichigoret + int32memory[ichigoloop];
動かすだけであれば、2ビット左シフトしてから2ビット右シフトするように書き換えれば良いです。 計算時に|0を入れないと場合はエラーになりますので、 その場合はエラーメッセージにしたがって入れてください。
      ichigoret = ichigoret + int32memory[(((ichigoloop|0)<<2)|0) >> 2];
なお、ichigoloopの値として4倍の値を使うと、左シフトをしなくてすみます。 より速いコードを生成したい場合は、左シフトをしないように書き換えると良いでしょう。

演算引数の整数化

先のコードをFirefoxで動かすと、ウェブコンソールには次のエラーが表示されました。
TypeError: asm.js type error: operands to + or - must both be int,
float? or double?, got int and intish
1 portasm_8.html:15:18
数値演算とライブラリ関数で説明したように、 (数値)+(数値)や(数値)-(数値)の演算は、int、float?、double?のいずれかの型である必要があります。 次の行で、引数としてintとintishが与えられているためエラーになっています。
      ichigoret = ichigoret + int32memory[(((ichigoloop|0)<<2)|0) >> 2];
intishは配列アクセスによって生成されていますので、|0を使ってこの部分をint化すれば良いことになります。
      ichigoret = ichigoret + (int32memory[(((ichigoloop|0)<<2)|0) >> 2]|0);
このコードをFirefoxで動かすと、ウェブコンソールには次のエラーが表示されました。
TypeError: asm.js type error: intish is not a subtype of int
1 portasm_9.html:15:6
今修正したばかりの次のコードですが、intとintの足し算によってintish型の数値になり、 そのintish型の数値をintに代入しようとしているためエラーになっています。
      ichigoret = ichigoret + (int32memory[(((ichigoloop|0)<<2)|0) >> 2]|0);
こちらも同様に修正できます。
      ichigoret = (ichigoret + (int32memory[(((ichigoloop|0)<<2)|0) >> 2]|0))|0;

未対応の式再び

先のコードをFirefoxで動かすと、ウェブコンソールには次のエラーが表示されました。
TypeError: asm.js type error: unsupported expression
1 portasm_10.html:14:68
エラーが出ているのはこの部分です。
    for ( ichigoloop = ichigobegin; (ichigoloop|0) < (ichigoend|0); ichigoloop++ ) {
最後の++でエラーになっていることがわかりますので、次のように修正します。
    for ( ichigoloop = ichigobegin; (ichigoloop|0) < (ichigoend|0);
          ichigoloop = (ichigoloop+1)|0 ) {
全体のコードは次のようになります。
<html><head></head><body>result: <div id="result">XXX</div>
<script type="text/javascript">

function buildScript(libs,extfuncs,heapmemory) {
  "use asm";
  var int32memory = new libs.Int32Array(heapmemory);

  function int32sum(ichigobegin,ichigoend) {
    ichigobegin = ichigobegin|0;
    ichigoend = ichigoend|0;
    var ichigoloop = 0;
    var ichigoret = 0;

    for ( ichigoloop = ichigobegin; (ichigoloop|0) < (ichigoend|0);
          ichigoloop = (ichigoloop+1)|0 ) {
      ichigoret = (ichigoret + (int32memory[(((ichigoloop|0)<<2)|0) >> 2]|0))|0;
    }
    return ichigoret;
  }

  return { int32sum: int32sum };
}


var heapmemory = new ArrayBuffer(100*4);
var int32memory = new Int32Array(heapmemory);
var int32begin = 0;
var int32end = 100;
for ( var ichigoloop = int32begin;
      ichigoloop < int32end; ichigoloop++ ) {
   int32memory[ichigoloop] = ichigoloop+1;
}

var asmcodes = buildScript(self,null,heapmemory);
var str = asmcodes.int32sum(0,100);
document.getElementById("result").innerHTML = str;

</script></body></html>

戻り値の整数化

先のコードをFirefoxで動かすと、ウェブコンソールには次のエラーが表示されました。
TypeError: asm.js type error: int is not a valid return type
1 portasm_11.html:18:11
エラーが出ているのはこの部分で、int型を返そうとしているためエラーになっています。
    return ichigoret;
この場合は、強制的にsignedにすると良いでしょう。
    return (ichigoret|0);

呼び出しヒープサイズの調整

先のコードをFirefoxで動かすと、ウェブコンソールには次のエラーが表示されました。
Successfully compiled asm.js code (total compilation time 7ms)
1 portasm_12.html
TypeError: asm.js link error: ArrayBuffer byteLength 0x190 is
not a valid heap length. The next valid length is 0x10000
1 portasm_12.html:34:16
asm.js部分のコードとしては正しいため「Successfully compiled」と表示されていますが、 次の行でArrayBufferのバイト数がasm.jsの期待するサイズではないためエラーになっています。
var asmcodes = buildScript(self,null,heapmemory);
関数の書き方で説明したように、 引数として与えることができるArrayBufferのバイト数には強い制限があります。 メモリとしてArrayBuffer(100*4)を確保していますが、 必要なメモリサイズ以上で、この制限を満たすバイト数である0x10000に修正すれば、 asm.jsとして動く完全なコードができます。
最終版のコードは次の形になります。
<html><head></head><body>result: <div id="result">XXX</div>
<script type="text/javascript">

function buildScript(libs,extfuncs,heapmemory) {
  "use asm";
  var int32memory = new libs.Int32Array(heapmemory);

  function int32sum(ichigobegin,ichigoend) {
    ichigobegin = ichigobegin|0;
    ichigoend = ichigoend|0;
    var ichigoloop = 0;
    var ichigoret = 0;

    for ( ichigoloop = ichigobegin; (ichigoloop|0) < (ichigoend|0);
          ichigoloop = (ichigoloop+1)|0 ) {
      ichigoret = (ichigoret + (int32memory[(((ichigoloop|0)<<2)|0) >> 2]|0))|0;
    }
    return (ichigoret|0);
  }

  return { int32sum: int32sum };
}


var heapmemory = new ArrayBuffer(0x10000);
var int32memory = new Int32Array(heapmemory);
var int32begin = 0;
var int32end = 100;
for ( var ichigoloop = int32begin;
      ichigoloop < int32end; ichigoloop++ ) {
   int32memory[ichigoloop] = ichigoloop+1;
}

var asmcodes = buildScript(self,null,heapmemory);
var str = asmcodes.int32sum(0,100);
document.getElementById("result").innerHTML = str;

</script></body></html>
このコードをFirefoxで動かすと、 ウェブコンソールには次の通り成功メッセージが表示されました。
Successfully compiled asm.js code (total compilation time 7ms)
1 portasm_13.html