HW부터 JavaScript까지 — V8 엔진 실행 구조

한 문장 요약

JS 코드 → V8이 파싱 → Ignition이 바이트코드 실행 → 반복 코드는 TurboFan이 JIT 컴파일 → 기계어 직접 실행 → CPU → 트랜지스터


전체 실행 스택

┌─────────────────────────────────────────────────┐
│              JavaScript 소스 코드               │
│  function add(a, b) { return a + b; }           │
│  console.log(add(1, 2));                        │
└──────────────────────┬──────────────────────────┘
                       │  V8 엔진 (C++로 작성된 바이너리)
                       ▼
┌─────────────────────────────────────────────────┐
│         1단계: 파싱 (Parser)                     │
│                                                 │
│  소스 코드 → 토큰 → AST                         │
│                                                 │
│  AST 예시:                                      │
│  FunctionDeclaration (add)                      │
│  ├─ params: [a, b]                              │
│  └─ body: ReturnStatement                       │
│           └─ BinaryExpression(+)               │
│              ├─ Identifier(a)                  │
│              └─ Identifier(b)                  │
│                                                 │
│  Lazy Parsing: 사용 안 되는 함수는 나중에 파싱  │
└──────────────────────┬──────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────────┐
│         2단계: Ignition (인터프리터)             │
│                                                 │
│  AST → 바이트코드 (빠른 시작, 낮은 메모리)       │
│                                                 │
│  add 함수 바이트코드:                           │
│  ┌─────────────────────────────────────────┐   │
│  │  Ldar a        // a를 accumulator로 로드│   │
│  │  Add r0, [0]   // r0(b)와 더하기        │   │
│  │  Return         // accumulator 반환     │   │
│  └─────────────────────────────────────────┘   │
│                                                 │
│  Ignition은 레지스터 기반 VM                    │
│  (Python의 스택 기반과 다름)                    │
│                                                 │
│  동시에 프로파일링 데이터 수집:                  │
│  - 함수 호출 횟수                               │
│  - 인자 타입 정보 (항상 숫자? 문자열?)          │
└──────────────────────┬──────────────────────────┘
                       │  반복 실행 감지 (Hot Code)
                       ▼
┌─────────────────────────────────────────────────┐
│         3단계: TurboFan (JIT 컴파일러)           │
│                                                 │
│  프로파일링 정보 활용 → 최적화된 기계어 생성     │
│                                                 │
│  최적화 예시 (add가 항상 숫자를 받으면):         │
│  ┌─────────────────────────────────────────┐   │
│  │  // 가정: a, b는 항상 SMI(Small Integer)│   │
│  │  mov eax, [a]                           │   │
│  │  add eax, [b]     ← 단 3줄 기계어       │   │
│  │  ret                                    │   │
│  └─────────────────────────────────────────┘   │
│                                                 │
│  Speculative Optimization (추측 최적화):        │
│  타입이 바뀌면 → Deoptimize → Ignition으로 복귀 │
│                                                 │
│  Hidden Class (V8 내부):                        │
│  ┌──────────────────────────────────────────┐  │
│  │  let obj = {x: 1}  → Class A {x: offset0}│  │
│  │  obj.y = 2         → Class B {x:0, y:4}  │  │
│  │  → 프로퍼티 접근을 오프셋으로 최적화      │  │
│  └──────────────────────────────────────────┘  │
└──────────────────────┬──────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────────┐
│         V8 힙 메모리 구조                        │
│                                                 │
│  ┌──────────────────────────────────────────┐  │
│  │  Young Generation (New Space)            │  │
│  │  ┌─────────────┬────────────────────┐   │  │
│  │  │  From Space │  To Space          │   │  │
│  │  │  (현재 활성) │  (GC 복사 대상)    │   │  │
│  │  └─────────────┴────────────────────┘   │  │
│  │  → Minor GC (Scavenge): 단명 객체 처리  │  │
│  │                                          │  │
│  │  Old Generation                          │  │
│  │  - 오래 살아남은 객체                   │  │
│  │  → Major GC (Mark-Compact)               │  │
│  │                                          │  │
│  │  Large Object Space                      │  │
│  │  Code Space (JIT 기계어 저장)            │  │
│  └──────────────────────────────────────────┘  │
└──────────────────────┬──────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────────┐
│         OS 시스템 콜                             │
│  console.log → write(1, buf, n) 시스템 콜       │
└──────────────────────┬──────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────────┐
│         CPU Fetch-Decode-Execute                │
│  JIT된 기계어 → 네이티브 실행 (C 수준 성능)      │
│  Ignition 바이트코드 → V8 C++ 코드 경유 실행    │
└──────────────────────┬──────────────────────────┘
                       │
                       ▼
┌─────────────────────────────────────────────────┐
│         트랜지스터 / 전기 신호                   │
└─────────────────────────────────────────────────┘

JIT 컴파일의 2가지 경로

JavaScript 함수 실행

┌──────────────────────────────────────────────────┐
│ 경로 1: 처음 실행 또는 가끔 호출                  │
│                                                  │
│  Ignition 바이트코드 → 인터프리팅                │
│  속도: 보통 (Python보다 빠름, 네이티브보다 느림)  │
└──────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────┐
│ 경로 2: 반복 호출 (Hot Function)                  │
│                                                  │
│  TurboFan JIT 컴파일 → 기계어 캐시               │
│  속도: 네이티브 코드 수준                         │
│  조건: 타입이 일정해야 최적화 유지               │
└──────────────────────────────────────────────────┘

타입이 갑자기 바뀌면 (add에 문자열 전달):
  최적화 해제(Deopt) → Ignition으로 폴백 → 재프로파일링

Hidden Class: 동적 타입을 정적처럼 최적화

// 이렇게 쓰면 최적화 유리 (프로퍼티 순서 일정)
function Point(x, y) {
  this.x = x;  // 항상 먼저
  this.y = y;  // 항상 나중
}

내부 Hidden Class 전이:
  Point()  → Class C0 {}
  .x = 1   → Class C1 { x: offset 0 }
  .y = 2   → Class C2 { x: offset 0, y: offset 4 }

// 이렇게 쓰면 최적화 불리 (프로퍼티 순서 다름)
let a = {}; a.x = 1; a.y = 2;  // Class C2
let b = {}; b.y = 2; b.x = 1;  // Class C3 (다른 클래스!)
→ 같은 구조인데도 다른 Hidden Class → 인라인 캐시 미스

핵심 요약

app.js
  ↓ V8 파서 (AST 생성)
AST
  ↓ Ignition (바이트코드 컴파일 + 프로파일링)
바이트코드 (레지스터 기반 VM)
  ↓ 반복 실행 감지
  ├─ 가끔 실행 → Ignition 인터프리팅 (C++ 코드 실행)
  └─ 자주 실행 → TurboFan JIT 컴파일 → 기계어 직접 실행
                                          ↓
                                   CPU Fetch-Decode-Execute
                                          ↓
                                   트랜지스터 ON/OFF

핵심: JS는 인터프리터도 아니고 완전 컴파일도 아닌
      JIT(Just-In-Time) 방식으로 실행된다.
      뜨거운(Hot) 코드만 선택적으로 네이티브로 컴파일.