CtrlK
BlogDocsLog inGet started
Tessl Logo

mcollina/nodejs-core

Debugs native module crashes, optimizes V8 performance, configures node-gyp builds, writes N-API/node-addon-api bindings, and diagnoses libuv event loop issues in Node.js. Use when working with C++ addons, native modules, binding.gyp, node-gyp errors, segfaults, memory leaks in native code, V8 optimization/deoptimization, libuv thread pool tuning, N-API or NAN bindings, build system failures, or any Node.js internals below the JavaScript layer.

99

Quality

99%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

v8-jit-compilation.mdrules/

name:
v8-jit-compilation
description:
V8 JIT compilation - TurboFan, optimization/deoptimization patterns
metadata:
{"tags":"v8, jit, turbofan, optimization, deoptimization, ignition, performance"}

V8 JIT Compilation

V8 uses a multi-tier compilation system to balance startup time and peak performance. Understanding this pipeline helps write code that V8 can optimize effectively.

Compilation Pipeline

JavaScript Source
       ↓
    Parser
       ↓
      AST
       ↓
   Ignition (Interpreter)
       ↓ (with profiling)
   TurboFan (JIT Compiler)
       ↓
   Machine Code

Ignition (Interpreter)

Ignition is V8's bytecode interpreter. It:

  • Compiles JavaScript to bytecode quickly
  • Executes code immediately
  • Collects type feedback for optimization
# View generated bytecode
node --print-bytecode app.js

# Filter bytecode for specific function
node --print-bytecode --print-bytecode-filter=myFunction app.js

Example bytecode:

function add(a, b) {
  return a + b;
}
Bytecode:
  Ldar a1          ; Load argument 1 to accumulator
  Add a0, [0]      ; Add argument 0 to accumulator
  Return           ; Return accumulator

TurboFan (Optimizing Compiler)

TurboFan compiles hot functions to optimized machine code:

# Trace TurboFan compilation
node --trace-opt app.js

# Trace with more detail
node --trace-turbo app.js

# Generate TurboFan IR graphs (JSON format)
node --trace-turbo-graph app.js

Optimization Tiers

Cold Code (executed once)
    ↓
Warm Code (interpreted, collecting feedback)
    ↓
Hot Code (compiled by TurboFan)

Tier-Up Thresholds

V8 decides to optimize based on:

  • Invocation count
  • Loop iterations
  • Collected type feedback quality
# Lower tier-up threshold for testing
node --interrupt-budget=100 app.js

Type Feedback and Speculation

TurboFan uses type feedback to speculate on types:

function add(a, b) {
  return a + b;
}

// V8 sees: add(1, 2), add(3, 4), add(5, 6)
// TurboFan speculates: a and b are always numbers
// Generates: optimized integer addition machine code

Deoptimization

When speculation fails, V8 deoptimizes:

function add(a, b) {
  return a + b;
}

for (let i = 0; i < 100000; i++) {
  add(i, i + 1);  // Optimized for numbers
}

add("hello", "world");  // Type mismatch!
// DEOPTIMIZATION: eager - not a number
# Trace deoptimization
node --trace-deopt app.js

# Output:
# [deoptimizing (DEOPT eager): begin ... bailout_type: deopt]
# [deoptimizing (DEOPT eager): ... Reason: not a number]

Common Deoptimization Reasons

ReasonDescriptionFix
not a numberExpected number, got something elseType consistency
wrong mapObject shape changedConsistent object structure
out of boundsArray access beyond lengthBounds checking
minus zeroOperation produced -0Avoid -0 producing operations
overflowInteger overflowUse BigInt for large numbers
holeAccessed array holeAvoid sparse arrays

Writing Optimizable Code

Type Stability

// BAD: Type instability
function process(value) {
  if (typeof value === 'number') {
    return value * 2;
  }
  return value + value;  // String concatenation
}

// GOOD: Type-stable code
function processNumber(value) {
  return value * 2;
}

function processString(value) {
  return value + value;
}

Avoid Megamorphic Call Sites

// BAD: Many different object types
function getLength(obj) {
  return obj.length;
}

getLength([1, 2, 3]);      // Array
getLength("hello");         // String
getLength({ length: 5 });   // Object
getLength(new Set([1, 2])); // Set (has size, not length!)
// Call site becomes megamorphic

// GOOD: Separate functions for different types
function getArrayLength(arr) { return arr.length; }
function getStringLength(str) { return str.length; }

Loop Optimization

// BAD: Type changes in loop
function sum(arr) {
  let result = 0;
  for (let i = 0; i < arr.length; i++) {
    result = result + arr[i];  // If arr has non-numbers, deopt
  }
  return result;
}

// GOOD: Type assertion in hot loop
function sum(arr) {
  let result = 0;
  for (let i = 0; i < arr.length; i++) {
    const val = arr[i];
    if (typeof val !== 'number') {
      throw new TypeError('Expected number');
    }
    result = result + val;
  }
  return result;
}

Avoid Deoptimization Triggers

// BAD: Arguments object deoptimizes
function sum() {
  let result = 0;
  for (let i = 0; i < arguments.length; i++) {
    result += arguments[i];
  }
  return result;
}

// GOOD: Use rest parameters
function sum(...args) {
  let result = 0;
  for (let i = 0; i < args.length; i++) {
    result += args[i];
  }
  return result;
}
// BAD: try/catch in hot function (historically problematic)
function processWithTry(data) {
  try {
    return riskyOperation(data);
  } catch (e) {
    return null;
  }
}

// Modern V8 handles this well, but for extreme perf:
function processWithCheck(data) {
  if (!isValid(data)) return null;
  return safeOperation(data);
}

Function Inlining

TurboFan inlines small functions:

// Small function - likely inlined
function square(x) {
  return x * x;
}

function processValues(arr) {
  return arr.map(square);  // square may be inlined
}

Prevent inlining for debugging:

// V8 won't inline functions with debugger or eval
function noInline(x) {
  // %NeverOptimizeFunction(noInline);  // With --allow-natives-syntax
  return x * x;
}

Avoid Deoptimization Bailouts

// BAD: Object spread in hot loop
function mergeAll(objects) {
  let result = {};
  for (const obj of objects) {
    result = { ...result, ...obj };  // Creates new object each time
  }
  return result;
}

// GOOD: Use Object.assign
function mergeAll(objects) {
  const result = {};
  for (const obj of objects) {
    Object.assign(result, obj);  // Mutates in place
  }
  return result;
}

Profiling Optimization

--trace-opt and --trace-deopt

# See what gets optimized
node --trace-opt app.js 2>&1 | grep -E "marking|optimizing"

# See what gets deoptimized and why
node --trace-deopt app.js 2>&1 | grep -E "DEOPT|Reason"

Turbolizer

TurboFan generates graph files viewable in Turbolizer:

# Generate turbo files
node --trace-turbo --trace-turbo-path=/tmp/turbo app.js

# Open in turbolizer (from V8 tools)
# https://nickmccurdy.github.io/turbolizer/

V8 Profiler

const v8 = require('node:v8');

// Take CPU profile
v8.setFlagsFromString('--prof');

// Run your code
runBenchmark();

// Process with:
// node --prof-process isolate-*.log > processed.txt

Native Syntax for Testing

// With --allow-natives-syntax
function testOptimization() {
  function add(a, b) { return a + b; }

  // Warm up
  for (let i = 0; i < 10000; i++) {
    add(i, i);
  }

  // Force optimization
  %OptimizeFunctionOnNextCall(add);
  add(1, 2);

  // Check if optimized
  const status = %GetOptimizationStatus(add);
  console.log('Is optimized:', (status & 16) !== 0);
}

Advanced Optimization Topics

Escape Analysis

TurboFan can eliminate heap allocations when objects don't "escape":

// Object may be stack-allocated (escape analysis)
function getDistance(x1, y1, x2, y2) {
  const point1 = { x: x1, y: y1 };  // Doesn't escape
  const point2 = { x: x2, y: y2 };  // Doesn't escape
  return Math.sqrt(
    Math.pow(point2.x - point1.x, 2) +
    Math.pow(point2.y - point1.y, 2)
  );
}

Load/Store Elimination

// Redundant loads can be eliminated
function process(obj) {
  const a = obj.value;  // Load
  const b = obj.value;  // Eliminated - use previous load
  return a + b;
}

Bounds Check Elimination

// V8 can eliminate redundant bounds checks
function sum(arr) {
  let result = 0;
  const len = arr.length;  // Cached length
  for (let i = 0; i < len; i++) {
    result += arr[i];  // V8 knows i < len, may eliminate check
  }
  return result;
}

Loop Invariant Code Motion

// V8 moves invariant code out of loops
function process(arr, multiplier) {
  const factor = multiplier * 2;  // Moved before loop
  for (let i = 0; i < arr.length; i++) {
    // const factor = multiplier * 2;  // If here, moved out
    arr[i] = arr[i] * factor;
  }
}

Deoptimization Debugging

Reading Deopt Output

node --trace-deopt app.js 2>&1
[deoptimizing (DEOPT eager): begin 0x...]
    bailout_type: deopt
    source_position: 123
    deopt_reason: not a Smi
[deoptimizing (DEOPT eager): end ...]

Key fields:

  • bailout_type: eager (immediate), lazy (at next call), soft (for profiling)
  • source_position: Byte offset in source
  • deopt_reason: Why deoptimization occurred

Common Fixes

// Deopt: "not a Smi" (Small Integer)
// Fix: Ensure values stay in SMI range (-2^30 to 2^30-1)
const MAX_SMI = 2 ** 30 - 1;
function increment(n) {
  if (n >= MAX_SMI) return BigInt(n) + 1n;
  return n + 1;
}

// Deopt: "wrong map"
// Fix: Consistent object shapes (see hidden-classes.md)

// Deopt: "insufficient feedback"
// Fix: Warm up code paths before optimization

Native Addon Considerations

When calling between JS and C++:

// N-API: Type checking adds overhead but prevents deopt
napi_value Add(napi_env env, napi_callback_info info) {
  size_t argc = 2;
  napi_value args[2];
  napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

  // Type check to match V8's expectations
  napi_valuetype type0, type1;
  napi_typeof(env, args[0], &type0);
  napi_typeof(env, args[1], &type1);

  if (type0 != napi_number || type1 != napi_number) {
    napi_throw_type_error(env, nullptr, "Expected numbers");
    return nullptr;
  }

  double a, b;
  napi_get_value_double(env, args[0], &a);
  napi_get_value_double(env, args[1], &b);

  napi_value result;
  napi_create_double(env, a + b, &result);
  return result;
}

References

  • V8 Ignition: https://v8.dev/docs/ignition
  • V8 TurboFan: https://v8.dev/docs/turbofan
  • V8 Deoptimization: deps/v8/src/deoptimizer/ in Node.js source
  • Turbolizer: https://nickmccurdy.github.io/turbolizer/

rules

build-system.md

child-process-internals.md

commit-messages.md

contributing.md

crypto-internals.md

debugging-native.md

fs-internals.md

libuv-async-io.md

libuv-event-loop.md

libuv-thread-pool.md

memory-debugging.md

napi.md

native-memory.md

net-internals.md

node-addon-api.md

profiling-v8.md

streams-internals.md

v8-garbage-collection.md

v8-hidden-classes.md

v8-jit-compilation.md

worker-threads-internals.md

SKILL.md

tile.json