Nodejs C++/JS Boundary: Crossing The Rubicon

Nodejs C++/JS Boundary: Crossing The Rubicon

Most articles about Nodejs internals always talk about the C++/JS boundary and crossing it sorta thing. But most don't usually go in-depth to

Most articles about Nodejs internals always talk about the C++/JS boundary and crossing it sorta thing. But most don’t usually go in-depth to explain what crossing the C++/JS boundary really meant and what crossing it is all about.

In this article, we will take an in-depth look at the C++/JS boundary to know what crossing it entails.

What we will gain from this article: Node.js powers over a million startups and companies. It is the most used backend framework. We use it every day. Developers with Node.js skill are in high demand, so learning how Node.js works in-depth will go a long way to broaden our horizon on Nodejs and be confident when building Nodejs apps.

C++/JS: The Boundary between two worlds

It all starts with the compiler.

First, what is a compiler?

A compiler is a program that translates a piece of code into machine code.

Great!! into machine code. Remember that.

Compilers are complex programs, they are broken down into parts, each part has a specific job.

source code
    |
    v
lexical analyzer
    |
    v
  parser
    |
    v
code generation

lexical analyzer: This generates tokens from the source code.

parser: This generates AST (Abstract Syntax Tree) tree from the tokens.

code generation: This generates machine/assembly code from the AST.

Most compilers generate the assembly equivalent of the source code and leave the assembler to mash everything up into a binary soup.

Nodejs uses v8 JS engine to compile and execute JS code. I hope, we know what v8 is.

if we don’t, v8 is an open-source high-performance JavaScript compiler from Google.

Whenever we run a js file in Node.js like this:

node script.js

Nodejs passes the script to v8 using its APIs. v8 compiles the JS code in the provided script (script.js) and returns the assembly equivalent. Node.js then uses another v8 API to run the generated assembly code.

The compiler compiles the code to assembly and copies it to memory.

This is image title

Looking at the above image, the JS code is compiled to assembly code.

To run the compiled code, it allocates a space on the memory, moves the code to the allocated space and jumps to the memory space.

At the jump, execution now begins from the compiled code. Hence, it has crossed a boundary.

The code being executed now isn’t a C++ code but JS code. All are now in assembly.

If the compiled JS code execution ends, it jumps back to the C++ code.

The C++ code and JS code here doesn’t mean the C++ source code or the JS source code. No. It is the assembly code generated from their source codes by the compiler is what is being executed. You can say C++ code and JS code to differentiate which assembly code being run.

Call from JS to C++

Functions in C++ source code can be called from a JS code.

Example:

// script.js
let f = 90
function send() {
    var f= 100
}
send()
sendMe()

In this code, we defined send function but there is no sendMe function.

The sendMe function will be defined in our C++ app:

// v8_demo.cpp
include "v8.h";
void sendMe() {
    cout << "Greetings from C++ land";
}
int main() {
    Maybe<Local> result = v8::Script::Compile('script.js');
    result->Run();
}

Note: the above snippets won’t run. Its just for demo purposes.

Here, we have the sendMe function.

See what happens. On execution, our v8_demo.cpp runs like this:

MEMORY
    ; v8_demo.cpp
x00    sendMe:
x01        push ""Greetings from C++ land""
x02        call cout
x03        ret
x04
x05    main:
x06        push "script.js"
x07        call Compile
x08        push eax
x09        call Run
x10
x11
x12
x13
x14
x15
x16
x17
x18
x19
x20

see the sendMe function is present. Execution starts from main and proceeds downwards. On x07, the script.js is compiled and pushed to memory.

MEMORY
    ; v8_demo.cpp
x00    sendMe:
x01        push ""Greetings from C++ land""
x02        call cout
x03        ret
x04
x05    main:
x06        push "script.js"
x07 ➥     call Compile
x08        push eax
x09        call Run
x10
x11     ;script.js
x12     mov 90,f
x13     send:
x14         push 100
x15     call send
x16     call sendMe
x17
x18
x19
x20

See our script.js assembly code in memory. :) Here, there is no C++ or JS code. Everything is in assembly. Like I said earlier, we can just demarcate like, here is assembly code from the C++ script and here is assembly code from JS script.

So, when Run x09 is executed,

MEMORY
    ; v8_demo.cpp
x00    sendMe:
x01        push ""Greetings from C++ land""
x02        call cout
x03        ret
x04
x05    main:
x06        push "script.js"
x07        call Compile
x08        push eax
x09 ➥     call Run
x10
x11     ;script.js
x12     mov 90,f
x13     send:
x14         push 100
x15     call send
x16     call sendMe
x17
x18
x19
x20

it jumps to the JS assembly code memory space x12.

Note: Run is a v8 API to run the compiled JS code after compilation.

MEMORY
    ; v8_demo.cpp
x00    sendMe:
x01        push ""Greetings from C++ land""
x02        call cout
x03        ret
x04
x05    main:
x06        push "script.js"
x07        call Compile
x08        push eax
x09        call Run
x10
x11     ;script.js
x12 ➥  mov 90,f
x13     send:
x14         push 100
x15     call send
x16     call sendMe
x17
x18
x19
x20

Here, the code we wrote in script.js is executed in assembly form.

When execution reach x15 call send, it jumps to x13 send: and executes its block, after that, it returns and calls sendMe.

MEMORY
    ; v8_demo.cpp
x00    sendMe:
x01        push ""Greetings from C++ land""
x02        call cout
x03        ret
x04
x05    main:
x06        push "script.js"
x07        call Compile
x08        push eax
x09        call Run
x10
x11     ;script.js
x12     mov 90,f
x13     send:
x14         push 100
x15     call send
x16 ➥  call sendMe
x17
x18
x19
x20

This function wasn’t defined in our script.js, but because it was defined in v8_demo.cpp, it is now available in memory. It can be called because the assembly form of script.js and v8_demo.cpp, now executes in one language and in the same memory space.

So, execution jumps to x00 sendMe:, the start of the sendMe function.

MEMORY
    ; v8_demo.cpp
x00 ➥ sendMe:
x01        push ""Greetings from C++ land""
x02        call cout
x03        ret
x04
x05    main:
x06        push "script.js"
x07        call Compile
x08        push eax
x09        call Run
x10
x11     ;script.js
x12     mov 90,f
x13     send:
x14         push 100
x15     call send
x16     call sendMe
x17
x18
x19
x20

:) Greetings from C++ land is displayed !!

So, this kind of jump is called Crossing the C++/JS boundary.

Likewise call from C++ to JS is feasible, provided the function is defined in JS land.

Conclusion

You see it is quite simple, no magic, no big deal. Whenever you hear "Crossing C++/JS", always picture everything running in assembly and in the same memory space.

In our next articles, we will dive in deep to see how some major Node.js APIs works underneath:

  • setTimeout
  • process.nextTick
  • setImmediate
  • Promise
  • setInterval

If you have any question regarding this or anything I should add, correct or remove, feel free to comment, email or DM me. Thanks !!! 👍

nodejs javascript programming

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

Hire NodeJs Developer

Looking to build dynamic, extensively featured, and full-fledged web applications? **[Hire NodeJs Developer](https://hourlydeveloper.io/hire-dedicated-node-js-developer/ "Hire NodeJs Developer")** to create a real-time, faster, and scalable...

Learning JavaScript: Development Environments for JavaScript Programming

One of the nice things about learning JavaScript these days is that there is a plethora of choices for writing and running JavaScript code. In this article, I’m going to describe a few of these environments and show you the environment I’ll be using in this series of articles.

Learning JavaScript: Data Types and Variables

To paraphrase the title of an old computer science textbook, “Algorithms + Data = Programs.” The first step in learning a programming language such as JavaScript is to learn what types of data the language can work with. The second step is to learn how to store that data in variables. In this article I’ll discuss the different types of data you can work with in a JavaScript program and how to create and use variables to store and manipulate that data.

[ Professor JavaScript ]: Introduction

Professor JavaScript is a JavaScript online learning courses YouTube Channel. Students can learn how to develop codes with JavaScript from basic to advanced levels through the online courses in this YouTube channel.

From imperative to declarative JavaScript

In this post, I will explain why declarative code is better than imperative code. Then I will list some techniques to convert imperative JavaScript to a declarative one in common situations.