-
Notifications
You must be signed in to change notification settings - Fork 0
Getting started
- Get LLVM and Clang, version 3.0
- Get a JavaScript shell, either node.js (0.5.5 is known to work, earlier versions may lack typed arrays) or the SpiderMonkey or V8 shells (a recent trunk version of either of the last two)
- Run the emscripten test runner,
python tests/runner.py
. On first run it will create~/.emscripten
. Edit that so the paths are to the right places - Run the test runner and see that the tests pass. Or run just an individual test with
python tests/runner.py test_hello_world
Emscripten is developed and tested on Linux, but should work on every platform where you can run a JS engine, LLVM, and Python.
You will need to get and build LLVM and Clang. You should really read the official Clang installation guide, but here is a short sketch:
- Get LLVM 3.0. Other versions may not work!
- Get Clang 3.0 and place it in your LLVM directory under
tools/clang
(note: notclang-3.0
or anything like that - simplyclang
). - Make a
cbuild/
directory inside the LLVM (not Clang) directory - In
cbuild/
, runcmake ..
(or../configure
) and thenmake
. This will build LLVM and Clang.
As of LLVM 3.0, llvm-gcc is deprecated, so Emscripten doesn't officially support it either. It might still work, or it might not.
Next you need a JavaScript shell environment. You can use either node.js, or the console shell of SpiderMonkey (building guide) or V8 (building guide, remember to do scons d8
to get the D8 shell built).
Not all versions of these may work. See version number comments at the top of this document in the tl;dr section.
To run all the tests and benchmarks, you will need both node.js and the SpiderMonkey shell. However, one of the two is enough to get started. (The v8 shell is somewhat limited here due to v8 issue 1822, so it isn't recommended.)
- In the top directory, run
python tests/runner.py
. That will run all the tests. They should all pass. This will take a long time - we have a lot of tests! - Note that the first time you run the tests (or emscripten.py), it will create a config file,
~/.emscripten
. Its initial values are taken fromsettings.py
. You should edit the paths in~/.emscripten
to those relevant for you. - You can do
python tests/runner.py test_X
whereX
is one of the tests inrunner.py
(for example,intvars
orfannkuch
). This will run that single test. If you want to view the generated code, doEM_SAVE_DIR=1 python tests/runner.py test_X
, and then you can look inside the temp directory (TEMP_DIR/tmp
, whereTEMP_DIR
is defined in~/.emscripten
), specifically atsrc.cpp.o.js
. The other files in that directory can also be useful to figure out what went wrong. - If you get errors on all the tests, there is probably something wrong in the config file that is mentioned above. To debug that, run a single test, say
python tests/runner.py test_hello_world
, and look at the output files in the temp directory as mentioned above.
The are two phases to compiling C/C++ (or something else) to JS:
- C/C++ => LLVM bitcode
- LLVM bitcode => JS
We have scripts to help with both:
- C/C++ => LLVM bitcode: emmaken.py wraps around clang (the LLVM compiler), and gets it to generate LLVM bitcode from your project.
- LLVM bitcode => JS: emscripten.py transforms LLVM bitcode into JS (writing to stdout).
In more detail, the stages are:
- C/C++ => LLVM bitcode: This stage is just about compiling the source code to LLVM bitcode. No optimization is done at this stage.
- LLVM bitcode => JS: Optionally, some LLVM optimizations can be done. Then Emscripten compiles the LLVM bitcode to JavaScript, optionally optimizing it. Typically you will then further optimize it afterwards using the Emscripten eliminator or Closure Compiler.
Now let's go back to the beginning and see what the actual commands would look like. For the first stage, emmaken.py is in tools/. You can either use it just like you would use clang or gcc, for example
python tools/emmaken.py myfile.cpp -o myfile.bc
will compile myfile.cpp to LLVM bitcode in myfile.bc. You can also tell your project to use emmaken.py instead of gcc/ld/ranlib/ar, and then build your project normally using configure/make (see docs in emmaken.py). Note that emmaken.py will tell clang to use the emscripten bundled headers, not your system headers, so that compilation is portable.
After you have LLVM bitcode, you can use emscripten.py to compile it to JS, for example with
python emscripten.py myfile.bc > myfile.js
Note that by default this is unoptimized code! To optimize it, see Optimizing-Code. For more advanced code generation options see Code Generation Modes.
You can then run your code using a JS engine shell, for example one of
node myfile.js arg1 arg2
js -m -n myfile.js arg1 arg2
d8 myfile.js -- arg1 arg2
Notes:
-
For building large projects, see Building Projects.
-
If you have issues with compiling or with the generated code, see the Debugging page.
-
As just mentioned, Emscripten is meant to be used with the included headers, since what we are talking about here is basically cross-compiling, that is, building for one platform ("javascript/the web") on another (whatever OS you are on). The included headers are a complete environment for building, which allows you to compile code for purposes of Emscripten from any OS (this is similar to say the Android NDK: it ships with its own header files, so building for Android can be done from any OS). So it shouldn't matter what local header files you have, and so forth - you can build on Linux, OS X or Windows, and the results should be the same. (Note that the headers are fairly similar to a *NIX-type environment, because that was easiest to do.) Likewise, you should not compile with flags like
-DWIN32
or other platform-specific definitions, as mentioned before, we are basically cross-compiling here. (Note that you might be able to use those flags, and to use local headers, but you will likely need to modify Emscripten or its library to do that - so this is not recommended.) You should also be careful about -I includes to local system locations, something like -I/usr/include can end up overriding the bundled headers, and causing compilation to fail. -
You can also run clang directly, instead of with emmaken. In this case, you should run it with
-emit-llvm
. You should also use the-g
flag when compiling to preserving symbols, since Emscripten uses these to calculate struct field offsets. After that, to generate human-readable assembly (.ll) from a .bc file, you should translate that to .ll usingllvm-dis
with the-show-annotations
option (emscripten.py will do this for you if necessary). Note that the automatic test runner,tests/runner.py
, does all of this during the tests, so reading what it does can be a useful way to learn. -
Note that Emscripten expects the output to be formatted and annotated the way
llvm-dis -show-annotations
does. So if you get .ll output from something else, you should run it throughllvm-as
(to generate LLVM bitcode) and thenllvm-dis -show-annotations
(to get properly formatted and annotated .ll).
You can interact in various ways with the compiled code:
- Functions in the original source become JS functions, so you can call them. C++ functions will be name-mangled, though - C functions are much more convenient. If you want to call functions, consider defining them with
extern "C" { .. }
in your C++ code. - The types of the parameters you pass to functions need to make sense. Integers and floating point values can be passed as is. Aside from those, there are pointers, which are simply integers in the generated code.
- Strings in JavaScript must be converted to pointers for compiled code, the relevant functions are
Pointer_stringify
which given a pointer returns a JavaScript string, and the other direction can be accomplished byallocate(intArrayFromString(someString), 'i8', ALLOC_STACK)
which will convert a JavaScript stringsomeString
to a pointer. Note that conversion to a pointer allocates memory (that's the call toallocate
there). - You can access memory using
getValue(ptr, type)
andsetValue(ptr, value, type)
. The first argument is a pointer, a number representing a memory address.type
must be an LLVM IR type, one ofi8,i16,i32,i64,float,double
or a pointer type likei8*
. - There are various other convenience functions, see preamble.js (that file will be included with the generated code).
- The bindings generator can generate convenient JS classes from C++ headers. See the bindings test in the test runner, and the ammo.js project (the main consumer of that tool)
- For filesystem-related manners, see the Filesystem Guide.