Bug 1866909 - Improve grammar, spelling, and formatting for clarity r=mgaudet

Differential Revision: https://phabricator.services.mozilla.com/D196752
This commit is contained in:
Bryan Thrall 2023-12-19 15:38:23 +00:00
Родитель 272c5888a0
Коммит a159e27da1
1 изменённых файлов: 64 добавлений и 56 удалений

Просмотреть файл

@ -7,7 +7,7 @@ This is archived here because it captures valuable documentation that even if po
---
This page list a few tips to help you investigate issues related to SpiderMonkey. All tips listed here are dealing with the JavaScript shell obtained at the end of the [build documentation of SpiderMonkey](https://mdn-archive.mossop.dev/en-US/docs/SpiderMonkey "/en-US/docs/SpiderMonkey"). It is separated in 2 parts, one section related to debugging and another section related to drafting optimizations. Many of these tips only apply to debug builds of the JS shell; they will not function in a release build.
This page lists a few tips to help you investigate issues related to SpiderMonkey. All tips listed here are dealing with the JavaScript shell obtained at the end of the [build documentation of SpiderMonkey](https://mdn-archive.mossop.dev/en-US/docs/SpiderMonkey "/en-US/docs/SpiderMonkey"). It is separated in 2 parts, one section related to debugging and another section related to drafting optimizations. Many of these tips only apply to debug builds of the JS shell; they will not function in a release build.
## Debugging Tips
@ -43,7 +43,7 @@ Source notes:
### Getting the bytecode of a function (from gdb)
In _jsopcode.cpp_, a function named **js::DisassembleAtPC** can print the bytecode of a script. Some variants of this function such as **js::DumpScript** etc are convenient for debugging.
In _jsopcode.cpp_, a function named **js::DisassembleAtPC** can print the bytecode of a script. Some variants of this function, such as **js::DumpScript** etc., are convenient for debugging.
### Printing the JS stack (from gdb)
@ -109,7 +109,7 @@ Note, when you enable the unwinder, the current version of gdb (7.10.1) does not
(gdb) record stop
```
If you have a core file, you can use the gdb unwinder the same way, or do everything from the command line as follow:
If you have a core file, you can use the gdb unwinder the same way, or do everything from the command line as follows:
```
$ gdb -ex 'enable unwinder .* SpiderMonkey' -ex 'bt 0' -ex 'thread apply all backtrace' -ex 'quit' out/dist/bin/js corefile
@ -119,7 +119,7 @@ The gdb unwinder is supposed to be loaded by `dist/bin/js-gdb.py` and load pytho
### Setting a breakpoint in the generated code (from gdb, x86 / x86-64, arm)
To set a breakpoint the generated code of a specific JSScript compiled with IonMonkey. Set a breakpoint on the instruction you are interested in. If you have no precise idea which function you are looking at, you can set a breakpoint on the **js::ion::CodeGenerator::visitStart** function. Optionally, a condition on the **ins->id()** of the LIR instruction can be added to select precisely the instruction you are looking for. Once the breakpoint is on **CodeGenerator** function of the LIR instruction, add a command to generate a static breakpoint in the generated code.
To set a breakpoint in the generated code of a specific JSScript compiled with IonMonkey, set a breakpoint on the instruction you are interested in. If you have no precise idea which function you are looking at, you can set a breakpoint on the **js::ion::CodeGenerator::visitStart** function. Optionally, a condition on the **ins->id()** of the LIR instruction can be added to select precisely the instruction you are looking for. Once the breakpoint is on the **CodeGenerator** function of the LIR instruction, add a command to generate a static breakpoint in the generated code.
```
$ gdb --args js
@ -143,7 +143,7 @@ Program received signal SIGTRAP, Trace/breakpoint trap.
```
Once you hit the generated breakpoint, you can replace it by a gdb breakpoint to make it conditional, the procedure is to first replace the generated breakpoint by a nop instruction, and to set a breakpoint at the address of the nop.
Once you hit the generated breakpoint, you can replace it by a gdb breakpoint to make it conditional. The procedure is to first replace the generated breakpoint by a nop instruction, and to set a breakpoint at the address of the nop.
```
(gdb) x /5i $pc - 1
@ -167,10 +167,10 @@ Breakpoint 2 at 0x7ffff7fb1659
If you want to look at the assembly code generated by IonMonkey, you can follow this procedure:
- Place a breakpoint at CodeGenerator.cpp on the CodeGenerator::link method.
- Step next a few times, so that the "code" variable gets generated
- Print code->code\_, which is the address of the code
- Disassembly code read at this address (using x/Ni address, where N is the number of instructions you would like to see)
1. Place a breakpoint at CodeGenerator.cpp on the CodeGenerator::link method.
1. Step next a few times, so that the "code" variable gets generated
1. Print code->code\_, which is the address of the code
1. Disassemble code read at this address (using x/Ni address, where N is the number of instructions you would like to see)
Here is an example. It might be simpler to use the CodeGenerator::link lineno instead of the full qualified name to put the breakpoint. Let's say that the line number of this function is 4780, for instance:
@ -201,7 +201,7 @@ Here is an example. It might be simpler to use the CodeGenerator::link lineno in
0xf7fd25a8: sub $0x80,%esp
0xf7fd25ae: mov 0x94(%esp),%ecx
On arm, the compiled JS code will always be ARM machine code, whereas spidermonkey itself is frequently Thumb2. Since there isn't debug info for the jitted code, you will need to tell gdb that you are looking at ARM code:
On arm, the compiled JS code will always be ARM machine code, whereas SpiderMonkey itself is frequently Thumb2. Since there isn't debug info for the JIT'd code, you will need to tell gdb that you are looking at ARM code:
(gdb) set arm force-mode arm
@ -218,13 +218,13 @@ Or you can wrap the x command in your own command:
- Set a breakpoint on `js::wasm::Instance::callExport` (defined in `WasmInstance.cpp` as of November 18th 2016). This will trigger for _any_ asm.js/wasm call, so you should find a way to set this breakpoint for the only generated codes you want to look at.
- Run the program.
- Do `next` in gdb until you reach the definition of the `funcPtr`:
// Call the per-exported-function trampoline created by GenerateEntry.
auto funcPtr = JS*DATA_TO_FUNC_PTR(ExportFuncPtr, codeBase() + func.entryOffset());
if (!CALL_GENERATED_2(funcPtr, exportArgs.begin(), &tlsData*))
return false;
- `After it's set, x/64i funcPtr` will show you the trampoline code. There should be a call to an address at some point ; that's what we're targeting. Copy that address.
```
// Call the per-exported-function trampoline created by GenerateEntry.
auto funcPtr = JS*DATA_TO_FUNC_PTR(ExportFuncPtr, codeBase() + func.entryOffset());
if (!CALL_GENERATED_2(funcPtr, exportArgs.begin(), &tlsData*))
return false;
```
- After it's set, `x/64i funcPtr` will show you the trampoline code. There should be a call to an address at some point; that's what we're targeting. Copy that address.
```
0x7ffff7ff6000: push %r15
@ -246,11 +246,11 @@ return false;
```
- `x/64i address` (in this case: `x/64i 0x7ffff7ff6032`).
- If you want to put a breakpoint at the function's entry, you can do: `b *address` (for instance here, `b* 0x7ffff7ff6032`). Then you can display the instructions around pc with `x/20i $pc,` and execute instruction by instruction with `stepi.`
- If you want to put a breakpoint at the function's entry, you can do: `b *address` (for instance here, `b* 0x7ffff7ff6032`). Then you can display the instructions around pc with `x/20i $pc,` and execute instruction by instruction with `stepi`.
### Finding the script of Ion generated assembly (from gdb)
When facing a bug in which you are in the middle of IonMonkey generated code, first thing to note, is that gdb's backtrace is not reliable, because the generated code does not keep a frame pointer. To figure it out you have to read the stack to infer the IonMonkey frame.
When facing a bug in which you are in the middle of IonMonkey generated code, the first thing to note is that gdb's backtrace is not reliable, because the generated code does not keep a frame pointer. To figure it out, you have to read the stack to infer the IonMonkey frame.
```
(gdb) x /64a $sp
@ -264,7 +264,7 @@ $1 = 1
$2 = 0xff92d1 "typein"
```
The stack is order as defined in js/src/ion/IonFrames-x86-shared.h, it is composed of the return address, a descriptor (a small value), the JSFunction (if it is even) or a JSScript (if the it is odd, remove it to dereference the pointer) and the frame ends with the number of actual arguments (a small value too). If you want to know at which LIR the code is failing at, the **js::ion::CodeGenerator::generateBody** function can be instrumented to dump the LIR **id** before each instruction.
The stack is ordered as defined in js/src/ion/IonFrames-x86-shared.h. It is composed of the return address, a descriptor (a small value), the JSFunction (if it is even) or a JSScript (if it is odd; remove it to dereference the pointer) and the frame ends with the number of actual arguments (a small value too). If you want to know at which LIR the code is failing at, the **js::ion::CodeGenerator::generateBody** function can be instrumented to dump the LIR **id** before each instruction.
```
for (; iter != current->end(); iter++) {
@ -276,11 +276,11 @@ for (; iter != current->end(); iter++) {
return false;
```
`This modification will add an instruction which abuse the stack pointer` to store an immediate value (the LIR id) to a location which would never be generated by any sane compiler. Thus when dumping the assembly under gdb, this kind of instructions would be easily noticeable.
`This modification will add an instruction which abuses the stack pointer` to store an immediate value (the LIR id) to a location which would never be generated by any sane compiler. Thus when dumping the assembly under gdb, this kind of instructions would be easily noticeable.
### Viewing the MIRGraph of Ion/Odin compilations (from gdb)
With gdb instrumentation, we can call [iongraph](https://github.com/sstangl/iongraph) program within gdb when the execution is stopped. This instrumentation adds an **`iongraph`** command when provided with an instance of a **`MIRGenerator*`**, will call `iongraph`, `graphviz` and your preferred png viewer to display the MIR graph at the precise time of the execution. To find **`MIRGenetator*`** instances, is best is to look up into the stack for `OptimizeMIR`, or `CodeGenerator::generateBody`. **`OptimizeMIR`** function has a **`mir`** argument, and the **`CodeGenerator::generateBody`** function has a member **`this->gen`**.
With gdb instrumentation, we can call [iongraph](https://github.com/sstangl/iongraph) program within gdb when the execution is stopped. This instrumentation adds an **`iongraph`** command when provided with an instance of a **`MIRGenerator*`**, will call `iongraph`, `graphviz` and your preferred png viewer to display the MIR graph at the precise time of the execution. To find **`MIRGenetator*`** instances, it is best to look up into the stack for `OptimizeMIR`, or `CodeGenerator::generateBody`. **`OptimizeMIR`** function has a **`mir`** argument, and the **`CodeGenerator::generateBody`** function has a member **`this->gen`**.
```
(gdb) bt
@ -296,13 +296,13 @@ With gdb instrumentation, we can call [iongraph](https://github.com/sstangl/iong
/* open your png viewer with the result of iongraph */
```
This gdb instrumentation is supposed to work with debug builds, or with optimized build compiled with `--enable-jitspew` configure flag. External programs such as `iongraph`, `dot`, and your png viewer are search into the `PATH`, otherwise custom one can either be configured with environment variables (`GDB_IONGRAPH`, `GDB_DOT`, `GDB_PNGVIEWER`) before starting gdb, or with gdb parameters (`set iongraph-bin <path>`, `set dot-bin <path>`, `set pngviewer-bin <path>`) within gdb.
This gdb instrumentation is supposed to work with debug builds, or with optimized builds compiled with `--enable-jitspew` configure flag. External programs such as `iongraph`, `dot`, and your png viewer are searched for in the `PATH`; otherwise custom one can either be configured with environment variables (`GDB_IONGRAPH`, `GDB_DOT`, `GDB_PNGVIEWER`) before starting gdb, or with gdb parameters (`set iongraph-bin <path>`, `set dot-bin <path>`, `set pngviewer-bin <path>`) within gdb.
Enabling GDB instrumentation may require launching a JS shell executable that shares a directory with a file name "js-gdb.py". If js/src/js does not provide the "iongraph" command, try js/src/shell/js. GDB may complain that ~/.gdbinit requires modification to authorize user scripts, and if so will print out directions.
### Break on valgrind errors
Sometimes, a bug can be reproduced under valgrind but hardly under gdb. One way to investigate is to let valgrind start gdb for you, the other way documented here is to let valgrind act as a gdb server which can be manipulated from the gdb remote.
Sometimes, a bug can be reproduced under valgrind but with great difficulty under gdb. One way to investigate is to let valgrind start gdb for you; the other way documented here is to let valgrind act as a gdb server which can be manipulated from the gdb remote.
```
$ valgrind --smc-check=all-non-file
@ -333,51 +333,55 @@ Note: the line 3196, listed above, corresponds to the location of the [Jit spew
### Using the Gecko Profiler (browser / xpcshell)
see the section dedicated to [profiling with the gecko profiler](https://mdn-archive.mossop.dev/en-US/docs/Performance/Profiling_with_the_Built-in_Profiler "/en-US/docs/Performance/Profiling_with_the_Built-in_Profiler"). This method of profiling has the advantage of mixing the JavaScript stack with the C++ stack, which is useful to analyze library function issues. One tip is to start looking at a script with an inverted JS stack to locate the most expensive JS function, then to focus on the frame of this JS function, and to remove the inverted stack and look at C++ part of this function to determine from where the cost is coming from.
See the section dedicated to [profiling with the gecko profiler](https://mdn-archive.mossop.dev/en-US/docs/Performance/Profiling_with_the_Built-in_Profiler "/en-US/docs/Performance/Profiling_with_the_Built-in_Profiler"). This method of profiling has the advantage of mixing the JavaScript stack with the C++ stack, which is useful for analyzing library function issues.
One tip is to start looking at a script with an inverted JS stack to locate the most expensive JS function, then to focus on the frame of this JS function, and to remove the inverted stack and look at C++ part of this function to determine from where the cost is coming from.
### Using callgrind (JS shell)
As SpiderMonkey just-in-time compiler are rewriting the executed program, valgrind should be informed from the command line by adding **--smc-check=all-non-file**.
Because SpiderMonkey just-in-time compilers rewrite the executed program, valgrind should be informed from the command line by adding **--smc-check=all-non-file**.
```
$ valgrind --tool=callgrind --callgrind-out-file=bench.clg \
--smc-check=all-non-file
```
The output file can then be use with **kcachegrind** which provides a graphical view of the call graph.
The output file can then be used with **kcachegrind**, which provides a graphical view of the call graph.
### Using IonMonkey spew (JS shell)
IonMonkey spew is extremely verbose (not as much as the INFER spew), but you can filter it to focus on the list of compiled scripts or channels, IonMonkey spew channels can be selected with the IONFLAGS environment variable, and compilation spew can be filtered with IONFILTER.
IONFLAGS contains the names of each channel separated by commas. The **logs** channel produces 2 files in _/tmp/_, one (_/tmp/ion.json_) made to be used with [iongraph](https://github.com/sstangl/iongraph) (made by Sean Stangl) and another one (_/tmp/ion.cfg_) made to be used with [c1visualizer](http://java.net/projects/c1visualizer/). These tools will show the MIR & LIR steps done by IonMonkey during the compilation. If you would like to use [iongraph](https://github.com/sstangl/iongraph), you must install [Graphviz](https://www.graphviz.org/download/ "graphviz downloads").
IONFLAGS contains the names of [each channel separated by commas](https://searchfox.org/mozilla-central/source/js/src/jit/JitSpewer.cpp#338). The **logs** channel produces 2 files in _/tmp/_, one (_/tmp/ion.json_) made to be used with [iongraph](https://github.com/sstangl/iongraph) (made by Sean Stangl) and another one (_/tmp/ion.cfg_) made to be used with [c1visualizer](http://java.net/projects/c1visualizer/). These tools will show the MIR & LIR steps done by IonMonkey during the compilation. If you would like to use [iongraph](https://github.com/sstangl/iongraph), you must install [Graphviz](https://www.graphviz.org/download/ "graphviz downloads").
Compilation logs and spew can be filtered with the IONFILTER environment variable which contains locations as output in other spew channels. Multiple locations can be separated with comma as a separator of locations.
Compilation logs and spew can be filtered with the IONFILTER environment variable which contains locations as output by other spew channels. Multiple locations can be specified using comma as a separator.
```
$ IONFILTER=pdfjs.js:16934 IONFLAGS=logs,scripts,osi,bailouts ./js --ion-offthread-compile=off ./run.js 2>&1 | less
```
The **bailouts** channel is likely to be the first thing you should focus on, because this means that something does not stay in IonMonkey and fallback to the interpreter. This channel output locations (as returned by the **id()** function of both instructions) of the latest MIR and the latest LIR phases. These locations should correspond to phases of the **logs** and a filter can be used to remove uninteresting functions.
The **bailouts** channel is likely to be the first thing you should focus on, because this means that something does not stay in IonMonkey and fallback to the interpreter. This channel outputs locations (as returned by the **id()** function of both instructions) of the latest MIR and the latest LIR phases. These locations should correspond to phases of the **logs** and a filter can be used to remove uninteresting functions.
### Using the ARM simulator
The ARM simulator can be used to test the ARM JIT backend on x86/x64 hardware. An ARM simulator build is an x86 shell (or browser) with the ARM JIT backend. Instead of entering JIT code, it runs it in a simulator (interpreter) for ARM code. To use the simulator, compile an x86 shell (32-bit, x64 doesn't work as we use a different Value format there), and pass --enable-arm-simulator to configure. For instance, on a 64-bit Linux host you can use the following configure command to get an ARM simulator build:
```html
```shell
AR=ar CC="gcc -m32" CXX="g++ -m32" ../configure --target=i686-pc-linux
--enable-debug --disable-optimize --enable-threadsafe --enable-simulator=arm
```
Or on OS X:
```shell
$ AR=ar CC="clang -m32" CXX="clang++ -m32" ../configure --target=i686-apple-darwin10.0.0 --enable-debug --disable-optimize --enable-threadsafe --enable-arm-simulator
```
An --enable-debug --enable-optimize build is recommended if you want to run jit-tests or jstests.
An **--enable-debug --enable-optimize** build is recommended if you want to run jit-tests or jstests.
#### Use the VIXL Debugger in the simulator (arm64)
Set a breakpoint (see the comments above about `masm.breakpoint()`) and run with the environment variable `USE_DEBUGGER=1`. This will then drop you into a simple debugger provided with VIXL, the ARM simulator technology used for arm64 simulation.
Set a breakpoint (see the section above about setting a breakpoint in generated code) and run with the environment variable `USE_DEBUGGER=1`. This will then drop you into a simple debugger provided with VIXL, the ARM simulator technology used for arm64 simulation.
#### Use the Simulator Debugger for arm32
@ -416,15 +420,15 @@ Wait until it hits a failure. Now you can run `rr replay` to replay that last (f
To break on the write of a differing pixel:
1. Find the X/Y of a pixel that differs
2. Use 'run Z' where Z is the mark in the log for TEST-START. For example in '[rr 28496 607198]REFTEST TEST-START | file:///home/bgirard/mozilla-central/tree/image/test/reftest/bmp/bmpsuite/b/wrapper.html?badpalettesize.bmp' Z would be 607198.
3. break 'mozilla::dom::CanvasRenderingContext2D::DrawWindow(nsGlobalWindow&, double, double, double, double, nsAString_internal const&, unsigned int, mozilla::ErrorResult&)'
4. cont
5. break 'PresShell::RenderDocument(nsRect const&, unsigned int, unsigned int, gfxContext\*)'
6. set print object on
7. set $x = <YOUR X VALUE>
8. set $y = <YOUR Y VALUE>
9. print &((cairo_image_surface_t*)aThebesContext->mDT.mRawPtr->mSurface).data[$y * ((cairo_image_surface_t*)aThebesContext->mDT.mRawPtr->mSurface).stride + $x * ((cairo_image_surface_t\*)aThebesContext->mDT.mRawPtr->mSurface).depth / 8]
10. watch _(char_)<ADDRESS OF PREVIOUS COMMAND> (NOTE: If you set a watch on the previous expression gdb will watch the expression and run out of watchpoint)
2. Use `run Z` where Z is the mark in the log for TEST-START. For example in '[rr 28496 607198]REFTEST TEST-START | file:///home/bgirard/mozilla-central/tree/image/test/reftest/bmp/bmpsuite/b/wrapper.html?badpalettesize.bmp', Z would be 607198.
3. `break 'mozilla::dom::CanvasRenderingContext2D::DrawWindow(nsGlobalWindow&, double, double, double, double, nsAString_internal const&, unsigned int, mozilla::ErrorResult&)'`
4. `cont`
5. `break 'PresShell::RenderDocument(nsRect const&, unsigned int, unsigned int, gfxContext\*)'`
6. `set print object on`
7. `set $x = <YOUR X VALUE>`
8. `set $y = <YOUR Y VALUE>`
9. `print &((cairo_image_surface_t*)aThebesContext->mDT.mRawPtr->mSurface).data[$y * ((cairo_image_surface_t*)aThebesContext->mDT.mRawPtr->mSurface).stride + $x * ((cairo_image_surface_t\*)aThebesContext->mDT.mRawPtr->mSurface).depth / 8]`
10. `watch *(char*)<ADDRESS OF PREVIOUS COMMAND>` (NOTE: If you set a watch on the previous expression gdb will watch the expression and run out of watchpoints)
#### rr with emacs
@ -438,7 +442,7 @@ to get it to emit file location information so that emacs will pop up the corres
### [Hack] Replacing one instruction
To replace one specific instruction, you can use in visit function of each instruction the JSScript **filename** in **lineno** fields as well as the **id()** of the LIR / MIR instructions. The JSScript can be obtained from **info().script()**.
To replace one specific instruction, you can customize the instruction's visit function using the JSScript **filename** in **lineno** fields, as well as the **id()** of the LIR / MIR instructions. The JSScript can be obtained from **info().script()**.
```
bool
@ -453,7 +457,7 @@ CodeGeneratorX86Shared::visitGuardShape(LGuardShape *guard)
### [Hack] Spewing all compiled code
I usually just add this to the appropriate executableCopy.
I usually just add this to the appropriate `executableCopy()` function.
if (getenv("INST_DUMP")) {
char buf[4096];
@ -461,22 +465,26 @@ I usually just add this to the appropriate executableCopy.
system(buf);
}
If you aren't running on arm, you should omit the -ex 'set arm force-mode arm' and -ex 'set arm force-mode auto'. And you should change the size()/4 to be something more appropriate for your architecture.
If you aren't running on arm, you should omit the `-ex 'set arm force-mode arm'` and `-ex 'set arm force-mode auto'`. And you should change the size()/4 to be something more appropriate for your architecture.
### Benchmarking with sub-milliseconds (JS shell)
In the shell we have 2 simple ways to benchmark a script, we can either use the **-b** shell option (**--print-timing**) which will evaluate a script given on the command line without any need to instrument the benchmark and print an extra line showing the run-time of the script. The other way is to wrap the section that you want to measure with the **dateNow()** function call which returns the number of milliseconds, with a decimal part for sub-milliseconds.
In the shell, we have 2 simple ways to benchmark a script. We can either use the **-b** shell option (**--print-timing**) which will evaluate a script given on the command line without any need to instrument the benchmark and print an extra line showing the run-time of the script. The other way is to wrap the section that you want to measure with the **dateNow()** function call, which returns the number of milliseconds, with a decimal part for sub-milliseconds.
```js
js> dateNow() - dateNow()
-0.0009765625
```
Since [Firefox 61](https://bugzilla.mozilla.org/show_bug.cgi?id=1439788), the shell also has **performance.now()** available.
### Benchmarking with sub-milliseconds (browser)
In a similar way as **dateNow()** in the JS shell, you can use **performance.now()** in the JavaScript code of a page.
Similar to how you can use **dateNow()** in the JS shell, you can use **performance.now()** in the JavaScript code of a page.
### Dumping the JavaScript heap
From the shell, you can call the dumpHeap before Firefox function to dump out all GC things (reachable and unreachable) that are present in the heap. By default the function writes to stdout, but a filename can be specified as an argument.
From the shell, you can call the `dumpHeap` function to dump out all GC things (reachable and unreachable) that are present in the heap. By default, the function writes to stdout, but a filename can be specified as an argument.
Example output might look as follows:
@ -484,11 +492,11 @@ Example output might look as follows:
0x1234abcd B global object
```
The output is textual. The first section of the file contains a list of roots, one per line. Each root has the form "0xabcd1234 <color> <description>", where <color> is the color of the given GC thing (B for black, G for gray, W for white) and <description> is a string. The list of roots ends with a line containing "==========".
The output is textual. The first section of the file contains a list of roots, one per line. Each root has the form "0xabcd1234 \<color> \<description>", where \<color> is the color of the given GC thing (B for black, G for gray, W for white) and \<description> is a string. The list of roots ends with a line containing "==========".
After the roots come a series of zones. A zone starts with several "comment lines" that start with hashes. The first comment declares the zone. It is followed by lines listing each compartment within the zone. After all the compartments come arenas, which is where the GC things are actually stored. Each arena is followed by all the GC things in the arena. A GC thing starts with a line giving its address, its color, and the thing kind (object, function, whatever). After this come a list of addresses that the GC thing points to, each one starting with ">".
After the roots come a series of zones. A zone starts with several "comment lines" that start with hashes. The first comment declares the zone. It is followed by lines listing each compartment within the zone. After all the compartments come arenas, which is where the GC things are actually stored. Each arena is followed by all the GC things in the arena. A GC thing starts with a line giving its address, its color, and the thing kind (object, function, whatever). After this comes a list of addresses that the GC thing points to, each one starting with ">".
It's also possible to dump the JavaScript heap from C++ code (or from gdb) using the js::DumpHeap function. It is part of jsfriendapi.h and it is available in release builds.
It's also possible to dump the JavaScript heap from C++ code (or from gdb) using the `js::DumpHeap` function. It is part of jsfriendapi.h and it is available in release builds.
### Inspecting MIR objects within a debugger
@ -501,20 +509,20 @@ For MIRGraph, MBasicBlock, and MDefinition and its subclasses (MInstruction, MCo
### How to debug oomTest() failures
The oomTest() function executes a piece of code many times, simulating an OOM failure at each successive allocation it makes. It's designed to highlight incorrect OOM handling and this may show up as a crash or assertion failure at some later point.
The oomTest() function executes a piece of code many times, simulating an OOM failure at each successive allocation it makes. It's designed to highlight incorrect OOM handling, which may show up as a crash or assertion failure at some later point.
When debugging such a crash the most useful thing is to locate the last simulated allocation failure, as it's usually this that has caused the subsequent crash.
When debugging such a crash, the most useful thing is to locate the last simulated allocation failure, as that is usually what has caused the subsequent crash.
My workflow for doing this is as follows:
1. Build a version of the engine with `--enable-debug` and `--enable-oom-breakpoint` configure flags.
2. Set the environment variable `OOM_VERBOSE=1` and reproduce the failure. This will print an allocation count at each simulated failure. Note the count of the last allocation.
3. Run the engine under a debugger and set a breakpoint on the function `js_failedAllocBreakpoint`.
4. Run the program and continue the necessary number of times until you reach the final allocation.
4. Run the program and `continue` the necessary number of times until you reach the final allocation.
- e.g. in lldb, if the allocation failure number shown is 1500, run `continue -i 1498` (subtracted 2 because we've already hit it once and don't want to skip the last). Drop "-i" for gdb.
5. Dump a backtrace. This should show you the point at which the OOM is incorrectly handled, which will be a few frames up from the breakpoint.
Note: if you are on linux it may be simpler to use rr.
Note: if you are on linux, it may be simpler to use rr.
Some guidelines for handling OOM that lead to failures when they are not followed:
@ -523,7 +531,7 @@ Some guidelines for handling OOM that lead to failures when they are not followe
2. Report OOM to the context if you have one
- If a function has a `JSContext*` argument, usually it should call `js::ReportOutOfMemory(cx)` on allocation failure to report this to the context.
3. Sometimes it's OK to ignore OOM
- For example if you are performing a speculative optimisation you might abandon it and continue anyway. But in this case you may have to call cx->recoverFromOutOfMemory() if something further down the stack has already reported the failure.
- For example if you are performing a speculative optimisation you might abandon it and continue anyway. In this case, you may have to call cx->recoverFromOutOfMemory() if something further down the stack has already reported the failure.
### Debugging GC marking/rooting