// ast-pool.js - Object pooling for AST nodes to reduce GC pressure /** * Object pool for AST nodes to reduce garbage collection overhead * Provides significant performance improvement for large programs */ export class ASTNodePool { constructor() { // Pools for different node types this.pools = new Map(); // Pool configuration this.maxPoolSize = 1000; // Maximum objects per pool this.initialPoolSize = 50; // Initial objects to create // Statistics this.stats = { created: 0, reused: 0, returned: 0, poolHits: 0, poolMisses: 0 }; // Initialize common node type pools this.initializePools(); } /** * Initialize pools for common AST node types */ initializePools() { const commonTypes = [ 'BinaryExpression', 'UnaryExpression', 'FunctionCall', 'Identifier', 'NumberLiteral', 'StringLiteral', 'BooleanLiteral', 'ListLiteral', 'TableLiteral', 'MemberExpression', 'WhenExpression', 'WhenCase', 'AnonymousFunction' ]; for (const type of commonTypes) { this.pools.set(type, []); // Pre-populate with initial objects for (let i = 0; i < this.initialPoolSize; i++) { this.pools.get(type).push(this.createFreshNode(type)); } } } /** * Create a fresh AST node of the specified type */ createFreshNode(type) { const node = { type }; // Initialize common properties based on node type switch (type) { case 'BinaryExpression': node.operator = null; node.left = null; node.right = null; break; case 'UnaryExpression': node.operator = null; node.operand = null; break; case 'FunctionCall': node.callee = null; node.arguments = []; break; case 'Identifier': node.name = null; break; case 'NumberLiteral': node.value = 0; node.isFloat = false; break; case 'StringLiteral': node.value = ''; break; case 'BooleanLiteral': node.value = false; break; case 'ListLiteral': node.elements = []; break; case 'TableLiteral': node.properties = []; break; case 'MemberExpression': node.object = null; node.property = null; break; case 'WhenExpression': node.discriminants = []; node.cases = []; break; case 'WhenCase': node.patterns = []; node.consequent = null; break; case 'AnonymousFunction': node.params = []; node.body = null; break; } this.stats.created++; return node; } /** * Get a node from the pool or create a new one */ acquire(type, properties = {}) { const pool = this.pools.get(type); let node; if (pool && pool.length > 0) { node = pool.pop(); this.stats.reused++; this.stats.poolHits++; } else { node = this.createFreshNode(type); this.stats.poolMisses++; } // Apply provided properties Object.assign(node, properties); return node; } /** * Return a node to the pool for reuse */ release(node) { if (!node || !node.type) { return; } const pool = this.pools.get(node.type); if (!pool) { // Create pool for unknown type this.pools.set(node.type, []); this.pools.get(node.type).push(node); this.stats.returned++; return; } // Don't exceed maximum pool size if (pool.length >= this.maxPoolSize) { return; } // Reset node properties this.resetNode(node); pool.push(node); this.stats.returned++; } /** * Reset a node to its initial state */ resetNode(node) { const type = node.type; // Clear all properties except type for (const key of Object.keys(node)) { if (key !== 'type') { delete node[key]; } } // Reinitialize based on type switch (type) { case 'BinaryExpression': node.operator = null; node.left = null; node.right = null; break; case 'UnaryExpression': node.operator = null; node.operand = null; break; case 'FunctionCall': node.callee = null; node.arguments = []; break; case 'Identifier': node.name = null; break; case 'NumberLiteral': node.value = 0; node.isFloat = false; break; case 'StringLiteral': node.value = ''; break; case 'BooleanLiteral': node.value = false; break; case 'ListLiteral': node.elements = []; break; case 'TableLiteral': node.properties = []; break; case 'MemberExpression': node.object = null; node.property = null; break; case 'WhenExpression': node.discriminants = []; node.cases = []; break; case 'WhenCase': node.patterns = []; node.consequent = null; break; case 'AnonymousFunction': node.params = []; node.body = null; break; } } /** * Recursively release an entire AST tree */ releaseTree(node) { if (!node || typeof node !== 'object') { return; } // Release child nodes first this.visitChildren(node, (child) => { this.releaseTree(child); }); // Release this node this.release(node); } /** * Visit all child nodes of an AST node */ visitChildren(node, callback) { if (!node || typeof node !== 'object') { return; } switch (node.type) { case 'BinaryExpression': if (node.left) callback(node.left); if (node.right) callback(node.right); break; case 'UnaryExpression': if (node.operand) callback(node.operand); break; case 'FunctionCall': if (node.callee) callback(node.callee); if (node.arguments) { for (const arg of node.arguments) { callback(arg); } } break; case 'ListLiteral': if (node.elements) { for (const element of node.elements) { callback(element); } } break; case 'TableLiteral': if (node.properties) { for (const prop of node.properties) { if (prop.value) callback(prop.value); } } break; case 'MemberExpression': if (node.object) callback(node.object); if (node.property) callback(node.property); break; case 'WhenExpression': if (node.discriminants) { for (const discriminant of node.discriminants) { callback(discriminant); } } if (node.cases) { for (const whenCase of node.cases) { callback(whenCase); } } break; case 'WhenCase': if (node.patterns) { for (const pattern of node.patterns) { callback(pattern); } } if (node.consequent) callback(node.consequent); break; case 'AnonymousFunction': if (node.body) callback(node.body); break; case 'Program': if (node.body) { for (const statement of node.body) { callback(statement); } } break; } } /** * Get pool statistics */ getStats() { const totalRequests = this.stats.poolHits + this.stats.poolMisses; const hitRate = totalRequests > 0 ? this.stats.poolHits / totalRequests : 0; const reuseRate = this.stats.created > 0 ? this.stats.reused / this.stats.created : 0; const poolSizes = {}; for (const [type, pool] of this.pools.entries()) { poolSizes[type] = pool.length; } return { ...this.stats, hitRate, reuseRate, poolCount: this.pools.size, poolSizes, totalPooledObjects: Array.from(this.pools.values()).reduce((sum, pool) => sum + pool.length, 0) }; } /** * Reset all statistics */ resetStats() { this.stats = { created: 0, reused: 0, returned: 0, poolHits: 0, poolMisses: 0 }; } /** * Clear all pools and reset */ clear() { this.pools.clear(); this.resetStats(); this.initializePools(); } /** * Warm up pools by pre-creating objects */ warmUp(type, count = 100) { if (!this.pools.has(type)) { this.pools.set(type, []); } const pool = this.pools.get(type); for (let i = 0; i < count; i++) { if (pool.length < this.maxPoolSize) { pool.push(this.createFreshNode(type)); } } } } /** * Global AST node pool instance */ export const globalASTPool = new ASTNodePool(); /** * Convenience functions for common operations */ export const pooledNodes = { binaryExpression: (operator, left, right) => globalASTPool.acquire('BinaryExpression', { operator, left, right }), unaryExpression: (operator, operand) => globalASTPool.acquire('UnaryExpression', { operator, operand }), functionCall: (callee, args) => globalASTPool.acquire('FunctionCall', { callee, arguments: args }), identifier: (name) => globalASTPool.acquire('Identifier', { name }), numberLiteral: (value, isFloat = false) => globalASTPool.acquire('NumberLiteral', { value, isFloat }), stringLiteral: (value) => globalASTPool.acquire('StringLiteral', { value }), booleanLiteral: (value) => globalASTPool.acquire('BooleanLiteral', { value }), listLiteral: (elements) => globalASTPool.acquire('ListLiteral', { elements }), tableLiteral: (properties) => globalASTPool.acquire('TableLiteral', { properties }), memberExpression: (object, property) => globalASTPool.acquire('MemberExpression', { object, property }), whenExpression: (discriminants, cases) => globalASTPool.acquire('WhenExpression', { discriminants, cases }), anonymousFunction: (params, body) => globalASTPool.acquire('AnonymousFunction', { params, body }) }; /** * Benchmark AST node pool performance */ export async function benchmarkASTPool(iterations = 100000) { console.log(`Benchmarking AST node pool with ${iterations} iterations...`); // Benchmark without pooling const nPoolStart = performance.now(); const nodes1 = []; for (let i = 0; i < iterations; i++) { nodes1.push({ type: 'BinaryExpression', operator: '+', left: { type: 'NumberLiteral', value: i }, right: { type: 'NumberLiteral', value: i + 1 } }); } const nPoolTime = performance.now() - nPoolStart; // Benchmark with pooling const pool = new ASTNodePool(); const poolStart = performance.now(); const nodes2 = []; for (let i = 0; i < iterations; i++) { const left = pool.acquire('NumberLiteral', { value: i }); const right = pool.acquire('NumberLiteral', { value: i + 1 }); const expr = pool.acquire('BinaryExpression', { operator: '+', left, right }); nodes2.push(expr); } // Return nodes to pool for (const node of nodes2) { pool.releaseTree(node); } const poolTime = performance.now() - poolStart; console.log(`Without pooling: ${nPoolTime.toFixed(2)}ms`); console.log(`With pooling: ${poolTime.toFixed(2)}ms`); console.log(`Speedup: ${(nPoolTime / poolTime).toFixed(2)}x`); const stats = pool.getStats(); console.log(`Pool hit rate: ${(stats.hitRate * 100).toFixed(1)}%`); console.log(`Reuse rate: ${(stats.reuseRate * 100).toFixed(1)}%`); return { nPoolTime, poolTime, speedup: nPoolTime / poolTime, stats }; }