Skip to content
Sameeri Pavan Kumar Marryboyina edited this page Dec 14, 2016 · 10 revisions

Inside the V8 code repository, sits a source file, d8.cc.

While V8 is a library project, d8 yields an executable.

A code block from the source file can verify this statement

int main(int argc, char* argv[]) {
  return v8::Shell::Main(argc, argv);
}

The main responsibility of d8 is to act as a shell.

It wraps the V8 engine and exposes certain helpful methods.

We can totally use d8 to play around and understand the underlying engine.

But the only gotcha is

Build V8, to get d8

That's the only way.

Since V8 is a huge project with various portability requirements, its build process is a little complicated and requires some external dependencies and setup and each varies for the platform you work on.

But it is definitely well documented in the Wiki

It might seem as a dark art or some magical procedure and people might project it that way, but when you think of it, it is code and code must be built. That is all.

I have built V8 on Windows and if i can, anyone can!

Once you have successfully built V8, its playtime.

We can run d8 at the command line.

c:\sameeri\adventures\v8\build\Debug>d8
V8 version 5.7.0 (candidate)
d8>

and it presents us the shell.

One might have noticed such an interface at the node-repl, or the browsers-console, but essentially it is the same idea.

Work with your snippets of JS code.

d8> var x = 5;
undefined
d8> x
5

And then you might type away some code like this.

d8> for (var i=0; i<5 ; i++){ console.log(i)}
(d8):1: ReferenceError: console is not defined
for (var i=0; i<5 ; i++){ console.log(i)}
                          ^
ReferenceError: console is not defined
    at (d8):1:27

What?

No console??

Let us step away from the desk for a second and grab some coffee. I need it. Everything I know about JavaScript is wrong! Why??

lol.

The console object that we are all too familiar is a thing we cannot take for granted.

It is a host object and is only available if the environment hosting the JS engine provides us with such an object.

Nothing too fancy.

The web browsers and node do. They provide a console object.

And everybody uses it to log messages to the console.

But d8 does not have it.

Maybe the guys who wrote d8 did not see a need for this object implementation.

So then the question that comes to mind is well, what are all provided?

The truth about JavaScript Engines

JavaScript Engines implement the ECMAScript standard.

They only provide implementations for the Native objects.

Nothing more. Nothing less.

So a console object is foreign territory.

The beauty of a JavaScript engine is in its ability to be embeddable.

One can include V8 as a project dependency and pass JavaScript code to it for execution.

Thus the concept of a host arises.

A host is any such program that embeds the JavaScript engine inside it.

Thus the host provides a platform to execute JavaScript code.

And then developers can target this platform.

The host exposes objects & functions that it thinks is necessary.

In the context of node (as a platform), the essential goal of node is to give ability to a developer to write server code.

Thus it becomes important for node to expose network objects (tcp, udp, http, dns), file objects and so on.

In the context of the browser (as a platform), the essential goal of browser is to provide a way to work with the html tree and associate graphical units with reactive capabilities by providing events and do much more like provide objects like WebSocket or the XmlHttpRequest to make certain network calls.

In the same light, d8 is a host. It wraps the V8 engine and provides additional facilities that it thinks are necessary.

Well, who can tell us this information?

  • No docs exist for d8
  • Source code is the source of all truth. So we could study the code and figure it out. Do you know how to read C++?
  • Hack away with certain fundamentals in mind.

The this and the global object

In JavaScript, all code runs inside an object.

At the root of all objects is the global object.

Anywhere in our code, we can ask what is the value of this.

Code runs in execution contexts.

At the global level, lies the global execution context.

The this in the global execution context points to the global object.

If you create variables inside the global execution context, they become properties of the global object.

We can verify all of this using d8.

Let us hack away!

c:\sameeri\adventures\v8\build\Debug>d8
V8 version 5.7.0 (candidate)
d8> this
[object global]
d8> global
(d8):1: ReferenceError: global is not defined
global
^
ReferenceError: global is not defined
    at (d8):1:1

d8>

Okay! Even though this is the global object, there is nothing inside called global.

That is interesting.

d8> this.x
undefined
d8> x
(d8):1: ReferenceError: x is not defined
x
^
ReferenceError: x is not defined
    at (d8):1:1
d8>

I try to access the variable x. Since it is not defined, i get a ReferenceError.

I try to access the property on the global object referenced by this and it says that x is not defined.

This is expected behavior.

d8> var x = 21;
undefined
d8> x
21
d8> this.x
21
d8> this.x === x
true

Now if we create that variable and assign it a value, firstly a variable is created.

Secondly it becomes a property of the global object.

This is one of the most important things for anyone to grasp if they are new to JavaScript.

Now how can we find what all is available for us?

We can inspect the global object and all its properties.

Wait, but we don't have a console.log, so how can we inspect?

Using the native Object.getOwnPropertyNames(object) call.

c:\sameeri\adventures\v8\build\Debug>d8
V8 version 5.7.0 (candidate)
d8> Object.getOwnPropertyNames(this)
["EvalError", "RegExp", "RangeError", "parseFloat", "Uint32Array", "Intl", "Func
tion", "TypeError", "eval", "Promise", "isFinite", "undefined", "ReferenceError"
, "Float32Array", "Set", "Uint8ClampedArray", "NaN", "WeakSet", "Math", "unescap
e", "Object", "Reflect", "Float64Array", "decodeURI", "Number", "URIError", "Boo
lean", "Error", "Proxy", "Date", "parseInt", "Map", "Uint16Array", "encodeURICom
ponent", "String", "Infinity", "encodeURI", "Symbol", "escape", "Uint8Array", "I
nt32Array", "WeakMap", "SyntaxError", "DataView", "decodeURIComponent", "JSON",
"ArrayBuffer", "Int16Array", "isNaN", "Int8Array", "Array", "print", "printErr",
 "write", "read", "readbuffer", "readline", "load", "quit", "version", "Realm",
"performance", "Worker", "os", "arguments"]

That is Awesome!

Quickly we can separate all the Native objects out and form a list

["EvalError", "RegExp", "RangeError", "parseFloat", "Uint32Array", "Intl", "Func
tion", "TypeError", "eval", "Promise", "isFinite", "undefined", "ReferenceError"
, "Float32Array", "Set", "Uint8ClampedArray", "NaN", "WeakSet", "Math", "unescap
e", "Object", "Reflect", "Float64Array", "decodeURI", "Number", "URIError", "Boo
lean", "Error", "Proxy", "Date", "parseInt", "Map", "Uint16Array", "encodeURICom
ponent", "String", "Infinity", "encodeURI", "Symbol", "escape", "Uint8Array", "I
nt32Array", "WeakMap", "SyntaxError", "DataView", "decodeURIComponent", "JSON",
"ArrayBuffer", "Int16Array", "isNaN", "Int8Array", "Array"]

look like they are all Native.

The only way to confirm is to search for each of those in the ECMAScript spec, if you are unsure, that is.

The others don't look native.

I mean, common. If there was something called print we would have known it, right?

Absolutely.

The whole industry would be having arguments as to how print() is better than console.log() and have wars for a simple conceptual principle and tool. Just Kidding!

But to be sure, we can check code.

I did a search for things like print, write on here but since printf(), fwrite() are functions it yields a lot of results.

We can have better luck with things like version, Realm, os etc.

But ultimately we come across this code block:

Local<ObjectTemplate> Shell::CreateGlobalTemplate(Isolate* isolate) {
  Local<ObjectTemplate> global_template = ObjectTemplate::New(isolate);
  global_template->Set(
      String::NewFromUtf8(isolate, "print", NewStringType::kNormal)
          .ToLocalChecked(),
      FunctionTemplate::New(isolate, Print));
  global_template->Set(
      String::NewFromUtf8(isolate, "printErr", NewStringType::kNormal)
          .ToLocalChecked(),
      FunctionTemplate::New(isolate, PrintErr));
  global_template->Set(
      String::NewFromUtf8(isolate, "write", NewStringType::kNormal)
          .ToLocalChecked(),
      FunctionTemplate::New(isolate, Write));
  global_template->Set(
      String::NewFromUtf8(isolate, "read", NewStringType::kNormal)
          .ToLocalChecked(),
      FunctionTemplate::New(isolate, Read));
  global_template->Set(
      String::NewFromUtf8(isolate, "readbuffer", NewStringType::kNormal)
          .ToLocalChecked(),
      FunctionTemplate::New(isolate, ReadBuffer));
  global_template->Set(
      String::NewFromUtf8(isolate, "readline", NewStringType::kNormal)
          .ToLocalChecked(),
      FunctionTemplate::New(isolate, ReadLine));
  global_template->Set(
      String::NewFromUtf8(isolate, "load", NewStringType::kNormal)
          .ToLocalChecked(),
      FunctionTemplate::New(isolate, Load));
  // Some Emscripten-generated code tries to call 'quit', which in turn would
  // call C's exit(). This would lead to memory leaks, because there is no way
  // we can terminate cleanly then, so we need a way to hide 'quit'.
  if (!options.omit_quit) {
    global_template->Set(
        String::NewFromUtf8(isolate, "quit", NewStringType::kNormal)
            .ToLocalChecked(),
        FunctionTemplate::New(isolate, Quit));
  }
  global_template->Set(
      String::NewFromUtf8(isolate, "version", NewStringType::kNormal)
          .ToLocalChecked(),
      FunctionTemplate::New(isolate, Version));
  global_template->Set(
      Symbol::GetToStringTag(isolate),
      String::NewFromUtf8(isolate, "global", NewStringType::kNormal)
          .ToLocalChecked());

  // Bind the Realm object.
  Local<ObjectTemplate> realm_template = ObjectTemplate::New(isolate);
  realm_template->Set(
      String::NewFromUtf8(isolate, "current", NewStringType::kNormal)
          .ToLocalChecked(),
      FunctionTemplate::New(isolate, RealmCurrent));
  realm_template->Set(
      String::NewFromUtf8(isolate, "owner", NewStringType::kNormal)
          .ToLocalChecked(),
      FunctionTemplate::New(isolate, RealmOwner));
  realm_template->Set(
      String::NewFromUtf8(isolate, "global", NewStringType::kNormal)
          .ToLocalChecked(),
      FunctionTemplate::New(isolate, RealmGlobal));
  realm_template->Set(
      String::NewFromUtf8(isolate, "create", NewStringType::kNormal)
          .ToLocalChecked(),
      FunctionTemplate::New(isolate, RealmCreate));
  realm_template->Set(
      String::NewFromUtf8(isolate, "createAllowCrossRealmAccess",
                          NewStringType::kNormal)
          .ToLocalChecked(),
      FunctionTemplate::New(isolate, RealmCreateAllowCrossRealmAccess));
  realm_template->Set(
      String::NewFromUtf8(isolate, "dispose", NewStringType::kNormal)
          .ToLocalChecked(),
      FunctionTemplate::New(isolate, RealmDispose));
  realm_template->Set(
      String::NewFromUtf8(isolate, "switch", NewStringType::kNormal)
          .ToLocalChecked(),
      FunctionTemplate::New(isolate, RealmSwitch));
  realm_template->Set(
      String::NewFromUtf8(isolate, "eval", NewStringType::kNormal)
          .ToLocalChecked(),
      FunctionTemplate::New(isolate, RealmEval));
  realm_template->SetAccessor(
      String::NewFromUtf8(isolate, "shared", NewStringType::kNormal)
          .ToLocalChecked(),
      RealmSharedGet, RealmSharedSet);
  global_template->Set(
      String::NewFromUtf8(isolate, "Realm", NewStringType::kNormal)
          .ToLocalChecked(),
      realm_template);

  Local<ObjectTemplate> performance_template = ObjectTemplate::New(isolate);
  performance_template->Set(
      String::NewFromUtf8(isolate, "now", NewStringType::kNormal)
          .ToLocalChecked(),
      FunctionTemplate::New(isolate, PerformanceNow));
  global_template->Set(
      String::NewFromUtf8(isolate, "performance", NewStringType::kNormal)
          .ToLocalChecked(),
      performance_template);

  Local<FunctionTemplate> worker_fun_template =
      FunctionTemplate::New(isolate, WorkerNew);
  Local<Signature> worker_signature =
      Signature::New(isolate, worker_fun_template);
  worker_fun_template->SetClassName(
      String::NewFromUtf8(isolate, "Worker", NewStringType::kNormal)
          .ToLocalChecked());
  worker_fun_template->ReadOnlyPrototype();
  worker_fun_template->PrototypeTemplate()->Set(
      String::NewFromUtf8(isolate, "terminate", NewStringType::kNormal)
          .ToLocalChecked(),
      FunctionTemplate::New(isolate, WorkerTerminate, Local<Value>(),
                            worker_signature));
  worker_fun_template->PrototypeTemplate()->Set(
      String::NewFromUtf8(isolate, "postMessage", NewStringType::kNormal)
          .ToLocalChecked(),
      FunctionTemplate::New(isolate, WorkerPostMessage, Local<Value>(),
                            worker_signature));
  worker_fun_template->PrototypeTemplate()->Set(
      String::NewFromUtf8(isolate, "getMessage", NewStringType::kNormal)
          .ToLocalChecked(),
      FunctionTemplate::New(isolate, WorkerGetMessage, Local<Value>(),
                            worker_signature));
  worker_fun_template->InstanceTemplate()->SetInternalFieldCount(1);
  global_template->Set(
      String::NewFromUtf8(isolate, "Worker", NewStringType::kNormal)
          .ToLocalChecked(),
      worker_fun_template);

  Local<ObjectTemplate> os_templ = ObjectTemplate::New(isolate);
  AddOSMethods(isolate, os_templ);
  global_template->Set(
      String::NewFromUtf8(isolate, "os", NewStringType::kNormal)
          .ToLocalChecked(),
      os_templ);

  return global_template;
}

A function that creates stuff on the global object and returns it.

Cool! So our guess is correct.

But it is very very interesting to observe these patterns.

 Local<ObjectTemplate> os_templ = ObjectTemplate::New(isolate);
  AddOSMethods(isolate, os_templ);
  global_template->Set(
      String::NewFromUtf8(isolate, "os", NewStringType::kNormal)
          .ToLocalChecked(),
      os_templ);

for objects.

global_template->Set(
      String::NewFromUtf8(isolate, "load", NewStringType::kNormal)
          .ToLocalChecked(),
      FunctionTemplate::New(isolate, Load));

for functions.

What we can learn from this is whenever we use the os object or the load function, we are calling the C++ code defined in this file.

Awesomeness!

But now, we can go ahead and use print function in our initial code example replacing console.log.

Rewriting that code piece,

c:\sameeri\adventures\v8\build\Debug>d8
V8 version 5.7.0 (candidate)
d8> for(var i=0; i<5; i++) {print(i)}
0
1
2
3
4
undefined

Excellante!

Native code and JS code

If you write a JS function, something like

function f(){
 print("Hello JS")
}

you can invoke it, using the name followed by ()

d8> function f(){ print ("Hello JS") }
undefined
d8> f()
Hello JS
undefined

or you can ask for its definition, by simply typing out its name.

d8> function f(){ print ("Hello JS") }
undefined
d8> f
function f(){ print ("Hello JS") }

And the function definition is displayed for us. In plain text. No secrets.

But sometimes, you can have this weird thing glaring at you [native code] when you ask for a function definition, what does that mean??

It means that this just acts as a JavaScript named alias for actual code written in C++ or whatever the native language implementation is. It is simply not JavaScript code.

For instance the String constructor function defined in V8 and the version function defined in d8, both are native implementations, so you will get that weird [native code] as an output instead of an actual implementation.

c:\sameeri\adventures\v8\build\Debug>d8
V8 version 5.7.0 (candidate)
d8> var s = String("sameeri")
undefined
d8> s
"sameeri"
d8> String
function String() { [native code] }
d8> version()
"5.7.0 (candidate)"
d8> version
function version() { [native code] }
d8>

I think that's all there is at a basic level what we need to know about d8.

There is a lot of others things we could learn from d8, but we will learn those some other day!

Love, Sameeri

PS:

Learning should come in bits & pieces. And we should be happy that we learnt something!

Clone this wiki locally