日別アーカイブ: 2014年5月23日

Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (5)

● JSライブラリ

JavaScript で書かれたライブラリと直接リンクすることができます。

// lib.js
mergeInto( LibraryManager.library, {
    print: function(x){
        console.log( x );
    }
});
// main.cpp
extern "C" {
    void print( int x );
}

int main()
{
    print( 12345 );	// これ
    return  0;
}

ビルド手順

emcc main.cpp --js-library lib.js -o main.html

C言語のリンケージに含まれるので、extern 宣言するだけで呼び出せます。

Emscription の library*.js を見ると、標準で用意された関数も
JavaScript で書かれていることがわかります。

利点としては Native な JavaScript の方が自由度が高いことが挙げられます。
ただ C/C++ で書いた場合は asm.js 対応コードに変換してくれるため、
どちらが効率がよく動作できるのかは処理内容に依存すると思われます。

● 引数

// lib.js
mergeInto( LibraryManager.library, {
    print_char : function(x){
        console.log( x );
    },
    print_short : function(x){
        console.log( x );
    },
    print_int : function(x){
        console.log( x );
    },
    print_float : function(x){
        console.log( x );
    },
    print_double : function(x){
        console.log( x );
    },
    print_llong : function(x,y){
        console.log( y * 4294967296 + x );
    },
    print_string : function(x){
        console.log( Pointer_stringify(x) );
    }
});
// main.cpp
extern "C" {
    void print_char( char x );
    void print_short( short x );
    void print_int( int x );
    void print_float( float x );
    void print_double( double x );
    void print_llong( long long x );
    void print_string( const char* x );
};

int main()
{
    print_char( 123 );
    print_short( 12345 );
    print_int( 123456789 );
    print_float( 1.2345f );
    print_double( 1.2345678901234 );
    print_llong( 12345678901234ll );
    print_string( "ABCDEFG" );
    return  0;
}
// output
123
12345
123456789
1.2345000505447388
1.2345678901234
12345678901234
ABCDEFG

ポインタを受け取る場合はメモリアクセスが必要です。
型に応じて HEAP8,HEAP16,HEAP32,HEAP64 を用いるか
setValue()/getValue() 命令を使うことができます。
文字列の Object 変換は Pointer_stringify() で可能。

double に収まらない long long (64bit int) 型は、
2個の整数 (low, high) に分解されていることもわかります。

● 戻り値

// lib.js
mergeInto( LibraryManager.library, {
    ret_llong : function(x,y){
	asm.setTempRet0( 1 );	// high
    	return	0>>>0;	// low
    },
});
// main.cpp
#include 

extern "C" {
    long long ret_llong( long long x );
}

int main()
{
    printf( "%lld\n", ret_llong( 0 ) );
    return  0;
}
// output
4294967296

64bit 型は global な戻り値レジスタ tempRet0~ を併用しています。
他の関数呼び出しで破壊される可能性があるので atomic ではない点に注意。

文字列定数など static なポインタを返すのは簡単ではないようです。
メインメモリ空間に配置されていなければならないためで、
static な data 領域はコンパイル時に作られます。
ライブラリ初期化時に HEAP から確保して代用する形になるでしょう。

C言語に返すことが可能な heap memory の確保は、
Module._malloc() / Module._free() を使用します。
C言語の runtime に含まれるので、コンパイル時にこれらの関数への参照がなければ
正しいコードが生成されない点に注意が必要です。

// lib.js
mergeInto( LibraryManager.library, {
    alloc_int : function(size){
    	return	Module._malloc( size * 4 );
    },
    dump_int : function(addr, size){
        for( i= 0 ; i< size ; i++ ){
	    console.log( Module.HEAP32[(addr>>2) + i] ); // memory アクセス
        }
    },
});
// main.cpp
#include 

extern "C" {
    int*  alloc_int( int size );
    void  dump_int( const int* buffer, int size );
}

int main()
{
    int*  buffer= alloc_int( 100 );
    for( int i= 0 ; i< 100 ; i++ ){
        buffer[i]= i * i;
    }
    dump_int( buffer, 100 );
    free( buffer );   // -- (1)
    return  0;
}

↑(1) の free が無ければ C言語で malloc()/free() が生成されないため、
JavaScript 側の Module._malloc() は正しい挙動になりません。

例えば下記のようにアロケータを完全に置き換えようとしても、
C言語から参照がないので _malloc/_free がダミーコードになります。
一見正しく動作しますがメモリリークします。

// lib.js
mergeInto( LibraryManager.library, {
    my_alloc : function(byte_size){
    	return	Module._malloc( byte_size );
    },
    my_free : function(addr){
    	Module._free( addr );
    },
});
// main.cpp
extern "C" {
    void* my_alloc( int byte_size );
    void  my_free( void* addr );
}

int main()
{
    int*  buffer= (int*)my_alloc( 100 * sizeof(int) );
    for( int i= 0 ; i< 100 ; i++ ){
        buffer[i]= i * i;
    }
    my_free( buffer );
    return  0;
}

● メモリマップ

0~7               NULL trap 領域
STATIC_BASE~      data/bss segument (8~)
STACK_BASE~       stack 領域
DYNAMIC_BASE~     heap 領域
~TOTAL_MEMORY-1

NULL アクセスはコンパイラが検出できる場合だけ例外が発生します。
動的な NULL アクセスはデフォルトではエラーになりませんが、
-s SAFE_HEAP=1 を付けることでメモリアクセス時のアドレスチェックが可能です。
その分動作速度は低下します。

stack は前方から後方に向かって確保されています。

文字列定数や static/global に確保したメモリは STATIC エリアに格納します。
初期値を持たない配列も含まれるため、下記のようなケースでは初期化データに
大量の 0 が並ぶことになります。
ファイルサイズが増加するので、HEAP から確保した方が無駄が少なく済むようです。

// main.cpp
int buffer[1024];

/* memory initializer */ allocate([0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
~

次回: Emscripten C++/OpenGL ES 2.0 (6) Chrome の速度と IE11/Safari

関連エントリ
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (4)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (3)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (2)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす (1)
Emscripten C++/OpenGL ES 2.0 のアプリケーションをブラウザで動かす 一覧