들어가기 전에

자바스크립트 개발자라면 알아야 할 33가지 개념 #11 V8 엔진이 JS를 기계 코드로 바꾸는 방법

크롬의 V8엔진을 배우기 전에, 먼저, 기초를 잡고 갑시다. 우리의 모든 시스템은 마이크로프로세서(CPU)를 포함합니다. 지금 컴퓨터 안에 들어있는 그 녀석입니다. 당신이 이 글을 읽는 것도 그 덕이죠.

마이크로프로세서는 전자적인 신호로 동작하는 작은 기계입니다. 그리고 궁극적으로는 job을 수행하죠. 우리는 마이크로프로세서들에게 지시(instruction)를 주죠. 여기서 지시란 것은 마이크로프로세서가 해석(interpret)할 수 있는 언어로 되어 있는 것을 말합니다. 다른 마이크로프로세서들은 다른 언어로 말합니다. 가장 일반적인 언어로는 IA-32, x86-64, MIPS 그리고 ARM이 있습니다. 이러한 언어들은 직접 하드웨어와 소통합니다. 그래서 거기에 쓰여진 코드는 기계어라 불립니다. 우리가 컴퓨터에 쓴 코드는 변형되거나 컴파일되어 기계어가 됩니다.

기계어는 다음과 같이 생겼습니다:

machinecode.png

위 코드는 우리 시스템에서 낮은 레벨의 어떤 메모리 위에 올려져 동작하는 지시(instruction)가 들어있습니다. 프로그램을 동작시키기 위해 이러한 것들을 작성해야 할 필요가 없다는 것은 참으로 행운입니다!

고수준의 언어는 기계어에서 많은 추상화가 이루어져있습니다. 아래 추상화의 수준에서, 자바스크립트가 기계어로부터 얼마나 추상화가 됐는지 알 수 있습니다. C/C++는 상대적으로 하드웨어 언어와 가깝고 다른 고수준 언어들보다 훨씬 빠릅니다.

abstractionlevel.png

이제 자바스크립트 V8엔진으로 다시 돌아가봅시다. V8은 구글이 제공하는 강력한 오픈소스 자바스크립트 엔진입니다. 자바스크립트 엔진이란 무엇일까요? 자바스크립트 엔진이란 자바스크립트 코드를 마이크로프로세서가 이해할 수 있는 더 낮은 수준의 언어 혹은 기계어로 변환해주는 역할을 합니다.

Rhino, JavaScriptCore, SpiderMonkey와 같은 다양한 종류의 자바스크립트 엔진이 존재합니다. 이 엔진들은 ECMAScript 표준을 따릅니다. ECMAScript는 스크립팅 언어를 위한 표준을 정의합니다. 자바스크립트는 ECMAScript 표준을 기반으로 합니다. 이러한 표준은 언어가 어떻게 동작할지 그리고 어떤 특성을 가져야 하는지를 정의합니다. 여기에서 ECMAScript에 대해 더 배울 수 있습니다.

v8engine.png

크롬 V8엔진의 특성은 다음과 같습니다.

  • V8엔진은 C++로 작성됐고 Chrome과 Nodejs에서 사용됩니다.
  • V8엔진은 ECMA-262에 기재된 ECMAScript를 구현했습니다.
  • V8엔진은 standalone으로 동작할 수 있어서 우리는 자바스크립트 엔진을 C++ 프로그램에 내장시킬 수 있습니다.

마지막 항목을 좀 더 깊이 이해해봅시다. V8 엔진은 standalone으로 동작할 수 있고 동시에 우리는 C++로 구현된 함수를 자바스크립트의 새로운 특성으로 넣을 수 있습니다.

js-standalone.png

그래서 예를 들면, print('hello world')는 Node.js에서 유효한 구문이 아닙니다. 컴파일하면 에러를 송출할 것입니다. 하지만 우리는 깃허브에 오픈소스로 제공된 V8엔진의 맨 위에 프린트 문을 추가할 수 있습니다. 그래서 print함수가 native로 동작하게 만들 수 있습니다. 이러한 행위는 자바스크립트가 ECMAScript 표준이 정의하는 자바스크립트 동작보다 더 많은 동작을 하도록 허용해줍니다.

이것은 강력한 기능입니다. C++은 자바스크립트에 비교했을 때, 더 많은 특성을 갖고있기 때문입니다. C++은 하드 드라이브에 있는 파일과 폴더를 다룰 때 하드웨어와 훨씬 더 가까이 있습니다.

우리가 C++로 된 코드를 작성하도록 허용하고 자바스크립트에서 동작 가능하게 만드는 것이 가능하고 그래서 우리는 자바스크립트에 더 많은 특성을 추가할 수 있습니다.

Node.js 자체는 V8엔진, C++ 구현입니다. C++로 구현된 V8엔진은 서버사이드 프로그래밍과 네트워킹 어플리케이션을 다룰 수 있게 해줍니다.

엔진 내부의 오픈소스 코드를 조금 봅시다. 소스코드를 보기 위해 v8/samples/shell.cc 앞의 링크를 클릭해보세요.

C++코드에서 PrintRead와 같은 함수들의 구현을 볼 수 있습니다. 이 함수들은 Node.js에서 네이티브로는 불가능한 것들입니다.

아래에서, Print함수의 구현을 볼 수 있습니다. print()가 Node.js에서 호출될 때마다, 이 함수는 콜백을 만들고 그 함수가 실행됩니다.

// 자바스크립트에서 'print' 함수가 호출될 때마다, v8엔진에 의해 콜백이 호출됩니다.
// 스페이스와 새로운 라인을 기준으로 분리된 stdout에 있는 인자들을 프린트합니다.
void Print(const v8::FunctionCallbackInfo<v8::Value>& args) {
  bool first = true;
  for (int i=0; i<args.length(); i++) {
    v8::HandleScope handle_scope(args.GetIsolate());
    if (first) {
      first = false;
    } else {
      print(" ");
    }
    v8::String::Utf8Value str(args.GetIsolate(), args[i]);
    const char* cstr = ToCString(str);
    printf("%s", cstr);
  }
  printf("\n");
  fflush(stdout);
}

비슷하게, 우리도 C++ 내부에 다른 함수를 추가적으로 구현하여 Node.js에서 인식되도록 할 수 있습니다.

machinecode2.png

이건 확실히 간단한 서술로 끝내기엔 너무나 많은 양입니다. V8엔진은 뒤에서 이러한 많은 양의 일을 합니다.

이제 Node.js가 어떻게 동작하는지 이해가 분명해졌을 것입니다. 그리고 Chrome의 V8 엔진이 실제로 무엇을 하는지도요.

article을 읽어주셔서 감사합니다.

medium 계정이 있으시다면 저자의 계정을 Follow하시면 더욱 많은 정보를 얻을 수 있습니다.