Skip to content

Simulator build

Ivica Bogosavljevic edited this page Apr 18, 2018 · 6 revisions

V8 Simulator

There are built-in instruction set simulators in v8 for all the non-x86 architectures1 to allow V8 to be run on x86 development machines for all targets.

As you are likely aware, V8 is a C++ program that itself JIT-compiles native-code binary from Javascript source. When the target is x86 code, that code can be executed directly on the x86 development machine. For a MIPS target, the C++ code is normally compiled for MIPS, and run on a MIPS cpu, and the JIT-compiled MIPS binary code can then be directly executed on the target machine.

When V8 is build for MIPS target on x86 host, then the C++ code is compiled for x86, but the JIT compiler emits MIPS binary code into memory. That MIPS code cannot be called directly, so v8 arranges to call the simulator, with a pointer to the MIPS instructions that must be executed. The simulator is an instruction set interpreter.

Simulator uses

The simulator allows developers to do feature development, debugging, and testing all on the x86 development machine, without having to cross-compile and load code to a target board. It also includes a debugger some architecture-specific debugging techniques. Take a look at Debugging mips v8

The other primary use for the simulator is to compile snapshot data during compilation on x86 (for either cross-compiled MIPS target, or for local simulator builds). Please see snapshots below for a more complete description of this feature.

Building for simulator

On x86 Linux, to build v8 for simulator in debug mode first you need to configure the build using gn

for mips32el

gn gen out.gn/mipsel_debug --args='is_debug=true target_cpu="x86" v8_target_cpu="mipsel" mips_arch_variant="r2"'

or for mips64el

gn gen out.gn/mips64el_debug --args='is_debug=true target_cpu="x64" v8_target_cpu="mips64el" mips_arch_variant="r2" '

Then you use ninja to start the compilation ninja -C /path/to/gn/configuration, in our case ninja -C out.gn/mipsel_debug or ninja -C out.gn/mips64el_debug The build will not use multiple cores on your host machine unless you specify that with the -j option, for example: ninja -C out.gn/mips64el_debug -j24

You can use GN parameters to specify further the architecture you would like to simulate, for example

mips_arch_variant="r1"
mips_arch_variant="r2"
mips_arch_variant="r6"

FPU mode (this applies to mips_arch_variant=r2 and mips32 only)

 mips_fpu_mode="fp32"
 mips_fpu_mode="fp64"
 mips_fpu_mode="fpxx"

If you need all the debug symbols:

 v8_optimized_debug=true

If you want to compile without snapshots:

 v8_use_snapshot=false

Snapshots

A lot of V8's JavaScript functionality is implemented in JavaScript itself.[^2] As a result, there is a one-time performance cost involved in creating a V8 context.

This performance cost can be brought down by creating V8 snapshots. The snapshot contains a serialized image of the state of the V8 heap after all the built-in JS code is compiled.[^3] This image can then be quickly de-serialized into an empty heap when v8 starts up (or a new context is created), greatly startup time.

This feature is optional, and maybe disabled by passing v8_use_snapshot=false in gn configure line, e.g., gn gen out.gn/mipsel_debug --args='is_debug=true target_cpu="x86" v8_target_cpu="mipsel" mips_arch_variant="r2" v8_use_snapshot=false'

There are cases when debugging broken code where it is desirable to start up without the snapshot. An obvious example is if you need to print or trace the code that is compiled at startup.

The snapshot image is now contained in two external files, by default:

  • snapshot_blob.bin
  • natives_blob.bin

These can be included inside the binary, by using GN variable v8_use_external_startup_data=false

Building the snapshot

The V8 engine starts up its state set to an "empty" snapshot (snapshot-empty.cc, used when snapshots are disabled). A custom V8 snapshot can be created by evaluating the initial JavaScript and dumping V8's machine state into a a binry blob image (default) or optionally into a cpp file (snapshot.cpp). This cpp file is then compiled instead of snapshot-empty. Snapshots are created using the mksnapshot tool.

For native builds v8 is compiled and linked with the empty snapshot, and then the native version is run to generate the snapshot blob.

For cross-compiled versions, v8 must be compiled twice:

  1. A simulator build is compiled (e.g., target mipsel, host ia32), then that code us run with the simulator to produce the snapshot blobs or cc file.
  2. Then the real target code is cross-compiled, and (possibly) linked using the snapshot.

This method works great, but it does have some implications:

  1. You cannot generate big-endian snapshots this way, since the x86 host is little-endian only. (See the Building v8 for BE with QEMU page for an alternate method).
  2. The snapshot is generated using the build flags on the host at compile time, so we do not have the option of probing the runtime CPU and changing options on the fly. (This currently presents a problem with fp32, fp64, and fpxx modes, and solutions are being investigated).

1: V8 supports arm, arm64, mips, mips64, ppc and x86 (ia32 and x64).

Clone this wiki locally