From 61ff8b01c6949e721310cfcbb38ac70e20be54df Mon Sep 17 00:00:00 2001 From: BlobMaster41 <96896824+BlobMaster41@users.noreply.github.com> Date: Thu, 11 Dec 2025 22:13:19 -0500 Subject: [PATCH] Refactor closure capture logic and add closures test config Refactors closure variable capture logic in compiler.ts for clarity and robustness, including more explicit local variable usage and conversion of capturedNames to a Set. Updates prescanNodeForFunctionExpressions to use an iterative approach to avoid stack overflows. Adds a 'closures' feature entry to tests/features.json for testing closure support. --- src/compiler.ts | 695 +++++++++++++++++++++++--------------------- tests/features.json | 7 + 2 files changed, 369 insertions(+), 333 deletions(-) diff --git a/src/compiler.ts b/src/compiler.ts index c1d63879f1..d6776c5493 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -1790,15 +1790,17 @@ export class Compiler extends DiagnosticEmitter { if (local && !local.isCaptured) { local.isCaptured = true; // Ensure environment structures are set up - if (!instance.capturedLocals) { - instance.capturedLocals = new Map(); + let capturedLocals = instance.capturedLocals; + if (!capturedLocals) { + capturedLocals = new Map(); + instance.capturedLocals = capturedLocals; } - if (!instance.capturedLocals.has(local)) { + if (!capturedLocals.has(local)) { // Calculate proper byte offset with alignment // Reserve slot 0 for parent environment pointer (4 or 8 bytes depending on wasm32/64) let ptrSize = this.options.usizeType.byteSize; let currentOffset = ptrSize; // Start after parent pointer slot - for (let _keys = Map_keys(instance.capturedLocals), j = 0, m = _keys.length; j < m; ++j) { + for (let _keys = Map_keys(capturedLocals), j = 0, m = _keys.length; j < m; ++j) { let existingLocal = _keys[j]; let endOfSlot = existingLocal.envSlotIndex + existingLocal.type.byteSize; if (endOfSlot > currentOffset) currentOffset = endOfSlot; @@ -1809,7 +1811,7 @@ export class Compiler extends DiagnosticEmitter { currentOffset = (currentOffset + align - 1) & ~(align - 1); local.envSlotIndex = currentOffset; local.envOwner = instance; // Track which function owns this capture - instance.capturedLocals.set(local, local.envSlotIndex); + capturedLocals.set(local, local.envSlotIndex); } if (!instance.envLocal) { let envLocal = flow.addScopedLocal("$env", this.options.usizeType); @@ -1824,13 +1826,15 @@ export class Compiler extends DiagnosticEmitter { let thisLocal = flow.lookupLocal(CommonNames.this_); if (thisLocal && !thisLocal.isCaptured) { thisLocal.isCaptured = true; - if (!instance.capturedLocals) { - instance.capturedLocals = new Map(); + let capturedLocals = instance.capturedLocals; + if (!capturedLocals) { + capturedLocals = new Map(); + instance.capturedLocals = capturedLocals; } - if (!instance.capturedLocals.has(thisLocal)) { + if (!capturedLocals.has(thisLocal)) { let ptrSize = this.options.usizeType.byteSize; let currentOffset = ptrSize; - for (let _keys = Map_keys(instance.capturedLocals), j = 0, m = _keys.length; j < m; ++j) { + for (let _keys = Map_keys(capturedLocals), j = 0, m = _keys.length; j < m; ++j) { let existingLocal = _keys[j]; let endOfSlot = existingLocal.envSlotIndex + existingLocal.type.byteSize; if (endOfSlot > currentOffset) currentOffset = endOfSlot; @@ -1840,7 +1844,7 @@ export class Compiler extends DiagnosticEmitter { currentOffset = (currentOffset + align - 1) & ~(align - 1); thisLocal.envSlotIndex = currentOffset; thisLocal.envOwner = instance; - instance.capturedLocals.set(thisLocal, thisLocal.envSlotIndex); + capturedLocals.set(thisLocal, thisLocal.envSlotIndex); } if (!instance.envLocal) { let envLocal = flow.addScopedLocal("$env", this.options.usizeType); @@ -1887,7 +1891,8 @@ export class Compiler extends DiagnosticEmitter { // Allocate closure environment if this function has captured variables // This is done after compiling the body because we discover captures during body compilation - if (instance.envLocal && instance.capturedLocals && instance.capturedLocals.size > 0) { + let capturedLocals = instance.capturedLocals; + if (instance.envLocal && capturedLocals && capturedLocals.size > 0) { let envAlloc = this.compileClosureEnvironmentAllocation(instance); // Insert at the beginning of the body for (let i = stmts.length - 1; i >= bodyStartIndex; --i) { @@ -1899,9 +1904,8 @@ export class Compiler extends DiagnosticEmitter { // For closures (functions that capture from outer scope), emit code to cache the // environment pointer in a local at function entry. This is needed because indirect // calls to other closures can overwrite the global $~lib/__closure_env. - if (instance.closureEnvLocal) { - let closureEnvLocal = instance.closureEnvLocal; - + let closureEnvLocal = instance.closureEnvLocal; + if (closureEnvLocal) { let closureEnvGlobal = this.ensureClosureEnvironmentGlobal(); let sizeTypeRef = this.options.sizeTypeRef; let cacheEnv = module.local_set( @@ -3333,15 +3337,17 @@ export class Compiler extends DiagnosticEmitter { if (preCapturedNames && preCapturedNames.has(name)) { local.isCaptured = true; // Ensure we have a closure environment for this function - if (!sourceFunc.capturedLocals) { - sourceFunc.capturedLocals = new Map(); + let capturedLocals = sourceFunc.capturedLocals; + if (!capturedLocals) { + capturedLocals = new Map(); + sourceFunc.capturedLocals = capturedLocals; } - if (!sourceFunc.capturedLocals.has(local)) { + if (!capturedLocals.has(local)) { // Calculate proper byte offset based on current environment size with alignment // Reserve slot 0 for parent environment pointer (4 or 8 bytes depending on wasm32/64) let ptrSize = this.options.usizeType.byteSize; let currentOffset = ptrSize; // Start after parent pointer slot - for (let _keys = Map_keys(sourceFunc.capturedLocals), i = 0, k = _keys.length; i < k; ++i) { + for (let _keys = Map_keys(capturedLocals), i = 0, k = _keys.length; i < k; ++i) { let existingLocal = _keys[i]; let endOfSlot = existingLocal.envSlotIndex + existingLocal.type.byteSize; if (endOfSlot > currentOffset) currentOffset = endOfSlot; @@ -3352,7 +3358,7 @@ export class Compiler extends DiagnosticEmitter { currentOffset = (currentOffset + align - 1) & ~(align - 1); local.envSlotIndex = currentOffset; local.envOwner = sourceFunc; // Track which function owns this capture - sourceFunc.capturedLocals.set(local, local.envSlotIndex); + capturedLocals.set(local, local.envSlotIndex); } // Ensure we have an environment local if (!sourceFunc.envLocal) { @@ -6824,6 +6830,7 @@ export class Compiler extends DiagnosticEmitter { if (declaration.parameterKind === ParameterKind.Rest) { const arrExpr = new ArrayLiteralExpression([], declaration.range.atEnd); initExpr = this.compileArrayLiteral(arrExpr, type, Constraints.ConvExplicit); + initExpr = module.local_set(operandIndex, initExpr, type.isManaged); } else if (initializer) { initExpr = this.compileExpression( initializer, @@ -7594,7 +7601,8 @@ export class Compiler extends DiagnosticEmitter { // If this is a closure, we need to allocate the Function object dynamically // and set the _env field to point to our environment - if (instance.capturedLocals && instance.capturedLocals.size > 0) { + let capturedLocals = instance.capturedLocals; + if (capturedLocals && capturedLocals.size > 0) { return this.compileClosureFunctionCreation(instance, sourceFunction); } @@ -7647,7 +7655,7 @@ export class Compiler extends DiagnosticEmitter { outerFlow: Flow, innerFunctionNames: Set, captures: Map | null, - capturedNames: Map | null = null, + capturedNames: Set | null = null, declaredVars: Map | null = null ): void { switch (node.kind) { @@ -7688,9 +7696,9 @@ export class Compiler extends DiagnosticEmitter { } } else if (capturedNames) { // Name mode: collect name if it's from outer scope - let isFromOuter = (declaredVars && declaredVars.has(name)) || outerFlow.lookupLocal(name); + let isFromOuter = (declaredVars != null && declaredVars.has(name)) || outerFlow.lookupLocal(name) != null; if (isFromOuter) { - capturedNames.set(name, null); + capturedNames.add(name); } } break; @@ -7728,7 +7736,7 @@ export class Compiler extends DiagnosticEmitter { } else if (capturedNames) { // Name mode if (local) { - capturedNames.set(CommonNames.this_, null); + capturedNames.add(CommonNames.this_); } } break; @@ -7748,8 +7756,9 @@ export class Compiler extends DiagnosticEmitter { for (let i = 0, k = params.length; i < k; i++) { innerFunctionNames.add(params[i].name.text); } - if (decl.body) { - this.scanNodeForCaptures(decl.body, outerFlow, innerFunctionNames, captures, capturedNames, declaredVars); + let declBody = decl.body; + if (declBody) { + this.scanNodeForCaptures(declBody, outerFlow, innerFunctionNames, captures, capturedNames, declaredVars); } for (let i = 0, k = params.length; i < k; i++) { innerFunctionNames.delete(params[i].name.text); @@ -7881,9 +7890,12 @@ export class Compiler extends DiagnosticEmitter { } case NodeKind.For: { let stmt = node; - if (stmt.initializer) this.scanNodeForCaptures(stmt.initializer, outerFlow, innerFunctionNames, captures, capturedNames, declaredVars); - if (stmt.condition) this.scanNodeForCaptures(stmt.condition, outerFlow, innerFunctionNames, captures, capturedNames, declaredVars); - if (stmt.incrementor) this.scanNodeForCaptures(stmt.incrementor, outerFlow, innerFunctionNames, captures, capturedNames, declaredVars); + let forInit = stmt.initializer; + let forCond = stmt.condition; + let forIncr = stmt.incrementor; + if (forInit) this.scanNodeForCaptures(forInit, outerFlow, innerFunctionNames, captures, capturedNames, declaredVars); + if (forCond) this.scanNodeForCaptures(forCond, outerFlow, innerFunctionNames, captures, capturedNames, declaredVars); + if (forIncr) this.scanNodeForCaptures(forIncr, outerFlow, innerFunctionNames, captures, capturedNames, declaredVars); this.scanNodeForCaptures(stmt.body, outerFlow, innerFunctionNames, captures, capturedNames, declaredVars); break; } @@ -7898,12 +7910,14 @@ export class Compiler extends DiagnosticEmitter { let stmt = node; this.scanNodeForCaptures(stmt.condition, outerFlow, innerFunctionNames, captures, capturedNames, declaredVars); this.scanNodeForCaptures(stmt.ifTrue, outerFlow, innerFunctionNames, captures, capturedNames, declaredVars); - if (stmt.ifFalse) this.scanNodeForCaptures(stmt.ifFalse, outerFlow, innerFunctionNames, captures, capturedNames, declaredVars); + let ifFalse = stmt.ifFalse; + if (ifFalse) this.scanNodeForCaptures(ifFalse, outerFlow, innerFunctionNames, captures, capturedNames, declaredVars); break; } case NodeKind.Return: { let stmt = node; - if (stmt.value) this.scanNodeForCaptures(stmt.value, outerFlow, innerFunctionNames, captures, capturedNames, declaredVars); + let retValue = stmt.value; + if (retValue) this.scanNodeForCaptures(retValue, outerFlow, innerFunctionNames, captures, capturedNames, declaredVars); break; } case NodeKind.Switch: { @@ -7912,7 +7926,8 @@ export class Compiler extends DiagnosticEmitter { let cases = stmt.cases; for (let i = 0, k = cases.length; i < k; i++) { let case_ = cases[i]; - if (case_.label) this.scanNodeForCaptures(case_.label, outerFlow, innerFunctionNames, captures, capturedNames, declaredVars); + let caseLabel = case_.label; + if (caseLabel) this.scanNodeForCaptures(caseLabel, outerFlow, innerFunctionNames, captures, capturedNames, declaredVars); let stmts = case_.statements; for (let j = 0, l = stmts.length; j < l; j++) { this.scanNodeForCaptures(stmts[j], outerFlow, innerFunctionNames, captures, capturedNames, declaredVars); @@ -7931,14 +7946,14 @@ export class Compiler extends DiagnosticEmitter { for (let i = 0, k = bodyStmts.length; i < k; i++) { this.scanNodeForCaptures(bodyStmts[i], outerFlow, innerFunctionNames, captures, capturedNames, declaredVars); } - if (stmt.catchStatements) { - let catchStmts = stmt.catchStatements; + let catchStmts = stmt.catchStatements; + if (catchStmts) { for (let i = 0, k = catchStmts.length; i < k; i++) { this.scanNodeForCaptures(catchStmts[i], outerFlow, innerFunctionNames, captures, capturedNames, declaredVars); } } - if (stmt.finallyStatements) { - let finallyStmts = stmt.finallyStatements; + let finallyStmts = stmt.finallyStatements; + if (finallyStmts) { for (let i = 0, k = finallyStmts.length; i < k; i++) { this.scanNodeForCaptures(finallyStmts[i], outerFlow, innerFunctionNames, captures, capturedNames, declaredVars); } @@ -7952,8 +7967,9 @@ export class Compiler extends DiagnosticEmitter { let decl = declarations[i]; // Add the variable name as a local name (not captured from outer) innerFunctionNames.add(decl.name.text); - if (decl.initializer) { - this.scanNodeForCaptures(decl.initializer, outerFlow, innerFunctionNames, captures, capturedNames, declaredVars); + let declInit = decl.initializer; + if (declInit) { + this.scanNodeForCaptures(declInit, outerFlow, innerFunctionNames, captures, capturedNames, declaredVars); } } break; @@ -8085,8 +8101,9 @@ export class Compiler extends DiagnosticEmitter { case NodeKind.If: { let ifStmt = node; this.collectDeclaredVariables(ifStmt.ifTrue, vars); - if (ifStmt.ifFalse) { - this.collectDeclaredVariables(ifStmt.ifFalse, vars); + let ifFalse = ifStmt.ifFalse; + if (ifFalse) { + this.collectDeclaredVariables(ifFalse, vars); } break; } @@ -8102,7 +8119,8 @@ export class Compiler extends DiagnosticEmitter { } case NodeKind.For: { let forStmt = node; - if (forStmt.initializer) this.collectDeclaredVariables(forStmt.initializer, vars); + let initializer = forStmt.initializer; + if (initializer) this.collectDeclaredVariables(initializer, vars); this.collectDeclaredVariables(forStmt.body, vars); break; } @@ -8241,325 +8259,332 @@ export class Compiler extends DiagnosticEmitter { } } - /** Recursively scans a node for function expressions and sets up their captures. */ + /** Iteratively scans a node for function expressions and sets up their captures. + * Uses an explicit stack to avoid stack overflow on deeply nested code. */ private prescanNodeForFunctionExpressions( node: Node, instance: Function, flow: Flow, declaredVars: Map ): void { - switch (node.kind) { - case NodeKind.Block: { - let block = node; - for (let i = 0, k = block.statements.length; i < k; i++) { - this.prescanNodeForFunctionExpressions(block.statements[i], instance, flow, declaredVars); - } - break; - } - case NodeKind.Expression: { - let exprStmt = node; - this.prescanNodeForFunctionExpressions(exprStmt.expression, instance, flow, declaredVars); - break; - } - case NodeKind.Return: { - let ret = node; - if (ret.value) { - this.prescanNodeForFunctionExpressions(ret.value, instance, flow, declaredVars); - } - break; - } - case NodeKind.Variable: { - let varStmt = node; - for (let i = 0, k = varStmt.declarations.length; i < k; i++) { - let decl = varStmt.declarations[i]; - if (decl.initializer) { - this.prescanNodeForFunctionExpressions(decl.initializer, instance, flow, declaredVars); + // Use an explicit stack to avoid recursion stack overflow + let stack = new Array(); + stack.push(node); + + while (stack.length > 0) { + let current = assert(stack.pop()); + switch (current.kind) { + case NodeKind.Block: { + let block = current; + let statements = block.statements; + for (let i = statements.length - 1; i >= 0; i--) { + stack.push(statements[i]); } + break; } - break; - } - case NodeKind.If: { - let ifStmt = node; - this.prescanNodeForFunctionExpressions(ifStmt.condition, instance, flow, declaredVars); - this.prescanNodeForFunctionExpressions(ifStmt.ifTrue, instance, flow, declaredVars); - if (ifStmt.ifFalse) { - this.prescanNodeForFunctionExpressions(ifStmt.ifFalse, instance, flow, declaredVars); + case NodeKind.Expression: { + let exprStmt = current; + stack.push(exprStmt.expression); + break; } - break; - } - case NodeKind.While: { - let whileStmt = node; - this.prescanNodeForFunctionExpressions(whileStmt.condition, instance, flow, declaredVars); - this.prescanNodeForFunctionExpressions(whileStmt.body, instance, flow, declaredVars); - break; - } - case NodeKind.Do: { - let doStmt = node; - this.prescanNodeForFunctionExpressions(doStmt.condition, instance, flow, declaredVars); - this.prescanNodeForFunctionExpressions(doStmt.body, instance, flow, declaredVars); - break; - } - case NodeKind.For: { - let forStmt = node; - if (forStmt.initializer) this.prescanNodeForFunctionExpressions(forStmt.initializer, instance, flow, declaredVars); - if (forStmt.condition) this.prescanNodeForFunctionExpressions(forStmt.condition, instance, flow, declaredVars); - if (forStmt.incrementor) this.prescanNodeForFunctionExpressions(forStmt.incrementor, instance, flow, declaredVars); - this.prescanNodeForFunctionExpressions(forStmt.body, instance, flow, declaredVars); - break; - } - case NodeKind.Function: { - // Found a function expression - analyze its captures - let funcExpr = node; - let declaration = funcExpr.declaration; - let capturedNames = this.analyzeCapturedVariablesWithDeclared(declaration, flow, instance, declaredVars); - if (capturedNames.size > 0) { - // Check if closures feature is enabled - if (!this.options.hasFeature(Feature.Closures)) { - this.error( - DiagnosticCode.Feature_0_is_not_enabled, - funcExpr.range, "closures" - ); - break; - } - // Store captured names for later use when compiling variable declarations - let existingNames = instance.preCapturedNames; - if (!existingNames) { - instance.preCapturedNames = new Set(); - existingNames = instance.preCapturedNames; + case NodeKind.Return: { + let ret = current; + let retValue = ret.value; + if (retValue) { + stack.push(retValue); } - for (let _keys = Map_keys(capturedNames), i = 0, k = _keys.length; i < k; i++) { - existingNames.add(_keys[i]); + break; + } + case NodeKind.Variable: { + let varStmt = current; + let declarations = varStmt.declarations; + for (let i = declarations.length - 1; i >= 0; i--) { + let declInit = declarations[i].initializer; + if (declInit) { + stack.push(declInit); + } } + break; } - // Also scan the function body for nested closures - if (declaration.body) { - // Create a temporary set of inner parameter names - let innerNames = new Set(); - let params = declaration.signature.parameters; - for (let i = 0, k = params.length; i < k; i++) { - innerNames.add(params[i].name.text); + case NodeKind.If: { + let ifStmt = current; + let ifFalse = ifStmt.ifFalse; + if (ifFalse) { + stack.push(ifFalse); } - // We don't recurse into nested function bodies here - that happens when the inner function is compiled + stack.push(ifStmt.ifTrue); + stack.push(ifStmt.condition); + break; } - break; - } - case NodeKind.Binary: { - let binary = node; - this.prescanNodeForFunctionExpressions(binary.left, instance, flow, declaredVars); - this.prescanNodeForFunctionExpressions(binary.right, instance, flow, declaredVars); - break; - } - case NodeKind.UnaryPrefix: { - let unary = node; - this.prescanNodeForFunctionExpressions(unary.operand, instance, flow, declaredVars); - break; - } - case NodeKind.UnaryPostfix: { - let unary = node; - this.prescanNodeForFunctionExpressions(unary.operand, instance, flow, declaredVars); - break; - } - case NodeKind.Call: { - let call = node; - this.prescanNodeForFunctionExpressions(call.expression, instance, flow, declaredVars); - let args = call.args; - for (let i = 0, k = args.length; i < k; i++) { - this.prescanNodeForFunctionExpressions(args[i], instance, flow, declaredVars); + case NodeKind.While: { + let whileStmt = current; + stack.push(whileStmt.body); + stack.push(whileStmt.condition); + break; } - break; - } - case NodeKind.New: { - let newExpr = node; - // Scan constructor arguments for function expressions - let args = newExpr.args; - for (let i = 0, k = args.length; i < k; i++) { - this.prescanNodeForFunctionExpressions(args[i], instance, flow, declaredVars); + case NodeKind.Do: { + let doStmt = current; + stack.push(doStmt.body); + stack.push(doStmt.condition); + break; } - break; - } - case NodeKind.Parenthesized: { - let paren = node; - this.prescanNodeForFunctionExpressions(paren.expression, instance, flow, declaredVars); - break; - } - case NodeKind.Ternary: { - let ternary = node; - this.prescanNodeForFunctionExpressions(ternary.condition, instance, flow, declaredVars); - this.prescanNodeForFunctionExpressions(ternary.ifThen, instance, flow, declaredVars); - this.prescanNodeForFunctionExpressions(ternary.ifElse, instance, flow, declaredVars); - break; - } - case NodeKind.Comma: { - let comma = node; - for (let i = 0, k = comma.expressions.length; i < k; i++) { - this.prescanNodeForFunctionExpressions(comma.expressions[i], instance, flow, declaredVars); + case NodeKind.For: { + let forStmt = current; + stack.push(forStmt.body); + let forIncr = forStmt.incrementor; + if (forIncr) stack.push(forIncr); + let forCond = forStmt.condition; + if (forCond) stack.push(forCond); + let forInit = forStmt.initializer; + if (forInit) stack.push(forInit); + break; } - break; - } - case NodeKind.Literal: { - let literal = node; - if (literal.literalKind == LiteralKind.Array) { - let arrLiteral = literal; - let elements = arrLiteral.elementExpressions; - for (let i = 0, k = elements.length; i < k; i++) { - let elem = elements[i]; - if (elem) { - this.prescanNodeForFunctionExpressions(elem, instance, flow, declaredVars); + case NodeKind.Function: { + // Found a function expression - analyze its captures + let funcExpr = current; + let declaration = funcExpr.declaration; + let capturedNames = this.analyzeCapturedVariablesWithDeclared(declaration, flow, instance, declaredVars); + if (capturedNames.size > 0) { + // Check if closures feature is enabled + if (!this.options.hasFeature(Feature.Closures)) { + this.error( + DiagnosticCode.Feature_0_is_not_enabled, + funcExpr.range, "closures" + ); + break; + } + // Store captured names for later use when compiling variable declarations + let existingNames = instance.preCapturedNames; + if (!existingNames) { + existingNames = new Set(); + instance.preCapturedNames = existingNames; + } + for (let _values = Set_values(capturedNames), i = 0, k = _values.length; i < k; i++) { + existingNames.add(_values[i]); } } - } else if (literal.literalKind == LiteralKind.Object) { - let objLiteral = literal; - let values = objLiteral.values; - for (let i = 0, k = values.length; i < k; i++) { - this.prescanNodeForFunctionExpressions(values[i], instance, flow, declaredVars); + // We don't recurse into nested function bodies here - that happens when the inner function is compiled + break; + } + case NodeKind.Binary: { + let binary = current; + stack.push(binary.right); + stack.push(binary.left); + break; + } + case NodeKind.UnaryPrefix: { + let unary = current; + stack.push(unary.operand); + break; + } + case NodeKind.UnaryPostfix: { + let unary = current; + stack.push(unary.operand); + break; + } + case NodeKind.Call: { + let call = current; + let args = call.args; + for (let i = args.length - 1; i >= 0; i--) { + stack.push(args[i]); } - } else if (literal.literalKind == LiteralKind.Template) { - let tmplLiteral = literal; - let expressions = tmplLiteral.expressions; - for (let i = 0, k = expressions.length; i < k; i++) { - this.prescanNodeForFunctionExpressions(expressions[i], instance, flow, declaredVars); + stack.push(call.expression); + break; + } + case NodeKind.New: { + let newExpr = current; + let args = newExpr.args; + for (let i = args.length - 1; i >= 0; i--) { + stack.push(args[i]); } + break; } - break; - } - case NodeKind.ElementAccess: { - let elemAccess = node; - this.prescanNodeForFunctionExpressions(elemAccess.expression, instance, flow, declaredVars); - this.prescanNodeForFunctionExpressions(elemAccess.elementExpression, instance, flow, declaredVars); - break; - } - case NodeKind.PropertyAccess: { - let propAccess = node; - this.prescanNodeForFunctionExpressions(propAccess.expression, instance, flow, declaredVars); - break; - } - case NodeKind.Assertion: { - let assertion = node; - this.prescanNodeForFunctionExpressions(assertion.expression, instance, flow, declaredVars); - break; - } - case NodeKind.InstanceOf: { - let instanceOf = node; - this.prescanNodeForFunctionExpressions(instanceOf.expression, instance, flow, declaredVars); - break; - } - case NodeKind.Switch: { - let switchStmt = node; - this.prescanNodeForFunctionExpressions(switchStmt.condition, instance, flow, declaredVars); - let cases = switchStmt.cases; - for (let i = 0, k = cases.length; i < k; i++) { - let switchCase = cases[i]; - if (switchCase.label) { - this.prescanNodeForFunctionExpressions(switchCase.label, instance, flow, declaredVars); + case NodeKind.Parenthesized: { + let paren = current; + stack.push(paren.expression); + break; + } + case NodeKind.Ternary: { + let ternary = current; + stack.push(ternary.ifElse); + stack.push(ternary.ifThen); + stack.push(ternary.condition); + break; + } + case NodeKind.Comma: { + let comma = current; + let expressions = comma.expressions; + for (let i = expressions.length - 1; i >= 0; i--) { + stack.push(expressions[i]); } - let statements = switchCase.statements; - for (let j = 0, l = statements.length; j < l; j++) { - this.prescanNodeForFunctionExpressions(statements[j], instance, flow, declaredVars); + break; + } + case NodeKind.Literal: { + let literal = current; + if (literal.literalKind == LiteralKind.Array) { + let arrLiteral = literal; + let elements = arrLiteral.elementExpressions; + for (let i = elements.length - 1; i >= 0; i--) { + let elem = elements[i]; + if (elem) { + stack.push(elem); + } + } + } else if (literal.literalKind == LiteralKind.Object) { + let objLiteral = literal; + let values = objLiteral.values; + for (let i = values.length - 1; i >= 0; i--) { + stack.push(values[i]); + } + } else if (literal.literalKind == LiteralKind.Template) { + let tmplLiteral = literal; + let expressions = tmplLiteral.expressions; + for (let i = expressions.length - 1; i >= 0; i--) { + stack.push(expressions[i]); + } } + break; } - break; - } - case NodeKind.ForOf: { - let forOfStmt = node; - this.prescanNodeForFunctionExpressions(forOfStmt.iterable, instance, flow, declaredVars); - if (forOfStmt.variable.kind == NodeKind.Variable) { - this.prescanNodeForFunctionExpressions(forOfStmt.variable, instance, flow, declaredVars); + case NodeKind.ElementAccess: { + let elemAccess = current; + stack.push(elemAccess.elementExpression); + stack.push(elemAccess.expression); + break; } - this.prescanNodeForFunctionExpressions(forOfStmt.body, instance, flow, declaredVars); - break; - } - case NodeKind.Try: { - let tryStmt = node; - let bodyStatements = tryStmt.bodyStatements; - for (let i = 0, k = bodyStatements.length; i < k; i++) { - this.prescanNodeForFunctionExpressions(bodyStatements[i], instance, flow, declaredVars); + case NodeKind.PropertyAccess: { + let propAccess = current; + stack.push(propAccess.expression); + break; } - let catchStatements = tryStmt.catchStatements; - if (catchStatements) { - for (let i = 0, k = catchStatements.length; i < k; i++) { - this.prescanNodeForFunctionExpressions(catchStatements[i], instance, flow, declaredVars); + case NodeKind.Assertion: { + let assertion = current; + stack.push(assertion.expression); + break; + } + case NodeKind.InstanceOf: { + let instanceOfExpr = current; + stack.push(instanceOfExpr.expression); + break; + } + case NodeKind.Switch: { + let switchStmt = current; + let cases = switchStmt.cases; + for (let i = cases.length - 1; i >= 0; i--) { + let switchCase = cases[i]; + let statements = switchCase.statements; + for (let j = statements.length - 1; j >= 0; j--) { + stack.push(statements[j]); + } + let caseLabel = switchCase.label; + if (caseLabel) { + stack.push(caseLabel); + } } + stack.push(switchStmt.condition); + break; } - let finallyStatements = tryStmt.finallyStatements; - if (finallyStatements) { - for (let i = 0, k = finallyStatements.length; i < k; i++) { - this.prescanNodeForFunctionExpressions(finallyStatements[i], instance, flow, declaredVars); + case NodeKind.ForOf: { + let forOfStmt = current; + stack.push(forOfStmt.body); + if (forOfStmt.variable.kind == NodeKind.Variable) { + stack.push(forOfStmt.variable); } + stack.push(forOfStmt.iterable); + break; + } + case NodeKind.Try: { + let tryStmt = current; + let finallyStatements = tryStmt.finallyStatements; + if (finallyStatements) { + for (let i = finallyStatements.length - 1; i >= 0; i--) { + stack.push(finallyStatements[i]); + } + } + let catchStatements = tryStmt.catchStatements; + if (catchStatements) { + for (let i = catchStatements.length - 1; i >= 0; i--) { + stack.push(catchStatements[i]); + } + } + let bodyStatements = tryStmt.bodyStatements; + for (let i = bodyStatements.length - 1; i >= 0; i--) { + stack.push(bodyStatements[i]); + } + break; + } + case NodeKind.Throw: { + let throwStmt = current; + stack.push(throwStmt.value); + break; + } + case NodeKind.Void: { + let voidStmt = current; + stack.push(voidStmt.expression); + break; } - break; - } - case NodeKind.Throw: { - let throwStmt = node; - this.prescanNodeForFunctionExpressions(throwStmt.value, instance, flow, declaredVars); - break; - } - case NodeKind.Void: { - let voidStmt = node; - this.prescanNodeForFunctionExpressions(voidStmt.expression, instance, flow, declaredVars); - break; - } - // Leaf expressions - no children to scan - case NodeKind.Identifier: - case NodeKind.True: - case NodeKind.False: - case NodeKind.Null: - case NodeKind.Super: - case NodeKind.This: - case NodeKind.Omitted: - case NodeKind.Compiled: - break; + // Leaf expressions - no children to scan + case NodeKind.Identifier: + case NodeKind.True: + case NodeKind.False: + case NodeKind.Null: + case NodeKind.Super: + case NodeKind.This: + case NodeKind.Omitted: + case NodeKind.Compiled: + break; - // Statements without expressions - case NodeKind.Break: - case NodeKind.Continue: - case NodeKind.Empty: - break; + // Statements without expressions + case NodeKind.Break: + case NodeKind.Continue: + case NodeKind.Empty: + break; - // Class expressions are not supported (will error during compilation) - case NodeKind.Class: - // Constructor keyword - not a capturable expression - case NodeKind.Constructor: - break; + // Class expressions are not supported (will error during compilation) + case NodeKind.Class: + // Constructor keyword - not a capturable expression + case NodeKind.Constructor: + break; - // Module-level declarations (shouldn't appear in function bodies normally) - case NodeKind.Export: - case NodeKind.ExportDefault: - case NodeKind.ExportImport: - case NodeKind.Import: - case NodeKind.Module: - case NodeKind.ClassDeclaration: - case NodeKind.EnumDeclaration: - case NodeKind.InterfaceDeclaration: - case NodeKind.NamespaceDeclaration: - case NodeKind.TypeDeclaration: - case NodeKind.FunctionDeclaration: - break; + // Module-level declarations (shouldn't appear in function bodies normally) + case NodeKind.Export: + case NodeKind.ExportDefault: + case NodeKind.ExportImport: + case NodeKind.Import: + case NodeKind.Module: + case NodeKind.ClassDeclaration: + case NodeKind.EnumDeclaration: + case NodeKind.InterfaceDeclaration: + case NodeKind.NamespaceDeclaration: + case NodeKind.TypeDeclaration: + case NodeKind.FunctionDeclaration: + break; - // Type nodes - no runtime expressions - case NodeKind.NamedType: - case NodeKind.FunctionType: - case NodeKind.TypeName: - case NodeKind.TypeParameter: - case NodeKind.Parameter: - break; + // Type nodes - no runtime expressions + case NodeKind.NamedType: + case NodeKind.FunctionType: + case NodeKind.TypeName: + case NodeKind.TypeParameter: + case NodeKind.Parameter: + break; - // Special nodes - case NodeKind.Source: - case NodeKind.Decorator: - case NodeKind.ExportMember: - case NodeKind.SwitchCase: - case NodeKind.IndexSignature: - case NodeKind.Comment: - case NodeKind.EnumValueDeclaration: - case NodeKind.FieldDeclaration: - case NodeKind.ImportDeclaration: - case NodeKind.MethodDeclaration: - case NodeKind.VariableDeclaration: - break; + // Special nodes + case NodeKind.Source: + case NodeKind.Decorator: + case NodeKind.ExportMember: + case NodeKind.SwitchCase: + case NodeKind.IndexSignature: + case NodeKind.Comment: + case NodeKind.EnumValueDeclaration: + case NodeKind.FieldDeclaration: + case NodeKind.ImportDeclaration: + case NodeKind.MethodDeclaration: + case NodeKind.VariableDeclaration: + break; - default: - assert(false, "prescanNodeForFunctionExpressions: unhandled node kind: " + (node.kind as i32).toString()); + default: + assert(false, "prescanNodeForFunctionExpressions: unhandled node kind: " + (current.kind as i32).toString()); + } } } @@ -8570,10 +8595,10 @@ export class Compiler extends DiagnosticEmitter { outerFlow: Flow, outerFunc: Function, declaredVars: Map - ): Map { + ): Set { // For prescan, we just collect variable NAMES that are captured // We'll create the actual captures with proper Local references later - let capturedNames = new Map(); + let capturedNames = new Set(); let innerFunctionNames = new Set(); // Scan parameter default values for captures (before adding params to inner names) @@ -8689,8 +8714,9 @@ export class Compiler extends DiagnosticEmitter { // Get the environment pointer from the outer function let envPtrExpr: ExpressionRef; - if (outerFunc.envLocal) { - envPtrExpr = module.local_get(outerFunc.envLocal.index, sizeTypeRef); + let outerEnvLocal = outerFunc.envLocal; + if (outerEnvLocal) { + envPtrExpr = module.local_get(outerEnvLocal.index, sizeTypeRef); } else { envPtrExpr = options.isWasm64 ? module.i64(0) : module.i32(0); } @@ -8744,15 +8770,17 @@ export class Compiler extends DiagnosticEmitter { let envOwner = capturedLocal.envOwner; // Case 1: We're in the function that owns the environment (the variable was declared here) - if (envOwner == currentFunc && currentFunc.envLocal) { - return module.local_get(currentFunc.envLocal.index, sizeTypeRef); + let envLocal = currentFunc.envLocal; + if (envOwner == currentFunc && envLocal) { + return module.local_get(envLocal.index, sizeTypeRef); } // Case 2: We're in a closure and need to access a variable from an outer scope // Start from our closure's environment and traverse parent pointers let envExpr: ExpressionRef; - if (currentFunc.closureEnvLocal) { - envExpr = module.local_get(currentFunc.closureEnvLocal.index, sizeTypeRef); + let closureEnvLocal = currentFunc.closureEnvLocal; + if (closureEnvLocal) { + envExpr = module.local_get(closureEnvLocal.index, sizeTypeRef); } else { // Fallback to global (shouldn't normally happen) let closureEnvGlobal = this.ensureClosureEnvironmentGlobal(); @@ -8783,7 +8811,7 @@ export class Compiler extends DiagnosticEmitter { } /** Compiles loading a captured variable from the closure environment. */ - private compileClosureLoad(local: Local, expression: Expression): ExpressionRef { + private compileClosureLoad(local: Local, expression: Node | null = null): ExpressionRef { let module = this.module; let localType = local.type; let slotOffset = local.envSlotIndex; @@ -8864,9 +8892,10 @@ export class Compiler extends DiagnosticEmitter { // If this is a closure (has outerFunction), use closureEnvLocal as parent // Otherwise, parent is null (0) let parentEnvExpr: ExpressionRef; - if (instance.closureEnvLocal) { + let closureEnvLocal = instance.closureEnvLocal; + if (closureEnvLocal) { // This is a nested closure - use the cached closure env as parent - parentEnvExpr = module.local_get(instance.closureEnvLocal.index, sizeTypeRef); + parentEnvExpr = module.local_get(closureEnvLocal.index, sizeTypeRef); } else { // This is the outermost function - no parent parentEnvExpr = options.isWasm64 ? module.i64(0) : module.i32(0); diff --git a/tests/features.json b/tests/features.json index 5c47772500..7d44d0f556 100644 --- a/tests/features.json +++ b/tests/features.json @@ -43,5 +43,12 @@ "v8_flags": [ "--experimental-wasm-relaxed-simd" ] + }, + "closures": { + "asc_flags": [ + "--enable closures" + ], + "v8_flags": [ + ] } }