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 file
quickjs.c
into multiple small files, which 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 inspector
The debugger can be tasted by following steps:
- 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 press ENTER
- Set breakpoints:
{ "type": "setBreakpoint", "data": { "file": "./tmp_test.js", "line": 3, "col": 0 } }
{ "type": "setBreakpoint", "data": { "file": "./tmp_test.js", "line": 8, "col": 0 } }
- Star 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:
client closed, stopping sess thread...
new sess thread is running...
2
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