Emscripten は、makefile を構成して、ほとんどの場合、プロジェクトの現在のビルドシステムの残りの部分を変更せずに、gcc のドロップイン代替として emcc を使用する 2 つのスクリプトを提供します。
Emscripten を使用してビルドするには、makefile で gcc を emcc に置き換える必要があります。これは、CXX (C++ コンパイラー) や CC (コンパイラー) などの適切な環境変数を設定する emconfigure を使用して行います。
通常、次のコマンドでビルドする場合を考えてみましょう
./configure
make
ヒント
これらのビルドコマンドに慣れていない場合は、記事 configure、make、make install の背後にある魔法 が良い入門書になります。
Emscripten でビルドするには、代わりに次のコマンドを使用します。
# Run emconfigure with the normal configure command as an argument.
emconfigure ./configure
# Run emmake with the normal make to generate Wasm object files.
emmake make
# Compile the linked code generated by make to JavaScript + WebAssembly.
# 'project.o' should be replaced with the make output for your project, and
# you may need to rename it if it isn't something emcc recognizes
# (for example, it might have a different suffix like 'project.so' or
# 'project.so.1', or no suffix like just 'project' for an executable).
# If the project output is a library, you may need to add your 'main.c' file
# here as well.
# [-Ox] represents build optimisations (discussed in the next section).
emcc [-Ox] project.o -o project.js
emconfigure は、引数として通常の configure を指定して呼び出され (configure ベースのビルドシステムの場合)、emmake は引数として make を指定して呼び出されます。ビルドシステムで **CMake** を使用する場合は、上記の例で ./configure を cmake . などに置き換えます。ビルドシステムで configure や CMake を使用しない場合は、最初のステップを省略して make を実行するだけで済みます (ただし、その場合は Makefile を手動で編集する必要がある場合があります)。
ヒント
configure ベースおよび CMake ベースのビルドシステムでは、emconfigure スクリプトと emmake スクリプトの両方を呼び出すことをお勧めします。実際に両方のツールを呼び出す**必要がある**かどうかは、ビルドシステムによって異なります (構成ステップで環境変数を格納するシステムもあれば、そうでないシステムもあります)。
Make は Wasm オブジェクトファイルを生成します。また、オブジェクトファイルをライブラリや Wasm 実行可能ファイルにリンクすることもあります。そのようなビルドシステムが JavaScript 出力を生成するように変更されていない限り、上記のように追加の emcc コマンドを実行して、最終的な実行可能な JavaScript と WebAssembly を出力する必要があります。
注
make からのファイル出力には、異なるサフィックスが付いている場合があります。静的ライブラリアーカイブの場合は **.a**、共有ライブラリの場合は **.so**、オブジェクトファイルの場合は **.o** (これらのファイル拡張子は、gcc がさまざまなタイプに使用するものと同じです)。ファイル拡張子に関係なく、これらのファイルには、emcc が最終的な JavaScript + WebAssembly にコンパイルできる何かが含まれています (通常、内容は Wasm オブジェクトファイルになりますが、LTO を使用してビルドする場合は、LLVM ビットコードが含まれます)。
注
一部のビルドシステムでは、上記の手順で Wasm オブジェクトファイルが正しく出力されない可能性があり、is not a valid input file という警告が表示される場合があります。file を実行してファイルの内容を確認できます (また、内容が \0asm で始まる場合は Wasm オブジェクトファイルであるかどうか、BC で始まる場合は LLVM ビットコードであるかどうかを手動で確認できます)。また、emmake make VERBOSE=1 を実行すると、実行されるコマンドが出力されるため、emcc が使用されていることが確認できます。ネイティブシステムのコンパイラは使用されていません。emcc が使用されていない場合は、configure または cmake スクリプトを変更する必要がある場合があります。
特定のフラグ ( -c、-S、-r、または -shared など) を指定して実行しない限り、emcc はリンクフェーズを実行し、1 つ以上のファイルを生成できます。生成されるファイルのセットは、emcc に渡される最終フラグと、指定された出力ファイルの名前によって異なります。ここでは、どの条件下でどのファイルが生成されるかについてのチートシートを示します。
emcc ... -o output.html は、出力として output.html ファイルを構築し、それに付随する output.js ランチャーファイルと output.wasm WebAssembly ファイルも構築します。
emcc ... -o output.js は、HTML ランチャーファイルの生成を省略し (ブラウザで実行する場合は自分で提供することを想定)、output.js と output.wasm の 2 つのファイルを生成します。(例: node.js シェルで実行できます)
emcc ... -o output.wasm は、JavaScriptまたはHTMLのランチャーファイルの生成を省略し、あたかも -sSTANDALONE_WASM 設定が使用されたかのように、スタンドアロンモードでビルドされた単一のWasmファイルを生成します。生成されたファイルは、WASI ABI で実行されることを想定しています。特に、モジュールを初期化したら、_start エクスポート、または(--no-entry の場合)_initialize エクスポートのいずれかを、それ以外のことを行う前に手動で呼び出す必要があります。
emcc ... -o output.{html,js} -sWASM=0 を指定すると、コンパイラはJavaScriptをターゲットとするため、.wasm ファイルは生成されません。
emcc ... -o output.{html,js} --emit-symbol-map は、WebAssemblyがターゲットの場合(-sWASM=0 が指定されていない場合)、またはJavaScriptがターゲットで、-Os、-Oz、または-O2以上の最適化レベルが指定されているが、デバッグレベル設定が-g1以下の場合(つまり、シンボルの圧縮が行われた場合)に、ファイル output.{html,js}.symbols を生成します。
emcc ... -o output.{html,js} -gsource-map は、ソースマップファイル output.wasm.map を生成します。-sWASM=0 を指定してJavaScriptをターゲットにする場合、ファイル名は output.{html,js}.map になります。
emcc ... -o output.{html,js} --preload-file xxx ディレクティブは、プリロードされたMEMFSファイルシステムファイル output.data を生成します。
emcc ... -o output.{html,js} -sWASM={0,1} -sSINGLE_FILE は、JavaScriptとWebAssemblyコードを単一の出力ファイル output.{html,js} に(base64で)マージし、デプロイメント用に1つのファイルのみを生成します。(--preload-file と組み合わせた場合、プリロードされた .data ファイルは個別のファイルとして存在します)
このリストは網羅的なものではありませんが、最も一般的に使用される組み合わせを示しています。
注
出力ファイルの名前に関係なく、emcc は常にリンクを実行し、特定のフラグ(例えば、-c)で別の動作が指示されない限り、最終的な実行可能ファイルを生成します。これは、以前の動作とは異なります。以前は、emcc はオブジェクトファイルを結合すること(基本的に -r を想定)をデフォルトとし、特定の実行可能ファイルの拡張子(例えば、.js または .html)が与えられない限り、結合しました。
Emscripten は、コンパイラの最適化を2つのレベルで実行します。各ソースファイルは、オブジェクトファイルにコンパイルされる際に LLVM によって最適化され、その後、オブジェクトファイルを最終的なJavaScript/WebAssemblyに変換する際に JavaScript/WebAssembly 固有の最適化が適用されます。
コードを適切に最適化するには、通常、ソースをオブジェクトコードにコンパイルするときと、オブジェクトコードを JavaScript(またはHTML)にコンパイルするときに、同じ 最適化フラグ およびその他の コンパイラオプション を使用するのが最適です。
以下の例を検討してください。
# Sub-optimal - JavaScript/WebAssembly optimizations are omitted
emcc -O2 a.cpp -c -o a.o
emcc -O2 b.cpp -c -o b.o
emcc a.o b.o -o project.js
# Sub-optimal - LLVM optimizations omitted
emcc a.cpp -c -o a.o
emcc b.cpp -c -o b.o
emcc -O2 a.o b.o -o project.js
# Usually the right thing: The same options are provided at compile and link.
emcc -O2 a.cpp -c -o a.o
emcc -O2 b.cpp -c -o b.o
emcc -O2 a.o b.o -o project.js
ただし、場合によっては、特定のファイルに対してわずかに異なる最適化を適用したい場合があります。
# Optimize the first file for size, and the rest using `-O2`.
emcc -Oz a.cpp -c -o a.o
emcc -O2 b.cpp -c -o b.o
emcc -O2 a.o b.o -o project.js
注
残念ながら、各ビルドシステムは、コンパイラと最適化メソッドを設定するための独自のメカニズムを定義しています。ご使用のシステムでLLVM最適化フラグを設定するための正しいアプローチを解明する必要があります。
一部のビルドシステムには、./configure --enable-optimize のようなフラグがあります。
JavaScript/WebAssemblyの最適化は、最終ステップ(場合によっては「リンク」とも呼ばれます。このステップでは、通常、一緒にコンパイルされた多くのファイルが1つの JavaScript/WebAssembly 出力にリンクされるため)で指定されます。たとえば、-O1 でコンパイルするには、次のようにします。
# Compile the object file to JavaScript with -O1 optimizations.
emcc -O1 project.o -o project.js
デバッグ情報を含むプロジェクトをビルドするには、LLVMとJavaScriptの両方のコンパイルフェーズでデバッグフラグを指定する必要があります。
Clang および LLVM にオブジェクトファイルにデバッグ情報を出力させるには、-g を使用してソースをコンパイルする必要があります(通常、clang または gcc の場合とまったく同じです)。
注
各ビルドシステムは、デバッグフラグを設定するための独自のメカニズムを定義しています。ClangにLLVMデバッグ情報を出力させるには、ご使用のシステムに合った正しいアプローチを解明する必要があります。
一部のビルドシステムには、./configure --enable-debug のようなフラグがあります。CMake ベースのビルドシステムでは、CMAKE_BUILD_TYPE を "Debug" に設定します。
emcc に、最終的なJavaScriptおよびWebAssemblyを生成する際にオブジェクトファイルに存在するデバッグ情報を含めるには、最終的な emcc コマンドで -g または -gN デバッグレベルオプション のいずれかを指定する必要があります。
# Compile the Wasm object file to JavaScript+WebAssembly, with debug info
# -g or -gN can be used to set the debug level (N)
emcc -g project.o -o project.js
詳細については、デバッグ のトピックを参照してください。
libc、libc++、および SDL を含む、多数の標準ライブラリが組み込みでサポートされています。これらは、それらを使用するコードをコンパイルするときに自動的にリンクされます(-lSDL を追加する必要さえありませんが、SDL固有の詳細については以下を参照してください)。
プロジェクトが他のライブラリ(たとえば、zlib や glib)を使用している場合は、それらをビルドしてリンクする必要があります。通常のアプローチは、ライブラリ(オブジェクトファイル、またはそれらの.aアーカイブ)をビルドし、それらをメインプログラムとリンクして JavaScript+WebAssembly を出力することです。
たとえば、プロジェクト「project」がライブラリ「libstuff」を使用しているケースを考えてみましょう。
# Compile libstuff to libstuff.a
emconfigure ./configure
emmake make
# Compile project to project.o
emconfigure ./configure
emmake make
# Link the library and code together.
emcc project.o libstuff.a -o final.html
Emscripten ポートは、Emscripten に移植された便利なライブラリのコレクションです。それらは GitHub 上に存在し、emcc に統合サポートがあります。ポートを使用するように要求すると、emcc はリモートサーバーからポートを取得し、ローカルでセットアップしてビルドし、プロジェクトとリンクし、ビルドコマンドに必要なインクルードを追加します。たとえば、SDL2 はポートにあり、--use-port=sdl2 で使用するように要求できます。例:
emcc test/browser/test_sdl2_glshader.c --use-port=sdl2 -sLEGACY_GL_EMULATION -o sdl2.html
SDL2 が使用されており、以前にビルドされていなかった場合はビルドされていることに関する通知が表示されるはずです。その後、ブラウザで sdl2.html を表示できます。
使用可能なすべてのポートのリストを表示するには、emcc --show-ports を実行します。
注
SDL_image もポートに追加されました。--use-port=sdl2_image を使用して利用します。sdl2_image を有用にするには、通常、--use-port=sdl2_image:formats=bmp,png,xpm,jpg のように、使用する予定の画像形式を指定する必要があります。これにより、それらの形式を指定した場合に IMG_Init が適切に機能することも保証されます。または、emcc --use-preload-plugins と --preload-file を使用して画像をプリロードすることもできます。この場合、ブラウザのコーデックで画像がデコードされます(ファイルのプリロード を参照)。sdl2_image ポートのコードパスは emscripten_get_preloaded_image_data() を介してロードしますが、プリロードを通じて画像が機能しても、IMG_Init はそれらの形式のサポートがないと報告するため、それらの画像形式を使用した IMG_Init の呼び出しは失敗します(つまり、IMG_Init はプリロードでのみ機能する形式のサポートを報告しません)。
注
SDL_net もポートに追加されました。--use-port=sdl2_net を使用して利用します。
注
Emscripten は、以前のSDL1もサポートしており、これは組み込まれています。上記のコマンドのように SDL2 を指定しない場合、SDL1 がリンクされ、SDL1 インクルードパスが使用されます。SDL1 は、system/bin にある sdl-config をサポートしています。ネイティブの sdl-config を使用すると、コンパイルエラーやシンボル欠落エラーが発生する可能性があります。Emscripten の sdl-config を使用するには、ビルドシステムを修正して、**emscripten/system** または **emscripten/system/bin** でファイルを検索する必要があります。
注
必要に応じて、ポートからライブラリを手動でビルドすることもできますが、その場合は、ポートが行う Python ロジックも適用する必要があります。(tools/ports/ 下にある)そのコードは、必要な JS 関数がビルドに含まれるようにしたり、エクスポートを追加したりするなどの処理を行う場合があります。一般に、テスト済みで動作することがわかっているため、ポートバージョンを使用する方が優れています。
注
emscripten 3.1.54以降、プロジェクトでポートを使用する場合は、--use-port が推奨される構文です。従来の構文(たとえば、-sUSE_SDL2、-sUSE_SDL_IMAGE=2)も引き続き利用できます。
Contrib ポートは、より広範なコミュニティによって提供され、「最善の努力」ベースでサポートされています。それらは emscripten CI の一部として実行されないため、常にビルドや機能が保証されるとは限りません。詳細については、Contrib ポートを参照してください。
新しいポートを追加する最も簡単な方法は、contrib ディレクトリの下に配置することです。 基本的に、手順は次のとおりです。
ポートがオープンソースであり、適切なライセンスを持っていることを確認してください。
tools/ports/contribの下にあるREADME.mdファイルを読んでください。より詳しい情報が含まれています。
Emscripten は、外部ポート(ディストリビューションの一部ではないポート)もサポートしています。このようなポートを使用するには、そのパスを指定するだけです。例:--use-port=/path/to/my_port.py
注
ポートのコードを操作する場合、emscripten が使用するポート API は 100% 安定しているわけではなく、バージョン間で変更される可能性があることに注意してください。
一部の大規模プロジェクトでは、実行可能ファイルを生成し、それを実行してビルドプロセスの後半部分への入力を生成します(たとえば、パーサーをビルドしてから文法で実行し、その文法を実装する C/C++ コードを生成する場合があります)。この種のビルドプロセスは、Emscripten を使用する際に問題を引き起こします。生成しているコードを直接実行できないためです。
最も簡単な解決策は通常、プロジェクトを2回ビルドすることです。1回はネイティブに、もう1回はJavaScriptにです。生成された実行可能ファイルが存在しないためにJavaScriptビルド手順が失敗した場合は、ネイティブビルドからその実行可能ファイルをコピーし、通常どおりビルドを続行できます。たとえば、このアプローチはPythonのコンパイルにうまく使用されています(ビルド中にpgen実行可能ファイルを実行する必要があるため)。
場合によっては、生成された実行可能ファイルをネイティブにビルドするようにビルドスクリプトを変更するのが理にかなっています。たとえば、ビルドスクリプトで2つのコンパイラ、emcc と gcc を指定し、生成された実行可能ファイルのみに gcc を使用することで、これを行うことができます。ただし、プロジェクトのビルドスクリプトを変更する必要があるため、最終結果と生成された実行可能ファイルの両方でコードがコンパイルされ使用されるケースに対処する必要があるため、前の解決策よりも複雑になる可能性があります。
Emscripten の目標は、可能な限り高速で最小のコードを生成することです。そのため、可能な限り動的リンクを避け、プロジェクト全体を単一の Wasm ファイルにコンパイルすることに重点を置いています。
デフォルトでは、共有ライブラリをビルドするために -shared フラグを使用すると、Emscripten は実際には通常の .o オブジェクトファイルである .so ライブラリを生成します。(内部的には、ld -r を使用してオブジェクトを単一のより大きなオブジェクトに結合します)。これらの疑似「共有ライブラリ」がアプリケーションにリンクされると、事実上、静的ライブラリとしてリンクされます。これらの共有ライブラリをビルドする場合、Emcc はコマンドライン上の他の共有ライブラリを無視します。これは、中間ビルド段階で同じ動的ライブラリが複数回リンクされないようにするためであり、これによりシンボルの重複エラーが発生する可能性があります。
ロード時またはランタイム時(dlopen経由)に一緒にリンクできる真の動的ライブラリをビルドする方法については、実験的なサポートを参照してください。
configure、cmake、またはその他の移植可能な構成方法を使用するプロジェクトでは、構成フェーズ中にツールチェーンとパスが適切に設定されていることを確認するためにチェックが実行される場合があります。Emcc は可能な限りチェックに合格するように試みますが、「偽陰性」のために失敗するテストを無効にする必要がある場合があります(たとえば、最終的な実行環境では合格するが、configure 中のシェルでは合格しないテスト)。
ヒント
チェックが無効になっている場合は、テストされた機能が実際に機能することを確認してください。これには、ビルドシステム固有の方法を使用して、makeファイルにコマンドを手動で追加することが含まれる場合があります。
注
一般に、configure は Emscripten のようなクロスコンパイラには適していません。configure はローカル設定でネイティブにビルドするように設計されており、ネイティブビルドシステムとローカルシステムヘッダーを見つけるために懸命に機能します。クロスコンパイラでは、別のシステムをターゲットにしており、これらのヘッダーなどを無視しています。
Emscripten は、オブジェクトファイルのバンドルである .a アーカイブファイルをサポートしています。これは、ライブラリの単純な形式であり、特別なセマンティクスがあります。たとえば、.a ファイルではリンクの順序が重要ですが、プレーンなオブジェクトファイルでは重要ではありません。ほとんどの場合、これらの特別なセマンティクスは、Emscripten でも他の場所と同じように機能するはずです。
Emscriptenチュートリアルでは、emcc を使用して単一のファイルをJavaScriptにコンパイルする方法を示しました。Emcc は、gcc に期待される他のすべての方法でも使用できます。
# Generate a.out.js from C++. Can also take .ll (LLVM assembly) or .bc (LLVM bitcode) as input
emcc src.cpp
# Generate an object file called src.o.
emcc src.cpp -c
# Generate result.js containing JavaScript.
emcc src.cpp -o result.js
# Generate an object file called result.o
emcc src.cpp -c -o result.o
# Generate a.out.js from two C++ sources.
emcc src1.cpp src2.cpp
# Generate object files src1.o and src2.o
emcc src1.cpp src2.cpp -c
# Combine two object files into a.out.js
emcc src1.o src2.o
# Combine two object files into another object file (not normally needed)
emcc src1.o src2.o -r -o combined.o
# Combine two object files into library file
emar rcs libfoo.a src1.o src2.o
gcc と共有する機能に加えて、emcc はコードを最適化したり、出力するデバッグ情報を制御したり、HTMLやその他の出力形式を生成したりするためのオプションをサポートしています。これらのオプションについては、emccツールリファレンス(コマンドラインで emcc --help)に記載されています。
Emscripten は、コンパイラのバージョンとプラットフォームを識別するために使用できる次のプリプロセッサマクロを提供します。
Emscripten でプログラムをコンパイルする場合、プリプロセッサ定義
__EMSCRIPTEN__は常に定義されます。プリプロセッサ変数
__EMSCRIPTEN_major__、__EMSCRIPTEN_minor__および__EMSCRIPTEN_tiny__はemscripten/version.hで定義されており、現在使用されている Emscripten コンパイラのバージョンを整数として指定します。Emscripten は Unix のバリアントのように動作するため、プリプロセッサ定義
unix、__unixおよび__unix__は Emscripten でコードをコンパイルする際に常に存在します。Emscripten は基盤となるコード生成コンパイラとして Clang/LLVM を使用するため、プリプロセッサ定義
__llvm__と__clang__が定義され、プリプロセッサ定義__clang_major__、__clang_minor__および__clang_patchlevel__は使用されている Clang のバージョンを示します。Clang/LLVM は GCC 互換であるため、プリプロセッサ定義
__GNUC__、__GNUC_MINOR__および__GNUC_PATCHLEVEL__も定義され、Clang/LLVM が提供する GCC 互換のレベルを表します。プリプロセッサ文字列
__VERSION__は、GCC 互換バージョンを示し、Emscripten バージョン情報も表示するように拡張されます。同様に、
__clang_version__が存在し、Emscripten と LLVM の両方のバージョン情報を示します。Emscripten は 32 ビットプラットフォームであるため、
size_tは 32 ビットの符号なし整数であり、__POINTER_WIDTH__=32、__SIZEOF_LONG__=4および__LONG_MAX__は2147483647Lに等しくなります。コマンドラインコンパイラフラグ
-msse、-msse2、-msse3、-mssse3、または-msse4.1のいずれかを使用して SSEx SIMD API をターゲットとする場合、プリプロセッサフラグ__SSE__、__SSE2__、__SSE3__、__SSSE3__、__SSE4_1__の 1 つ以上が存在し、これらの命令セットの利用可能なサポートを示します。コンパイラおよびリンカフラグ
-pthreadで pthreads マルチスレッドサポートをターゲットにする場合、プリプロセッサ定義__EMSCRIPTEN_PTHREADS__が存在します。
ccache、distcc、または gomacc などの操作を行うためにコンパイララッパーを使用すると便利な場合があります。ccache の場合、コンパイラ全体をラップする通常のメソッド(例:ccache emcc)が機能するはずです。分散ビルドの場合、emscripten ドライバーをローカルで実行し、基盤となる clang コマンドのみを配布すると有益な場合があります。これが望ましい場合、構成ファイルの COMPILER_WRAPPER 設定を使用して、clang への内部呼び出しの周りにラッパーを追加できます。他の構成設定と同様に、これは環境変数を介して設定することもできます。例:
EM_COMPILER_WRAPPER=gomacc emcc -c hello.c
emconfigure と emmake は、クロスコンパイル用に pkg-config を構成し、環境変数 PKG_CONFIG_LIBDIR と PKG_CONFIG_PATH を設定します。カスタムの pkg-config パスを提供するには、環境変数 EM_PKG_CONFIG_PATH を設定します。
Emscripten テストスイート(test/runner.py)には、上記の通常のビルドシステムを使用してビルドされた多数の優れた C/C++ プロジェクトの例が含まれています。 freetype、openjpeg、zlib、bullet および poppler。
ammo.js プロジェクトのビルドスクリプトも見る価値があります。
システム の ar はオブジェクトファイルをサポートしていない可能性があるため、必ず emar (llvm-ar を呼び出す)を使用してください。emmake および emconfigure は AR 環境変数を正しく設定しますが、ビルドシステムが誤って ar をハードコードしている可能性があります。
同様に、システム の ranlib の代わりに emranlib (llvm-ranlib を呼び出す)を使用すると、オブジェクトファイルのサポートがなかったり、インデックスが削除され、wasm-ld から archive has no index; run ranlib to add one のような問題が発生する可能性があります。ここでも、emmake/emconfigure を使用すると、環境変数 RANLIB が設定されるため、この問題を回避できるはずですが、ビルドシステムがハードコードされていたり、オプションを渡す必要がある場合があります。
コンパイルエラー multiply defined symbol は、プロジェクトが特定の静的ライブラリを複数回リンクしていることを示します。プロジェクトを変更して、問題のあるライブラリが一度だけリンクされるようにする必要があります。
注
llvm-nm を使用すると、各オブジェクトファイルで定義されているシンボルを確認できます。
1つの解決策は、動的リンクを使用することです。これにより、ライブラリが最終ビルドステージで一度だけリンクされるようになります。
スタンドアロンの Wasm を生成する場合は、モジュールを使用する前に、必ず _start エクスポート、または(--no-entry の場合は)_initialize エクスポートを呼び出してください。