Node.js Under the Hood #8 - Understanding Bytecodes
Tags: node, javascript, v8, assembly
Published: 2020-02-04 19:19:50

We've been talking a lot about bytecodes lately. But what are bytecodes?

Bytecodes are abstractions of machine codes. Think of it as something in between the code we can read and the code machines execute. However, bytecodes are **machine agnostic**, which means that bytecodes can be compiled into whatever machine architecture you're running on – however, compiling bytecode to machine code is way easier if you generate bytecode which was designed with the same computational model as the underlying CPU.

CPUs are Turing machines that are either based on stacks, registers or states. V8's Ignition interpreter is a **register-based interpreter with an accumulator** as well as most of the CPUs.

![](https://github.com/khaosdoctor/my-notes/raw/master/node/assets/ignition-bytecode.png)

In the end, bytecode is translated into assembly/machine code which can be sent to the processor and executed.

You can think of JavaScript as a series of small building blocks. Each operator (or sets of operators) have a bytecode notation in V8. So we have bytecodes for operators like `typeof`, `add`, `sub` and we also have operators for property loads like `LdaSmi` for small integers or `LdaNamedProperty`. The complete list can be found at the [header file](https://github.com/v8/v8/blob/master/src/interpreter/bytecodes.h)

## Registers

Ignition uses registers like `r0, r1, r2 ...` to store bytecode inputs or outputs and specifies which ones to use. Along with input registers, Ignition also has an accumulator register, which stores the results of operations, we'll call it `acc`. It's pretty much the same as common registers but operands do not specify it at all, for instance, `sub r0` is subtracting the value in `r0` **from** the value **in** the accumulator, leaving the result value in the `acc` itself.

You'll see that many bytecodes start with `Lda` or `Sta`, the `a` stands for "accumulator", while `Ld` is "load" and `St` is "store". So, by intuition, `LdaSmi [99]` loads the small integer `99` into the accumulator, while `Star r0` stores the value of the accumulator into the register `r0`.

This is because if we wrote: "LoadSmallIntToAccumulator" instead of "LdaSmi" we'd have to allocate more memory just to store the bytecode name. This is why bytecodes scare a lot of people.

> Shorter bytecodes = less memory

## Hands-on

Let's take a real bytecode from a real function in JavaScript. We're not using our `readFile` function since it'd be too complicated. Let's use this simple function:

```js
function multiplyXByY (obj) {
  return obj.x * obj.y
}

multiplyXByY({ x: 1, y: 2 })
```

> *A small note*: V8 compiler is lazy, so if you don't run a function, it is not compiled, which means it does not generate any bytecode.

This will generate the following bytecode:

```
[generated bytecode for function: multiplyXByY]
Parameter count 2
Register count 1
Frame size 8
   22 E> 0x334a92de11fe @    0 : a5                StackCheck
   43 S> 0x334a92de11ff @    1 : 28 02 00 01       LdaNamedProperty a0, [0], [1]
         0x334a92de1203 @    5 : 26 fb             Star r0
   51 E> 0x334a92de1205 @    7 : 28 02 01 03       LdaNamedProperty a0, [1], [3]
   45 E> 0x334a92de1209 @   11 : 36 fb 00          Mul r0, [0]
   52 S> 0x334a92de120c @   14 : a9                Return
Constant pool (size = 2)
Handler Table (size = 0)
```

> You can generate and print your bytecode using the command `node --print-bytecode --print-bytecode-filter=functionName <file>`

Let's ignore the header and the footer since it's just metadata for the bytecodes.

**LdaNamedProperty a0, [0], [1]**

This bytecode loads a named property of `a0` – Ignition identifies parameters as `a0, a1, a2...`, the number is the index of the argument, so `a0` is the first argument of the function (`obj`) – into the accumulator.

In this particular bytecode, we're looking up the named property on `a0`, so we're loading the first argument of the function, which is `obj`. The name we're looking at is determined by the first parameter: `[0]`. This constant is used to look up the name in a separate table – which can be accessed in the `Constant Pool` part of the print, but only in Node.js debug mode:

```
0x263ab302cf21: [FixedArray] in OldSpace
 - map = 0x2ddf8367abce <Map(HOLEY_ELEMENTS)>
 - length: 2
           0: 0x2ddf8db91611 <String[1]: x>
           1: 0x2ddf8db67544 <String[1]: y>
```

So we see that position 0 is `x`. The `[1]` is the index of what is called "feedback vector", which contains runtime information that is used for optimizations.

**Star r0**

`Star r0` stores the value that is currently in the accumulator, which is the value of the `x` index we just loaded, in the register `r0`.

**LdaNamedProperty a0, [1], [3]**

This is the same thing, but we're now loading the index 1, which is `y`.

**Mul r0, [0]**

This operation multiplies the value that is currently in the accumulator (`y`) by `r0` (`x`) and stores the result into the accumulator.

**Return**

The return statement returns the value that is currently in the accumulator. It's also the end of the function. So the function caller will start with the result of our last bytecode operation – which is `2` – already in the accumulator.

## Takeaways

Most bytecodes may seem gibberish at first glance. But keep in mind that Ignition is a register machine with an accumulator, that is basically how we can simply understand how it works.

This would be the bytecode for our `readFile` function:

```
[generated bytecode for function: readFileAsync]
Parameter count 2
Register count 3
Frame size 24
         0x23e95d8a1ef6 @    0 : 84 00 01          CreateFunctionContext [0], [1]
         0x23e95d8a1ef9 @    3 : 16 fb             PushContext r0
         0x23e95d8a1efb @    5 : 25 02             Ldar a0
         0x23e95d8a1efd @    7 : 1d 04             StaCurrentContextSlot [4]
  261 E> 0x23e95d8a1eff @    9 : a5                StackCheck
  279 S> 0x23e95d8a1f00 @   10 : 13 01 00          LdaGlobal [1], [0]
         0x23e95d8a1f03 @   13 : 26 fa             Star r1
         0x23e95d8a1f05 @   15 : 81 02 00 02       CreateClosure [2], [0], #2
         0x23e95d8a1f09 @   19 : 26 f9             Star r2
         0x23e95d8a1f0b @   21 : 25 fa             Ldar r1
  286 E> 0x23e95d8a1f0d @   23 : 65 fa f9 01 02    Construct r1, r2-r2, [2]
  446 S> 0x23e95d8a1f12 @   28 : a9                Return
Constant pool (size = 3)
Handler Table (size = 0)
```

We can see it has a series of bytecodes specifically designed to several aspects of the language, such as closures, globals and so on... Can you read that bytecode? Leave it here in the comments :)

#### Thanks

A big thanks to [Franziska Hinkelmann](https://twitter.com/fhinkel), her articles and talks about V8 bytecodes are simply awesome and helped me a lot when I started studying this topic. Especially [this](https://medium.com/dailyjs/understanding-v8s-bytecode-317d46c94775) article!