Edited at
PLAIDDay 17

V8の基本的なAPIを学ぶ

More than 1 year has passed since last update.

PLAID Advent Calendar 2017 の17日目。今回はV8についてです。


記事の内容


  • V8を使った簡単なソースコードを読む

  • 注)V8自体のソースではない

  • そこで使われているコードについてV8のwikiV8 API Reference Guideで調べる

  • 注)TurboFan/Ignition などの熱い部分には触れていない


前提知識


  • JavaScriptを知っている

  • C++は知らなくてもいい

  • Web開発をしたことがある


背景

弊社プレイドが提供しているKARTEというサービスの管理画面ではブラウザとしてGoogle Chromeを使用していただくことを推奨しており、またそのKARTEのサーバーサイドでは主にNode.jsを使用しています。つまりその内部で動いているV8にはがっつりお世話になっています。そこで、V8のAPIについて少しでも理解しておくことでV8、Node.js、Chromeそれ自体の理解が深まったり、そのソースコードを読む際に役に立ったりすると思い簡単に調べてみました。


V8とは

まずはそもそもV8とは何かについて。wikiのWhat is V8?によると、


V8 is Google’s open source high-performance JavaScript engine, written in C++. It is used in Google Chrome, the open source browser from Google, and in Node.js, among others. It implements ECMAScript as specified in ECMA-262, and runs on Windows 7 or later, macOS 10.12+, and Linux systems that use IA-32, ARM, or MIPS processors. V8 can run standalone, or can be embedded into any C++ application.


と書かれており、ざっくりまとめるとV8とは次のようなものです。


  • Google製のJavaScriptの実行エンジン

  • C++でかかれている

  • Google ChromeやNode.jsなどで使用されている

  • 様々なOS上で動く

  • スタンドアロンでも動く

  • C++アプリケーションに組み込める

例えばNode.jsでは、メインのコードはC++で書かれており、ファイルなどから読み込んだ文字列をJavaScriptとして実行する所でライブラリとしてV8を使って、コンパイルと実行をしています。


Hello, World

それではV8を使った基本的なコードを見てみましょう。基本的なコードといえばHello, World。V8のwiki内のGetting-Started-with-Embeddingのページに‘Hello’ + ‘, World’というJavaScriptの式をコンパイルして実行結果を出力するコードがリンクされているのでそちらを見てみます。


hello-world.cc

// Copyright 2015 the V8 project authors. All rights reserved.

// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "include/libplatform/libplatform.h"
#include "include/v8.h"
using namespace v8;
int main(int argc, char* argv[]) {
// Initialize V8.
V8::InitializeICUDefaultLocation(argv[0]);
V8::InitializeExternalStartupData(argv[0]);
Platform* platform = platform::CreateDefaultPlatform();
V8::InitializePlatform(platform);
V8::Initialize();
// Create a new Isolate and make it the current one.
Isolate::CreateParams create_params;
create_params.array_buffer_allocator =
v8::ArrayBuffer::Allocator::NewDefaultAllocator();
Isolate* isolate = Isolate::New(create_params);
{
Isolate::Scope isolate_scope(isolate);
// Create a stack-allocated handle scope.
HandleScope handle_scope(isolate);
// Create a new context.
Local<Context> context = Context::New(isolate);
// Enter the context for compiling and running the hello world script.
Context::Scope context_scope(context);
// Create a string containing the JavaScript source code.
Local<String> source =
String::NewFromUtf8(isolate, "'Hello' + ', World!'",
NewStringType::kNormal).ToLocalChecked();
// Compile the source code.
Local<Script> script = Script::Compile(context, source).ToLocalChecked();
// Run the script to get the result.
Local<Value> result = script->Run(context).ToLocalChecked();
// Convert the result to an UTF8 string and print it.
String::Utf8Value utf8(result);
printf("%s\n", *utf8);
}
// Dispose the isolate and tear down V8.
isolate->Dispose();
V8::Dispose();
V8::ShutdownPlatform();
delete platform;
delete create_params.array_buffer_allocator;
return 0;
}

Hello, Worldの割に大分長いですが大体こんな感じでコードを実行しているようです。


  1. V8の初期化のための処理

  2. Isolateインスタンス作成

  3. HandleScope作成

  4. Context作成

  5. JavaScriptのソースコードとなる文字列を作成

  6. その文字列をコンパイル

  7. 実行して結果をV8のオブジェクトとして取得

  8. UTF8文字列に変換して出力

  9. 終了のための処理

どうやらHello, Worldを理解するにはIsolate, HandleScope, Local, Contextとはなんなのかを知る必要がありそうです。ということでそれぞれどういった概念なのかを見ていきます。


Isolateとは

まずはIsolateです。公式のドキュメントによると、


An isolate is a VM instance with its own heap.

- Getting Started with Embedding

Isolate represents an isolated instance of the V8 engine. V8 isolates have completely separate states. Objects from one isolate must not be used in other isolates. The embedder can create multiple isolates and use them in parallel in multiple threads. An isolate can be entered by at most one thread at any given time. The Locker/Unlocker API must be used to synchronize.

- V8 API Reference Guide



  • V8のisolated(隔離された)な実行環境

  • 複数のIsolateインスタンス間で状態を共有しない

  • あるIsolateインスタンスのObjectは別のIsolateインスタンスで使ってはいけない

  • 開発者は複数のisolateを作ることができ、マルチスレッドで並列に使うことができる

  • 1つのIsolateに1スレッドで、同期するならLocker/Unlocker APIを使う

というものです。例えばChromeではメインのIsolateインスタンスとworkerで使われるIsolateインスタンスなどがあり、それらを並列実行できるようになっています。

(workerについては弊社の@otolabによる従来のWebアプリの常識を変える! Service WorkerがもたらすWebの未来という記事でWeb WorkerからService Workerまで触れているので興味のある方はご覧になってみてください。 [追記] 2017年ver.はこちら→ Service Worker周りの知識をアップデートする(2017年クリスマス版)


Contextとは

Hello, Worldのコードに出てきた順番とは異なりますが、Isolateと絡んでくるので先にContextについて書きます。こちらも公式ドキュメントによると、


In V8, a context is an execution environment that allows separate, unrelated, JavaScript applications to run in a single instance of V8. You must explicitly specify the context in which you want any JavaScript code to be run.

- Embedder's Guide

A sandboxed execution context with its own set of built-in objects and functions.

- V8 API Reference Guide



  • 独立したJavaScriptをV8インスタンス(Isolate)で実行するためのsand-box化された実行環境

  • JavaScriptを実行するためにはContextを指定する必要がある

  • Contextそれぞれにbuilt-inオブジェクトや関数が紐付いている

例えばiframeのあるページではメインのwindowのcontextとiframe用のcontextがあり、Chrome拡張のスクリプトがあればそれも別のcontextです。(ただし、それらは1つのIsolateインスタンスで動くので並列で実行されているわけではありません。)

"例えば"、と書きましたがそもそもContextを用意している動機はブラウザで各windowとiframeがそれぞれの実行環境を持てるようにするためだったようです。(Embedder's Guideより)

また、Web開発の世界で"same origin policy"(同一生成元ポリシー。異なる環境でのスクリプト実行を制限するためのセキュリティのポリシー)という言葉を聞いたことがあるかもしれませんが、そこでのoriginとはV8のレイヤでいうとContextのことを指しており、異なるContextへのアクセスはV8のレイヤで制限されています。


HandleScope / Local とは

最後にHandleScope / Localについてです。Getting Started with Embeddingによると



  • A local handle is a pointer to an object. All V8 objects are accessed using handles, they are necessary because of the way the V8 garbage collector works.

  • A handle scope can be thought of as a container for any number of handles. When you've finished with your handles, instead of deleting each one individually you can simply delete their scope.



  • local handle(Local)はV8オブジェクトへのポインタでGCのために必要

  • handleScopeは複数のlocal handleを管理するもの

  • local handleを解放するときはそのhandleScopeを削除すればよい

とあり、雑にまとめるとローカル変数とそのスコープを表したものです。JavaScriptでは関数スコープがあるので、主に関数を実行するときにHandleScopeが作成されます。

Hello, Worldのコード上でhandleとしてlocal handle(Local)しか使用されていませんが、他にもPersistentという永続的なオブジェクトを扱うためのhandleもあります。


ここまででHello, Worldのコード内で使われていたIsolate, HandleScope, Local, Contextについてそれぞれどういうものなのかを簡単に見てきました。これらが何なのかわかったところでもう一度Hello, Worldのコードを見てみると、より理解しやすくなったのではないかと思います。


おわりに

今回はV8のwikiV8 API Reference Guideを見ながら基本的なAPIについて学びました。

そういえばMicrosoftがNapa.jsというNode.jsからでも使えるマルチスレッドのライブラリを公開していますが、どのようにマルチスレッドを実現しているかについてはここまでよんでくださった方はなんとなく想像できるかと思います。並列処理したい分だけIsolateインスタンスを作成してそれぞれ実行している、ということですね。Napa.jsについては弊社の@makinoyによるMicrosoftのNapa.jsでJavaScriptをマルチスレッド化するでまとめられていますので、興味のある方はそちらもご覧になってみてください。