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
99%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
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.
JavaScript Source
↓
Parser
↓
AST
↓
Ignition (Interpreter)
↓ (with profiling)
TurboFan (JIT Compiler)
↓
Machine CodeIgnition is V8's bytecode interpreter. It:
# View generated bytecode
node --print-bytecode app.js
# Filter bytecode for specific function
node --print-bytecode --print-bytecode-filter=myFunction app.jsExample 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 accumulatorTurboFan 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.jsCold Code (executed once)
↓
Warm Code (interpreted, collecting feedback)
↓
Hot Code (compiled by TurboFan)V8 decides to optimize based on:
# Lower tier-up threshold for testing
node --interrupt-budget=100 app.jsTurboFan 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 codeWhen 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]| Reason | Description | Fix |
|---|---|---|
not a number | Expected number, got something else | Type consistency |
wrong map | Object shape changed | Consistent object structure |
out of bounds | Array access beyond length | Bounds checking |
minus zero | Operation produced -0 | Avoid -0 producing operations |
overflow | Integer overflow | Use BigInt for large numbers |
hole | Accessed array hole | Avoid sparse arrays |
// 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;
}// 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; }// 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;
}// 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);
}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;
}// 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;
}# 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"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/const v8 = require('node:v8');
// Take CPU profile
v8.setFlagsFromString('--prof');
// Run your code
runBenchmark();
// Process with:
// node --prof-process isolate-*.log > processed.txt// 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);
}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)
);
}// Redundant loads can be eliminated
function process(obj) {
const a = obj.value; // Load
const b = obj.value; // Eliminated - use previous load
return a + b;
}// 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;
}// 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;
}
}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 sourcedeopt_reason: Why deoptimization occurred// 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 optimizationWhen 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;
}deps/v8/src/deoptimizer/ in Node.js sourcerules