Skip to content

Commit 4b8cc27

Browse files
authored
Merge pull request bellard#8 from hsiaosiyuan0/feat/gc-dump
feat/gc-dump
2 parents 16b9f09 + bff8311 commit 4b8cc27

31 files changed

+1640
-90
lines changed

.vscode/launch.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
"request": "launch",
1010
"name": "Debug",
1111
"program": "${workspaceFolder}/build/qjs/qjs",
12-
"args": ["--debug", "8097"],
13-
// "args": ["tmp_test_1.mjs"],
12+
// "args": ["--debug", "8097"],
13+
"args": ["tmp_test.js"],
1414
"cwd": "${workspaceFolder}"
1515
}
1616
]

.vscode/settings.json

+6
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"DATAVIEW",
4141
"dbuf",
4242
"DCMAKE",
43+
"dctx",
4344
"DECOMP",
4445
"DECREF",
4546
"divrem",
@@ -64,17 +65,20 @@
6465
"FPROUND",
6566
"fsetter",
6667
"ftoa",
68+
"gcdump",
6769
"getset",
6870
"gosub",
6971
"hasexpr",
7072
"hasval",
73+
"heapsnapshot",
7174
"HTMLDDA",
7275
"indexof",
7376
"INTRINS",
7477
"isqrt",
7578
"jscalc",
7679
"JSGC",
7780
"jsstring",
81+
"JSVALUE",
7882
"libbf",
7983
"LIBREGEXP",
8084
"LIBUNICODE",
@@ -86,6 +90,7 @@
8690
"MAPSET",
8791
"Microbench",
8892
"MKPTR",
93+
"MKVAL",
8994
"nfinity",
9095
"NFKC",
9196
"NFKD",
@@ -156,6 +161,7 @@
156161
"TYPARR",
157162
"TYPEDARR",
158163
"typedarray",
164+
"uctx",
159165
"uncatchable",
160166
"unconsistent",
161167
"unparenthesized",

README.md

+59-3
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@ SlowJS - QuickJS is quick but I can make it slow!
44

55
Learning the awesome [QuickJS](https://github.com/bellard/quickjs) by extending it with below functionalities:
66

7-
- [x] Divide the 5.4W LoC [quickjs.c](https://github.com/bellard/quickjs/blob/master/quickjs.c) into multiple small files, which makes the code easy to browser and navigate
7+
- [x] Divide the 5.4W LoC [quickjs.c](https://github.com/bellard/quickjs) into multiple small files, makes the code easy to browser and navigate
88
- [x] A debugger which supports inline breakpoints and includes web interfaces which is easy to integrate with the [Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/)
9-
- [ ] Dump the GC managed objects and view the results in the Chrome inspector
9+
- [x] Dump the GC managed objects and view the results in the Chrome devtools
1010

1111
## Debugger
1212

1313
The debugger can be tasted by following steps:
1414

15+
<details>
16+
<summary>Click to expand</summary>
17+
1518
1. Build our SlowJS:
1619

1720
```bash
@@ -167,11 +170,64 @@ the output looks like:
167170
11. Now the test script is done and the debugger server prints the final results:
168171

169172
```bash
170-
client closed, stopping sess thread...
171173
new sess thread is running...
172174
2
173175
```
174176

177+
</details>
178+
179+
## GC Dump
180+
181+
The GC dump functionality can be tasted by following steps:
182+
183+
<details>
184+
<summary>Click to expand</summary>
185+
186+
187+
1. Build our SlowJS:
188+
189+
```bash
190+
cmake -S . --preset=default
191+
cmake --build --preset=qjs
192+
```
193+
194+
the location of the built stuff is `./build/qjs/qjs`
195+
196+
2. Make up a file `tmp_test.js` to test:
197+
198+
```js
199+
var o = {
200+
a: { a1: { a2: 1 } },
201+
b: { b1: { b2: 1 } },
202+
c: function () {
203+
return 1;
204+
},
205+
d: new ArrayBuffer((1 << 20) * 50, 0),
206+
e: new Uint16Array((1 << 20) * 50, 0),
207+
};
208+
209+
__js_gcdump_objects();
210+
print(o); // retain the obj to prevent it from being freed
211+
```
212+
213+
3. Run the test script:
214+
215+
```bash
216+
./build/qjs/qjs tmp_test.js
217+
```
218+
219+
4. The output file will have name looks like `Heap.20230318.123156.775845.heapsnapshot` in this pattern `Heap.date.time.ms.heapsnapshot`
220+
221+
5. Import the output file into Chrome devtools:
222+
223+
![](/docs/imgs/chrome-devtools-load-heap.png)
224+
225+
6. The we can dig into the heap:
226+
227+
![](/docs/imgs/chrome-devtools-heap.png)
228+
229+
</details>
230+
175231
## Development
176232

177233
It's better to glance over the available options before you perform the actual build:

docs/imgs/chrome-devtools-heap.png

671 KB
Loading
269 KB
Loading

include/quickjs-libc.h

+3
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ void js_std_set_worker_new_context_func(JSContext *(*func)(JSRuntime *rt));
5555
JSValue js_debug_pc2line(JSContext *ctx, JSValueConst this_val, int argc,
5656
JSValueConst *argv);
5757

58+
JSValue js_gcdump_objects(JSContext *ctx, JSValueConst this_val, int argc,
59+
JSValueConst *argv);
60+
5861
#ifdef __cplusplus
5962
} /* extern "C" { */
6063
#endif

include/quickjs.h

+70
Original file line numberDiff line numberDiff line change
@@ -347,10 +347,77 @@ void JS_FreeRuntime(JSRuntime *rt);
347347
void *JS_GetRuntimeOpaque(JSRuntime *rt);
348348
void JS_SetRuntimeOpaque(JSRuntime *rt, void *opaque);
349349
typedef void JS_MarkFunc(JSRuntime *rt, JSGCObjectHeader *gp);
350+
typedef struct JSProperty JSProperty;
351+
typedef struct JSShapeProperty JSShapeProperty;
350352
void JS_MarkValue(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func);
351353
void JS_RunGC(JSRuntime *rt);
352354
JS_BOOL JS_IsLiveObject(JSRuntime *rt, JSValueConst obj);
353355

356+
// node types come from the v8's impl
357+
enum {
358+
JSGCDumpEdge_TYPE_CTX_VAR, // A variable from a function context.
359+
JSGCDumpEdge_TYPE_ELEM, // An element of an array.
360+
JSGCDumpEdge_TYPE_PROP, // A named object property.
361+
JSGCDumpEdge_TYPE_INTERNAL, // A link that can't be accessed from JS,
362+
// thus, its name isn't a real property name
363+
// (e.g. parts of a ConsString).
364+
JSGCDumpEdge_TYPE_HIDDEN, // A link that is needed for proper sizes
365+
// calculation, but may be hidden from user.
366+
JSGCDumpEdge_TYPE_SHORTCUT, // A link that must not be followed during
367+
// sizes calculation.
368+
JSGCDumpEdge_TYPE_WEAK // A weak reference (ignored by the GC).
369+
};
370+
371+
// edge types come from the v8's impl
372+
enum {
373+
JSGCDumpNode_TYPE_HIDDEN, // Hidden node, may be filtered when shown to
374+
// user.
375+
JSGCDumpNode_TYPE_ARRAY, // An array of elements.
376+
JSGCDumpNode_TYPE_STRING, // A string.
377+
JSGCDumpNode_TYPE_OBJECT, // A JS object (except for arrays and strings).
378+
JSGCDumpNode_TYPE_CODE, // Compiled code.
379+
JSGCDumpNode_TYPE_CLOSURE, // Function closure.
380+
JSGCDumpNode_TYPE_REGEXP, // RegExp.
381+
JSGCDumpNode_TYPE_HEAP_NUMBER, // Number stored in the heap.
382+
JSGCDumpNode_TYPE_NATIVE, // Native object (not from V8 heap).
383+
JSGCDumpNode_TYPE_SYNTHETIC, // Synthetic object, usually used for grouping
384+
// snapshot items together.
385+
JSGCDumpNode_TYPE_CONS_STRING, // Concatenated string. A pair of pointers
386+
// to strings.
387+
JSGCDumpNode_TYPE_SLICED_STRING, // Sliced string. A fragment of another
388+
// string.
389+
JSGCDumpNode_TYPE_SYMBOL, // A Symbol (ES6).
390+
JSGCDumpNode_TYPE_BIGINT // BigInt.
391+
};
392+
393+
typedef struct JSGCDumpNode JSGCDumpNode;
394+
typedef struct JSGCDumpEdge JSGCDumpEdge;
395+
typedef struct JSGCDumpContext JSGCDumpContext;
396+
397+
typedef struct JS_GCDumpFuncContext {
398+
JSGCDumpContext *dc;
399+
400+
// prop info when traversing the parent object
401+
JSShapeProperty *prs;
402+
JSProperty *pr;
403+
404+
// manually specified prop name
405+
union {
406+
const char *n;
407+
int i;
408+
} p;
409+
int plen; // -1: i, >0: n
410+
411+
int parent;
412+
// user specified data
413+
void *udata;
414+
} JS_GCDumpFuncContext;
415+
416+
typedef void JS_GCDumpFunc(JSRuntime *rt, void *cell,
417+
JS_GCDumpFuncContext dctx);
418+
void JS_GCDumpValue(JSRuntime *rt, JSValueConst val, JS_GCDumpFunc *walk_func,
419+
JS_GCDumpFuncContext dctx);
420+
354421
JSContext *JS_NewContext(JSRuntime *rt);
355422
void JS_FreeContext(JSContext *s);
356423
JSContext *JS_DupContext(JSContext *ctx);
@@ -478,6 +545,8 @@ typedef struct JSClassExoticMethods {
478545
typedef void JSClassFinalizer(JSRuntime *rt, JSValue val);
479546
typedef void JSClassGCMark(JSRuntime *rt, JSValueConst val,
480547
JS_MarkFunc *mark_func);
548+
typedef void JSClassGCDump(JSRuntime *rt, JSValueConst val,
549+
JS_GCDumpFunc *walk_func, JS_GCDumpFuncContext dctx);
481550
#define JS_CALL_FLAG_CONSTRUCTOR (1 << 0)
482551
typedef JSValue JSClassCall(JSContext *ctx, JSValueConst func_obj,
483552
JSValueConst this_val, int argc, JSValueConst *argv,
@@ -487,6 +556,7 @@ typedef struct JSClassDef {
487556
const char *class_name;
488557
JSClassFinalizer *finalizer;
489558
JSClassGCMark *gc_mark;
559+
JSClassGCDump *gc_dump;
490560
/* if call != NULL, the object is a function. If (flags &
491561
JS_CALL_FLAG_CONSTRUCTOR) != 0, the function is called as a
492562
constructor. In this case, 'this_val' is new.target. A

libs/quickjs-libc.c

+5-1
Original file line numberDiff line numberDiff line change
@@ -3606,7 +3606,11 @@ void js_std_add_helpers(JSContext *ctx, int argc, char **argv) {
36063606

36073607
JS_SetPropertyStr(
36083608
ctx, global_obj, "__js_debug_pc2line",
3609-
JS_NewCFunction(ctx, js_debug_pc2line, "__js_debug_pc2line", 0));
3609+
JS_NewCFunction(ctx, js_debug_pc2line, "__js_debug_pc2line", 1));
3610+
3611+
JS_SetPropertyStr(
3612+
ctx, global_obj, "__js_gcdump_objects",
3613+
JS_NewCFunction(ctx, js_gcdump_objects, "__js_gcdump_objects", 0));
36103614

36113615
JS_FreeValue(ctx, global_obj);
36123616
}

0 commit comments

Comments
 (0)