javascript

examples

examples.js
/**
 * ============================================================
 * 22.1 ENGINE ARCHITECTURE - EXAMPLES
 * ============================================================
 *
 * Practical demonstrations of JavaScript engine concepts
 * including parsing, optimization, and performance patterns.
 */

/**
 * ============================================================
 * EXAMPLE 1: LAZY PARSING DEMONSTRATION
 * ============================================================
 *
 * Engines use lazy parsing to speed up initial load.
 * Inner functions are pre-parsed but not fully compiled
 * until they're actually called.
 */

console.log('=== Example 1: Lazy Parsing ===\n');

// This outer function is eagerly parsed (top-level)
function outerFunction() {
  console.log('Outer function executed');

  // This inner function is lazily parsed
  // Only pre-parsed initially, fully parsed when called
  function innerFunction() {
    // Complex logic here would only be fully parsed
    // when innerFunction is actually invoked
    return Array.from({ length: 1000 }, (_, i) => i * 2)
      .filter((x) => x % 4 === 0)
      .reduce((a, b) => a + b, 0);
  }

  // Only now is innerFunction fully parsed and compiled
  return innerFunction();
}

console.log('Result:', outerFunction());

// IIFE - Immediately Invoked, so eagerly parsed
const immediateResult = (function () {
  // This is eagerly parsed because it's called immediately
  return 'IIFE result';
})();
console.log('IIFE:', immediateResult);
console.log();

/**
 * ============================================================
 * EXAMPLE 2: UNDERSTANDING HOT CODE
 * ============================================================
 *
 * Functions that run many times become "hot" and get
 * optimized by the JIT compiler.
 */

console.log('=== Example 2: Hot Code Detection ===\n');

// This function will become "hot" after many calls
function addNumbers(a, b) {
  return a + b;
}

// Warm up the function - making it "hot"
console.log('Warming up function with consistent types...');
const warmupStart = performance.now();

for (let i = 0; i < 100000; i++) {
  addNumbers(i, i + 1); // Always integers
}

const warmupEnd = performance.now();
console.log(`Warmup time: ${(warmupEnd - warmupStart).toFixed(2)}ms`);

// Now the function is optimized for integer addition
console.log("Function is now 'hot' and optimized for integers");
console.log('Result:', addNumbers(100, 200));
console.log();

/**
 * ============================================================
 * EXAMPLE 3: TYPE STABILITY
 * ============================================================
 *
 * Keeping types stable allows engines to optimize better.
 * Type changes can cause deoptimization.
 */

console.log('=== Example 3: Type Stability ===\n');

// GOOD: Consistent types
function multiplyStable(x, y) {
  return x * y;
}

// Train with consistent types
for (let i = 0; i < 10000; i++) {
  multiplyStable(i, 2); // Always numbers
}

console.log('Stable function result:', multiplyStable(5, 3));

// BAD: Mixed types cause deoptimization
function multiplyUnstable(x, y) {
  return x * y;
}

// This pattern causes optimization issues
multiplyUnstable(5, 3); // Numbers
multiplyUnstable('5', 3); // String coercion
multiplyUnstable(5, '3'); // String coercion
multiplyUnstable(null, 3); // null coercion

console.log('Unstable function (may deoptimize):', multiplyUnstable(5, 3));
console.log();

/**
 * ============================================================
 * EXAMPLE 4: FUNCTION INLINING
 * ============================================================
 *
 * The JIT compiler can inline small functions, eliminating
 * function call overhead.
 */

console.log('=== Example 4: Function Inlining ===\n');

// Small function - candidate for inlining
function square(x) {
  return x * x;
}

// Using the function in a loop
function calculateSquares(n) {
  let sum = 0;
  for (let i = 0; i < n; i++) {
    sum += square(i); // This call may be inlined
  }
  return sum;
}

// After optimization, this might become:
function calculateSquaresInlined(n) {
  let sum = 0;
  for (let i = 0; i < n; i++) {
    sum += i * i; // Inlined - no function call
  }
  return sum;
}

console.log('With function:', calculateSquares(1000));
console.log('Inlined (manual):', calculateSquaresInlined(1000));

// Benchmark
const start1 = performance.now();
for (let i = 0; i < 10000; i++) calculateSquares(100);
console.log(`With function: ${(performance.now() - start1).toFixed(2)}ms`);

const start2 = performance.now();
for (let i = 0; i < 10000; i++) calculateSquaresInlined(100);
console.log(`Inlined: ${(performance.now() - start2).toFixed(2)}ms`);
console.log();

/**
 * ============================================================
 * EXAMPLE 5: ESCAPE ANALYSIS
 * ============================================================
 *
 * Objects that don't "escape" a function can be
 * stack-allocated instead of heap-allocated.
 */

console.log('=== Example 5: Escape Analysis ===\n');

// Object DOES escape - must be heap allocated
function createPointEscapes(x, y) {
  return { x, y }; // Object leaves the function
}

// Object DOESN'T escape - can be stack allocated
function distanceNoEscape(x1, y1, x2, y2) {
  // This object is created and used only within the function
  const point1 = { x: x1, y: y1 };
  const point2 = { x: x2, y: y2 };

  // After optimization, the engine might not allocate
  // objects at all - just use the values directly
  const dx = point2.x - point1.x;
  const dy = point2.y - point1.y;

  return Math.sqrt(dx * dx + dy * dy);
}

console.log('Escaping object:', createPointEscapes(3, 4));
console.log('Non-escaping (optimized):', distanceNoEscape(0, 0, 3, 4));
console.log();

/**
 * ============================================================
 * EXAMPLE 6: CONSTANT FOLDING
 * ============================================================
 *
 * The compiler can compute constant expressions at compile time.
 */

console.log('=== Example 6: Constant Folding ===\n');

// These constants can be computed at compile time
const SECONDS_PER_MINUTE = 60;
const MINUTES_PER_HOUR = 60;
const HOURS_PER_DAY = 24;

// This multiplication is done at compile time, not runtime
const SECONDS_PER_DAY = SECONDS_PER_MINUTE * MINUTES_PER_HOUR * HOURS_PER_DAY;

console.log('Seconds per day (folded):', SECONDS_PER_DAY);

// Function with constant expressions
function getCircleArea(radius) {
  // Math.PI * 2 might be folded to ~6.283185307179586
  const circumference = Math.PI * 2 * radius;
  // Math.PI is constant, multiplication can be optimized
  const area = Math.PI * radius * radius;
  return { circumference, area };
}

console.log('Circle (r=5):', getCircleArea(5));
console.log();

/**
 * ============================================================
 * EXAMPLE 7: DEAD CODE ELIMINATION
 * ============================================================
 *
 * The compiler removes code that has no effect.
 */

console.log('=== Example 7: Dead Code Elimination ===\n');

function withDeadCode(x) {
  let result = x * 2;

  // This is dead code - never used
  let unused = x * 3;
  let alsoUnused = Math.sqrt(x);

  // This is dead code - condition always false
  if (false) {
    result = x * 100;
  }

  // This is unreachable code
  return result;

  // Everything below is unreachable
  result = x * 4;
  console.log('Never printed');
}

// After optimization, this becomes essentially:
function optimized(x) {
  return x * 2;
}

console.log('With dead code:', withDeadCode(5));
console.log('Optimized:', optimized(5));
console.log();

/**
 * ============================================================
 * EXAMPLE 8: LOOP OPTIMIZATION
 * ============================================================
 *
 * Engines optimize loops in various ways:
 * - Loop unrolling
 * - Loop invariant code motion
 * - Bounds check elimination
 */

console.log('=== Example 8: Loop Optimization ===\n');

// Loop invariant code motion
function loopWithInvariant(arr, multiplier) {
  const result = [];
  const len = arr.length; // Hoisted out of loop

  for (let i = 0; i < len; i++) {
    // multiplier * 2 is invariant - computed once
    result.push(arr[i] * (multiplier * 2));
  }

  return result;
}

// After optimization, equivalent to:
function loopOptimized(arr, multiplier) {
  const result = [];
  const len = arr.length;
  const factor = multiplier * 2; // Computed once

  for (let i = 0; i < len; i++) {
    result.push(arr[i] * factor);
  }

  return result;
}

const testArr = [1, 2, 3, 4, 5];
console.log('With invariant:', loopWithInvariant(testArr, 3));
console.log('Optimized:', loopOptimized(testArr, 3));

// Loop unrolling example (conceptual)
function sumUnrolled(arr) {
  let sum = 0;
  const len = arr.length;

  // Process 4 elements at a time
  let i = 0;
  for (; i + 3 < len; i += 4) {
    sum += arr[i] + arr[i + 1] + arr[i + 2] + arr[i + 3];
  }

  // Handle remaining elements
  for (; i < len; i++) {
    sum += arr[i];
  }

  return sum;
}

console.log('Unrolled sum:', sumUnrolled([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]));
console.log();

/**
 * ============================================================
 * EXAMPLE 9: DEOPTIMIZATION TRIGGERS
 * ============================================================
 *
 * These patterns can cause deoptimization.
 * Avoid them in performance-critical code.
 */

console.log('=== Example 9: Deoptimization Triggers ===\n');

// 1. Type changes
function processValue(val) {
  return val * 2;
}

// Good: consistent types
for (let i = 0; i < 1000; i++) {
  processValue(i);
}
console.log('After numbers:', processValue(50));

// Bad: type change causes deopt
// processValue("hello");  // Uncomment to trigger deopt

// 2. Using arguments object
function useArguments() {
  // Using 'arguments' can prevent optimization
  // return Array.from(arguments).reduce((a, b) => a + b, 0);

  // Better: use rest parameters
  return [...arguments].reduce((a, b) => a + b, 0);
}

// Even better: use rest parameters directly
function useRest(...args) {
  return args.reduce((a, b) => a + b, 0);
}

console.log('With arguments:', useArguments(1, 2, 3, 4, 5));
console.log('With rest:', useRest(1, 2, 3, 4, 5));

// 3. try-catch in hot code
function withTryCatch(x) {
  try {
    return x * 2;
  } catch (e) {
    return 0;
  }
}

// Better: keep try-catch separate
function safeMath(x) {
  return x * 2;
}

function withErrorHandling(x) {
  try {
    return safeMath(x);
  } catch (e) {
    return 0;
  }
}

console.log('Inline try-catch:', withTryCatch(5));
console.log('Separated:', withErrorHandling(5));
console.log();

/**
 * ============================================================
 * EXAMPLE 10: HIDDEN CLASS TRANSITIONS PREVIEW
 * ============================================================
 *
 * Objects with the same structure share "hidden classes".
 * This is covered in detail in section 22.2.
 */

console.log('=== Example 10: Hidden Class Preview ===\n');

// Good: All objects have same shape (same hidden class)
function createPoint(x, y) {
  return { x, y }; // Same property order
}

const p1 = createPoint(1, 2);
const p2 = createPoint(3, 4);
const p3 = createPoint(5, 6);

console.log('Points with same hidden class:');
console.log(p1, p2, p3);

// Bad: Objects created with different property orders
// have different hidden classes
function createPointBad(x, y, useZ) {
  const point = {};
  if (useZ) {
    point.z = 0; // Different order
  }
  point.x = x;
  point.y = y;
  return point;
}

const bad1 = createPointBad(1, 2, false); // { x, y }
const bad2 = createPointBad(3, 4, true); // { z, x, y }

console.log('\nPoints with different hidden classes:');
console.log(bad1, bad2);
console.log();

/**
 * ============================================================
 * EXAMPLE 11: BENCHMARKING PATTERNS
 * ============================================================
 *
 * How to properly benchmark JavaScript code considering
 * JIT compilation effects.
 */

console.log('=== Example 11: Proper Benchmarking ===\n');

function benchmarkFunction(fn, iterations = 100000, warmup = 10000) {
  // Warmup phase - let the JIT optimize
  console.log(`Warming up with ${warmup} iterations...`);
  for (let i = 0; i < warmup; i++) {
    fn(i);
  }

  // Force garbage collection if possible (Node.js with --expose-gc)
  if (global.gc) {
    global.gc();
  }

  // Actual benchmark
  console.log(`Running ${iterations} iterations...`);
  const start = performance.now();

  for (let i = 0; i < iterations; i++) {
    fn(i);
  }

  const end = performance.now();
  const totalTime = end - start;
  const opsPerSecond = (iterations / totalTime) * 1000;

  return {
    totalTime: totalTime.toFixed(2) + 'ms',
    opsPerSecond: opsPerSecond.toFixed(0),
    avgTimePerOp: ((totalTime / iterations) * 1000000).toFixed(2) + 'ns',
  };
}

function testFunction(x) {
  return x * x + x / 2 - (x % 3);
}

const results = benchmarkFunction(testFunction);
console.log('Benchmark results:', results);
console.log();

/**
 * ============================================================
 * EXAMPLE 12: VIEWING ENGINE INTERNALS
 * ============================================================
 *
 * Ways to inspect what the engine is doing.
 * (Some require special Node.js flags)
 */

console.log('=== Example 12: Engine Inspection Commands ===\n');

console.log('V8 inspection flags (run with Node.js):\n');

console.log('1. View bytecode:');
console.log(
  '   node --print-bytecode --print-bytecode-filter=functionName script.js\n'
);

console.log('2. View optimizations:');
console.log('   node --trace-opt script.js\n');

console.log('3. View deoptimizations:');
console.log('   node --trace-deopt script.js\n');

console.log('4. View hidden class transitions:');
console.log('   node --trace-ic script.js\n');

console.log('5. View garbage collection:');
console.log('   node --trace-gc script.js\n');

console.log('6. Generate V8 optimization log:');
console.log('   node --trace-turbo script.js\n');

console.log('7. Profile with inspector:');
console.log('   node --inspect-brk script.js\n');

// Example of checking optimization status (V8 native)
// Note: This requires Node.js with --allow-natives-syntax
/*
function exampleForOptCheck(x) {
  return x + 1;
}

// Warm up
for (let i = 0; i < 100000; i++) {
  exampleForOptCheck(i);
}

// Check optimization status
console.log("Is optimized:", %IsCrankshafted(exampleForOptCheck));
*/

console.log('=== Examples Complete ===\n');
Examples - JavaScript Tutorial | DeepML