@@ -1795,12 +1795,17 @@ export class Compiler extends DiagnosticEmitter {
17951795 instance . capturedLocals = new Map ( ) ;
17961796 }
17971797 if ( ! instance . capturedLocals . has ( local ) ) {
1798- // Calculate proper byte offset
1798+ // Calculate proper byte offset with alignment
17991799 let currentOffset = 0 ;
18001800 for ( let _keys = Map_keys ( instance . capturedLocals ) , j = 0 , m = _keys . length ; j < m ; ++ j ) {
18011801 let existingLocal = _keys [ j ] ;
1802- currentOffset += existingLocal . type . byteSize ;
1802+ let endOfSlot = existingLocal . envSlotIndex + existingLocal . type . byteSize ;
1803+ if ( endOfSlot > currentOffset ) currentOffset = endOfSlot ;
18031804 }
1805+ // Align to the type's natural alignment
1806+ let typeSize = local . type . byteSize ;
1807+ let align = typeSize ;
1808+ currentOffset = ( currentOffset + align - 1 ) & ~ ( align - 1 ) ;
18041809 local . envSlotIndex = currentOffset ;
18051810 instance . capturedLocals . set ( local , local . envSlotIndex ) ;
18061811 }
@@ -1813,6 +1818,14 @@ export class Compiler extends DiagnosticEmitter {
18131818 }
18141819 }
18151820
1821+ // For closures (functions that capture from outer scope), create a local to cache
1822+ // the environment pointer. This is needed because indirect calls to other closures
1823+ // can overwrite the global $$~lib/__closure_env.
1824+ if ( instance . outerFunction && ! instance . closureEnvLocal ) {
1825+ let closureEnvLocal = flow . addScopedLocal ( "$closureEnv" , this . options . usizeType ) ;
1826+ instance . closureEnvLocal = closureEnvLocal ;
1827+ }
1828+
18161829 // compile statements
18171830 if ( bodyNode . kind == NodeKind . Block ) {
18181831 stmts = this . compileStatements ( ( < BlockStatement > bodyNode ) . statements , stmts ) ;
@@ -1851,6 +1864,27 @@ export class Compiler extends DiagnosticEmitter {
18511864 stmts [ bodyStartIndex ] = envAlloc ;
18521865 }
18531866
1867+ // For closures (functions that capture from outer scope), emit code to cache the
1868+ // environment pointer in a local at function entry. This is needed because indirect
1869+ // calls to other closures can overwrite the global $$~lib/__closure_env.
1870+ if ( instance . closureEnvLocal ) {
1871+ let closureEnvLocal = instance . closureEnvLocal ;
1872+
1873+ let closureEnvGlobal = this . ensureClosureEnvironmentGlobal ( ) ;
1874+ let sizeTypeRef = this . options . sizeTypeRef ;
1875+ let cacheEnv = module . local_set (
1876+ closureEnvLocal . index ,
1877+ module . global_get ( closureEnvGlobal , sizeTypeRef ) ,
1878+ false // not a reference type
1879+ ) ;
1880+
1881+ // Insert at the beginning of the body
1882+ for ( let i = stmts . length - 1 ; i >= bodyStartIndex ; -- i ) {
1883+ stmts [ i + 1 ] = stmts [ i ] ;
1884+ }
1885+ stmts [ bodyStartIndex ] = cacheEnv ;
1886+ }
1887+
18541888 // Make constructors return their instance pointer, and prepend a conditional
18551889 // allocation if any code path accesses `this`.
18561890 if ( instance . is ( CommonFlags . Constructor ) ) {
@@ -3271,12 +3305,17 @@ export class Compiler extends DiagnosticEmitter {
32713305 sourceFunc . capturedLocals = new Map ( ) ;
32723306 }
32733307 if ( ! sourceFunc . capturedLocals . has ( local ) ) {
3274- // Calculate proper byte offset based on current environment size
3308+ // Calculate proper byte offset based on current environment size with alignment
32753309 let currentOffset = 0 ;
32763310 for ( let _keys = Map_keys ( sourceFunc . capturedLocals ) , i = 0 , k = _keys . length ; i < k ; ++ i ) {
32773311 let existingLocal = _keys [ i ] ;
3278- currentOffset += existingLocal . type . byteSize ;
3312+ let endOfSlot = existingLocal . envSlotIndex + existingLocal . type . byteSize ;
3313+ if ( endOfSlot > currentOffset ) currentOffset = endOfSlot ;
32793314 }
3315+ // Align to the type's natural alignment
3316+ let typeSize = local . type . byteSize ;
3317+ let align = typeSize ;
3318+ currentOffset = ( currentOffset + align - 1 ) & ~ ( align - 1 ) ;
32803319 local . envSlotIndex = currentOffset ;
32813320 sourceFunc . capturedLocals . set ( local , local . envSlotIndex ) ;
32823321 }
@@ -6094,6 +6133,16 @@ export class Compiler extends DiagnosticEmitter {
60946133 // In the declaring function, we need to check if environment is set up
60956134 // In a closure, we always use the environment
60966135 if ( ! isInDeclaringFunction || sourceFunc . envLocal ) {
6136+ // Mark the local as initialized for flow analysis
6137+ flow . setLocalFlag ( localIndex , LocalFlags . Initialized ) ;
6138+ if ( type . isNullableReference ) {
6139+ if ( ! valueType . isNullableReference || flow . isNonnull ( valueExpr , type ) ) flow . setLocalFlag ( localIndex , LocalFlags . NonNull ) ;
6140+ else flow . unsetLocalFlag ( localIndex , LocalFlags . NonNull ) ;
6141+ }
6142+ if ( type . isShortIntegerValue ) {
6143+ if ( ! flow . canOverflow ( valueExpr , type ) ) flow . setLocalFlag ( localIndex , LocalFlags . Wrapped ) ;
6144+ else flow . unsetLocalFlag ( localIndex , LocalFlags . Wrapped ) ;
6145+ }
60976146 let storeExpr = this . compileClosureStore ( local , valueExpr , valueType ) ;
60986147 if ( tee ) {
60996148 // For tee, we need to return the stored value
@@ -7541,14 +7590,26 @@ export class Compiler extends DiagnosticEmitter {
75417590 }
75427591 if ( local && ! captures . has ( local ) ) {
75437592 local . isCaptured = true ;
7544- // Calculate proper byte offset based on existing captures
7545- let currentOffset = 0 ;
7546- for ( let _keys = Map_keys ( captures ) , idx = 0 , cnt = _keys . length ; idx < cnt ; ++ idx ) {
7547- let existingLocal = _keys [ idx ] ;
7548- currentOffset += existingLocal . type . byteSize ;
7593+ // If envSlotIndex is already set (from variable declaration), use it
7594+ if ( local . envSlotIndex >= 0 ) {
7595+ captures . set ( local , local . envSlotIndex ) ;
7596+ } else {
7597+ // Calculate proper byte offset based on existing captures with alignment
7598+ // We need to compute the end of the last capture (including its size)
7599+ let currentOffset = 0 ;
7600+ for ( let _keys = Map_keys ( captures ) , idx = 0 , cnt = _keys . length ; idx < cnt ; ++ idx ) {
7601+ let existingLocal = _keys [ idx ] ;
7602+ // The slot index already accounts for alignment, add the size to get next free offset
7603+ let endOfSlot = existingLocal . envSlotIndex + existingLocal . type . byteSize ;
7604+ if ( endOfSlot > currentOffset ) currentOffset = endOfSlot ;
7605+ }
7606+ // Align to the type's natural alignment
7607+ let typeSize = local . type . byteSize ;
7608+ let align = typeSize ;
7609+ currentOffset = ( currentOffset + align - 1 ) & ~ ( align - 1 ) ;
7610+ local . envSlotIndex = currentOffset ;
7611+ captures . set ( local , local . envSlotIndex ) ;
75497612 }
7550- local . envSlotIndex = currentOffset ;
7551- captures . set ( local , local . envSlotIndex ) ;
75527613 }
75537614 break ;
75547615 }
@@ -8102,6 +8163,64 @@ export class Compiler extends DiagnosticEmitter {
81028163 }
81038164 break ;
81048165 }
8166+ case NodeKind . ElementAccess : {
8167+ let elemAccess = < ElementAccessExpression > node ;
8168+ this . collectCapturedNames ( elemAccess . expression , innerFunctionNames , outerFlow , declaredVars , capturedNames ) ;
8169+ this . collectCapturedNames ( elemAccess . elementExpression , innerFunctionNames , outerFlow , declaredVars , capturedNames ) ;
8170+ break ;
8171+ }
8172+ case NodeKind . PropertyAccess : {
8173+ let propAccess = < PropertyAccessExpression > node ;
8174+ this . collectCapturedNames ( propAccess . expression , innerFunctionNames , outerFlow , declaredVars , capturedNames ) ;
8175+ break ;
8176+ }
8177+ case NodeKind . Parenthesized : {
8178+ let paren = < ParenthesizedExpression > node ;
8179+ this . collectCapturedNames ( paren . expression , innerFunctionNames , outerFlow , declaredVars , capturedNames ) ;
8180+ break ;
8181+ }
8182+ case NodeKind . UnaryPrefix : {
8183+ let unary = < UnaryPrefixExpression > node ;
8184+ this . collectCapturedNames ( unary . operand , innerFunctionNames , outerFlow , declaredVars , capturedNames ) ;
8185+ break ;
8186+ }
8187+ case NodeKind . UnaryPostfix : {
8188+ let unary = < UnaryPostfixExpression > node ;
8189+ this . collectCapturedNames ( unary . operand , innerFunctionNames , outerFlow , declaredVars , capturedNames ) ;
8190+ break ;
8191+ }
8192+ case NodeKind . Ternary : {
8193+ let ternary = < TernaryExpression > node ;
8194+ this . collectCapturedNames ( ternary . condition , innerFunctionNames , outerFlow , declaredVars , capturedNames ) ;
8195+ this . collectCapturedNames ( ternary . ifThen , innerFunctionNames , outerFlow , declaredVars , capturedNames ) ;
8196+ this . collectCapturedNames ( ternary . ifElse , innerFunctionNames , outerFlow , declaredVars , capturedNames ) ;
8197+ break ;
8198+ }
8199+ case NodeKind . Assertion : {
8200+ let assertion = < AssertionExpression > node ;
8201+ this . collectCapturedNames ( assertion . expression , innerFunctionNames , outerFlow , declaredVars , capturedNames ) ;
8202+ break ;
8203+ }
8204+ case NodeKind . New : {
8205+ let newExpr = < NewExpression > node ;
8206+ let args = newExpr . args ;
8207+ for ( let i = 0 , k = args . length ; i < k ; i ++ ) {
8208+ this . collectCapturedNames ( args [ i ] , innerFunctionNames , outerFlow , declaredVars , capturedNames ) ;
8209+ }
8210+ break ;
8211+ }
8212+ case NodeKind . InstanceOf : {
8213+ let instanceOf = < InstanceOfExpression > node ;
8214+ this . collectCapturedNames ( instanceOf . expression , innerFunctionNames , outerFlow , declaredVars , capturedNames ) ;
8215+ break ;
8216+ }
8217+ case NodeKind . Comma : {
8218+ let comma = < CommaExpression > node ;
8219+ for ( let i = 0 , k = comma . expressions . length ; i < k ; i ++ ) {
8220+ this . collectCapturedNames ( comma . expressions [ i ] , innerFunctionNames , outerFlow , declaredVars , capturedNames ) ;
8221+ }
8222+ break ;
8223+ }
81058224 // Add more cases as needed for complete coverage
81068225 default : {
81078226 // For other nodes, recursively scan children
@@ -8112,19 +8231,17 @@ export class Compiler extends DiagnosticEmitter {
81128231
81138232 /** Computes the total size needed for a closure environment. */
81148233 private computeEnvironmentSize ( captures : Map < Local , i32 > ) : i32 {
8115- let size = 0 ;
8116- let usizeSize = this . options . usizeType . byteSize ;
8234+ // Calculate the total size based on already-assigned slot indices
8235+ // The envSlotIndex values were already assigned during capture analysis
8236+ let maxEnd = 0 ;
81178237 for ( let _keys = Map_keys ( captures ) , i = 0 , k = _keys . length ; i < k ; i ++ ) {
81188238 let local = _keys [ i ] ;
8119- // Align each slot to the type's natural alignment (at least pointer size)
8120- let typeSize = local . type . byteSize ;
8121- let align = typeSize < usizeSize ? usizeSize : typeSize ;
8122- size = ( size + align - 1 ) & ~ ( align - 1 ) ;
8123- local . envSlotIndex = size ; // Store the byte offset as slot index
8124- size += typeSize ;
8239+ let endOfSlot = local . envSlotIndex + local . type . byteSize ;
8240+ if ( endOfSlot > maxEnd ) maxEnd = endOfSlot ;
81258241 }
81268242 // Ensure total size is aligned to pointer size
8127- size = ( size + usizeSize - 1 ) & ~ ( usizeSize - 1 ) ;
8243+ let usizeSize = this . options . usizeType . byteSize ;
8244+ let size = ( maxEnd + usizeSize - 1 ) & ~ ( usizeSize - 1 ) ;
81288245 return size ;
81298246 }
81308247
@@ -8258,9 +8375,15 @@ export class Compiler extends DiagnosticEmitter {
82588375 return module . local_get ( currentFunc . envLocal . index , sizeTypeRef ) ;
82598376 }
82608377
8261- // Case 2: We're in a closure - load environment from the global
8378+ // Case 2: We're in a closure - use the cached local if available
82628379 // The environment was passed via the closure's _env field and stored to global
8263- // before the indirect call
8380+ // before the indirect call. We cache it in a local at function entry because
8381+ // nested indirect calls can overwrite the global.
8382+ if ( currentFunc . closureEnvLocal ) {
8383+ return module . local_get ( currentFunc . closureEnvLocal . index , sizeTypeRef ) ;
8384+ }
8385+
8386+ // Case 3: Fallback to global (shouldn't normally happen for closures)
82648387 let closureEnvGlobal = this . ensureClosureEnvironmentGlobal ( ) ;
82658388 return module . global_get ( closureEnvGlobal , sizeTypeRef ) ;
82668389 }
@@ -8544,7 +8667,7 @@ export class Compiler extends DiagnosticEmitter {
85448667 // Handle closure access BEFORE initialization check
85458668 // Captured variables are stored in the environment, not in flow locals
85468669 if ( ! local . declaredByFlow ( flow ) ) {
8547- // Closure: load from environment
8670+ // Closure: load from environment (from inner function)
85488671 if ( local . isCaptured && local . envSlotIndex >= 0 ) {
85498672 this . currentType = localType ;
85508673 return this . compileClosureLoad ( local , expression ) ;
@@ -8557,6 +8680,13 @@ export class Compiler extends DiagnosticEmitter {
85578680 ) ;
85588681 return module . unreachable ( ) ;
85598682 }
8683+ // Also handle captured locals in the declaring function
8684+ // When a local is captured, its value lives in the environment
8685+ let sourceFunc = flow . sourceFunction ;
8686+ if ( local . isCaptured && local . envSlotIndex >= 0 && sourceFunc . envLocal ) {
8687+ this . currentType = localType ;
8688+ return this . compileClosureLoad ( local , expression ) ;
8689+ }
85608690
85618691 if ( ! flow . isLocalFlag ( localIndex , LocalFlags . Initialized ) ) {
85628692 this . error (
0 commit comments