QuickJS is a small and embeddable Javascript engine that supports the ES2020 specification, including modules, asynchronous generators, and proxies.

Quickjs is a small and beautiful JAVASCRIPT engine published by Fabrice Bellard. It is a cost effective engine to learn how to implement javascript. In this series, we introduce how the JS specification is implemented in QuickJS, including variables, objects, functions, prototypes, closures, scopes, and so on. Garbage collection and so on.

Why learn the QuickJS engine yourself? Learning is actually a process of self-denial and affirmation; In the beginning of learning often hearsay, and do not know why; When to enter the stage of teaching, need to be rigorous and realistic, do not mislead the attitude of children, must understand WWW from the root; From a javascript engine point of view, QuickJS is just the starting point for a gradual transition to v8’s behemoth.

GDB is inescapable

Whether learning QuickJS or V8, you must learn the basic GDB commands, so GDB is the first lesson to get started (no vscode, no xcode).

  1. Compile quickjs

Quickjs is written in pure C and is relatively easy to compile. Just turn off GCC’s optimization options, search for CFLAGS_OPT in the Makefile, change O2 to O0, and make in the root directory

$ make
Copy the code
  1. Enter the GDB

After make compiles, a QJS file is generated, ready to test the javascript code and execute

gdb --args ./qjs path-of-javascript
Copy the code
  1. Several GDB instructions

Common GDB commands: b- break, r- start, C – continue, s- enter; P-print, X-print

Breakpoints can be set by function name or by file path: line number

b main
b quickjs.c:1000
Copy the code

Printing usually involves printing basic data types, Pointers, structures, enumerations, and so on

P v p *v p /d vCopy the code

Purely from the principle of learning engine implementation, I feel that these basic instructions are enough

According to the example

Anyone who has studied the principles of compilation should have a pretty good idea of the process, but I didn’t. Let’s get rid of all the concepts and start debugging and see what happens.

  1. Test the file example/variable.js
const a = 1;
const b = 2;
const c = a + b;
Copy the code
  1. The GDB debugging is displayed
$GDB --args./ QJS example/ variably.js $b main $b JS_CallInternal # Bytecode execution function $b quickjs.c:16627 # Bytecode parsing $r # execute $n . $ s ... $ cCopy the code
  1. Get the bytecode BUf

Unwinding unwinding,…. At this point the breakpoint stops at Quickjs :16627 and prints the bytecode.

# p b->byte_code_len # byte_code_len # (GDB) x/66db b->byte_code_buf # Print each byte size in base 10 0x55ACbb8a08c0: 63 60 2 0 0 -128 63 61 0x55acbb8a08c8: 2 0 0 -128 63 62 2 0 0x55acbb8a08d0: 0 -128 62 60 2 0 0 -128 0x55acbb8a08d8: 62 61 2 0 0 -128 62 62 0x55acbb8a08e0: 2 0 0 -128 -74 58 60 2 0x55acbb8a08e8: 0 0 -73 58 61 2 0 0 0x55acbb8a08f0: 56 60 2 0 0 56 61 2 0x55acbb8a08f8: 0 0 -99 58 62 2 0 0 0x55acbb8a0900: -59 40Copy the code
  1. Bytecode logic

Formatted according to the execution logic of the bytecode, the first column is the operator, followed by the data

63 60 2 0 0 -128 (OP_check_define_var) // Check whether variables are defined, corresponding to a, b, c 63 61 2 0 0 -128 (OP_check_define_var) 63 62 2 0 0 -128 (OP_check_define_var) 62 60 2 0 0 -128 (OP_define_var) // Define variables, One time corresponds to a, b, C 62 61 2 0 0-128 (OP_define_var) 62 62 2 0 0-128 (OP_define_var) -74 (op_PUSH_0-op_PUSH_7) // Push 58 60 2 0 0 (OP_put_var_init/OP_put_var) -73 (OP_push_0 - OP_push_7) // push 58 61 2 0 0 (OP_put_var_init/OP_put_var) // push out, 56 61 2 0 0 (OP_get_var) // Get variable, push -99 (OP_add) // stack operation, 58 62 2 0 0 (OP_put_var_init/OP_put_var) // out of the stack, set to the variable -59 (OP_get_loc0) 40 (OP_return) // return valueCopy the code

Can best guess:

60, 2, 0, 0 is a

61, 2, 0, 0 is b

62, 2, 0, 0 is c

  1. Stack virtual machine

Quickjs is based on stack virtual machine implementation, can observe the stack data changes to implement logic

Stack action The stack data The stack data
1 stack 1
1 out of the stack
2 into the stack 2
2 out of the stack
1 stack 1
2 into the stack 1 2
Take the data on the stack, add, and push the result 3
3 out of the stack
Return value on the stack 0
Returns the value out of the stack 0

Very troublesome oh, in and out, laborious, this is the disadvantage of stack virtual machine

conclusion

Getting started with QuickJS debugging, you can see our JS code translated into custom bytecode and then executed in our custom virtual machine. That’s the bytecode generation.

Follow my wechat official account “SUNTOPO WLOG”, welcome to leave a comment and discuss, I will reply as much as possible, thank you for reading.