SlowJS - QuickJS is quick but I can make it slow!
Learning the awesome QuickJS by extending it with below functionalities:
- Divide the 5.4W LoC quickjs.c into multiple small files, makes the code easy to browser and navigate
- A debugger which supports inline breakpoints and includes web interfaces which is easy to integrate with the Debug Adapter Protocol
- Dump the GC managed objects and view the results in the Chrome devtools
The debugger can be tasted by following steps:
Click to expand
-
Build our SlowJS:
cmake -S . --preset=default cmake --build --preset=qjs
the location of the built stuff is
./build/qjs/qjs
-
Make up a file
tmp_test.js
to test:function add(a, b) { const c = a + b; return c; } function sub(a, b) { const c = a - b; return c; } function doSth(a, b) { return add(a, b) + sub(a, b); } print(doSth(1, 2));
-
Start the debugger:
./build/qjs/qjs --debug 8097
-
Connect to the debugger:
nc 0.0.0.0 8097
We use
nc
to communicate with the debugger server, then we can paste come commands to perform debug -
Call the debugger to launch a new session:
{ "type": "launch", "data": { "file": "./tmp_test.js" } }
Paste above json into the
nc
REPL and pressENTER
-
Set breakpoints:
{ "type": "setBreakpoint", "data": { "file": "./tmp_test.js", "line": 3, "col": 0 } }
{ "type": "setBreakpoint", "data": { "file": "./tmp_test.js", "line": 8, "col": 0 } }
-
Start to run our test script:
{ "type": "run" }
-
Now the debugger is paused at the first breakpoint, we can list the stack frames:
{ "type": "listStackframes" }
the output looks like:
{ "type": "listStackframes", "data": [ { "name": "add", "file": "./tmp_test.js", "line": 1 }, { "name": "doSth", "file": "./tmp_test.js", "line": 11 }, { "name": "<eval>", "file": "./tmp_test.js", "line": 1 } ] }
-
We can resume the debugger by issuing below command:
{ "type": "continue" }
-
Now the debugger is paused at the second breakpoint, we can print the variable in the topmost stack frame:
{ "type": "dumpStackframe", "data": { "i": 0 } }
the output looks like:
{ "type": "dumpStackframe", "data": { "args": [ { "name": "a", "value": 1 }, { "name": "b", "value": 2 } ], "vars": [ { "name": "c", "value": -1 } ], "closure_vars": [], "name": "sub", "file": "./tmp_test.js", "line": 6 } }
-
We can use the
continue
command resume the debugger again:{ "type": "continue" }
-
Now the test script is done and the debugger server prints the final results:
new sess thread is running... 2
The GC dump functionality can be tasted by following steps:
Click to expand
-
Build our SlowJS:
cmake -S . --preset=default cmake --build --preset=qjs
the location of the built stuff is
./build/qjs/qjs
-
Make up a file
tmp_test.js
to test:var o = { a: { a1: { a2: 1 } }, b: { b1: { b2: 1 } }, c: function () { return 1; }, d: new ArrayBuffer((1 << 20) * 50, 0), e: new Uint16Array((1 << 20) * 50, 0), }; __js_gcdump_objects(); print(o); // retain the obj to prevent it from being freed
-
Run the test script:
./build/qjs/qjs tmp_test.js
-
The output file will have name looks like:
Heap.20230318.130209.224.heapsnapshot
the filename is in this pattern:
Heap.date.time.ms.heapsnapshot
-
Import the output file into Chrome devtools:
-
Then we can dig into the heap:
It's better to glance over the available options before you perform the actual build:
cmake -B build -LH
-B
stands for the building directory-L
stands for listing all the options-H
stands for printing the help messages along with the options
above command will print the available options and their help messages, use them like this:
cmake -B build -S . -G Ninja -D QJS_DUMP_BYTECODE=1
-S
stands for the source directory-D
stands for specifying an options in akey=value
pattern
then choose one of below sections to run in project root directory
cmake -S . --preset=default
cmake --build --preset=qjs
cmake -S . --preset=default -D CMAKE_BUILD_TYPE=Release
cmake --build --preset=qjs
cmake -S . --preset=default
cmake --build --preset=run-tests
cmake -S . --preset=default -D CMAKE_BUILD_TYPE=Release
cmake --build --preset=run-microbench
cmake -S . --preset=default -D CMAKE_BUILD_TYPE=Release
cmake --build --preset=run-test262
# Result: 302/75790 errors, 1396 excluded, 7712 skipped, 302 new
You can also choose the presets listed in CMakePresets.json
to run:
# Use a config preset
cmake -S . --preset=default
# Use a build preset
cmake --build --preset=run-tests
# Link your own JS
cmake --build --preset=myjs