@@ -15,6 +15,7 @@ import { guard } from '../../util/assert';
1515import type { ControlDependency } from '../info' ;
1616import { happensInEveryBranch } from '../info' ;
1717import { uniqueMergeValuesInDefinitions } from './append' ;
18+ import { Frame , MemoryView , WritableMemory } from './frame-memory' ;
1819import type { NodeId } from '../../r-bridge/lang-4.x/ast/model/processing/node-id' ;
1920import { log } from '../../util/log' ;
2021
@@ -25,7 +26,7 @@ export interface IEnvironment {
2526 /** Lexical parent of the environment, if any (can be manipulated by R code) */
2627 parent : IEnvironment
2728 /** Maps to exactly one definition of an identifier if the source is known, otherwise to a list of all possible definitions */
28- memory : BuiltInMemory
29+ memory : MemoryView
2930 /** Built-in environment that must not change; only for the top-most envs. */
3031 builtInEnv ?: true | undefined
3132}
@@ -43,7 +44,7 @@ interface Jsonified {
4344 id : NodeId ;
4445 parent : Jsonified | undefined ;
4546 builtInEnv ?: true ;
46- memory : BuiltInMemory ;
47+ memory : ReadonlyMap < BrandedIdentifier , IdentifierDefinition [ ] > ;
4748 n ?: string ;
4849 t ?: EnvType ;
4950 globalEnv ?: true ;
@@ -66,25 +67,36 @@ export class Environment implements IEnvironment {
6667 /** if created by a closure, the node id of that closure */
6768 private c ?: NodeId ;
6869 parent : Environment ;
69- memory : BuiltInMemory ;
70+ /** the frame this environment's names live in, shared with every other snapshot of it */
71+ private frame : Frame ;
72+ /** the version of {@link frame} this environment sees */
73+ private fv : number ;
74+ private view ?: MemoryView ;
75+ private writable ?: WritableMemory ;
7076 cache ?: Map < Identifier , IdentifierDefinition [ ] > ;
77+ /** what a resolution found below this frame, by target and name; only frames nothing writes keep one */
78+ tailCache ?: Map < string , readonly IdentifierDefinition [ ] | undefined > ;
7179 builtInEnv ?: true ;
72- /** {@link memory } is shared with a clone; writing needs { @link writableMemory} to unshare it first */
80+ /** {@link memory } may be seen by another snapshot, so a write has to take a version of its own first */
7381 private sharedMemory ?: true ;
7482 /** {@link parent } is shared with the environment this was cloned from; writing through it needs {@link writableParent} */
7583 private sharedParent ?: true ;
7684 /** marks the global environment (`.GlobalEnv`); attached packages (see {@link EnvType}) live below it */
7785 globalEnv ?: true ;
7886 /** What the lexical frame this stands in for held when its closure was created; `<<-` binds lexically, so defining super needs this. */
79- superMemory ?: BuiltInMemory ;
87+ superMemory ?: MemoryView ;
8088 /** What the configuration states about packages R does not attach on startup, by package; only the built-in env carries it (see {@link statedIn}). */
8189 namespaces ?: ReadonlyMap < string , BuiltInMemory > ;
8290
83- constructor ( parent : Environment , isBuiltInDefault : true | undefined = undefined , memory ?: BuiltInMemory ) {
91+ constructor ( parent : Environment , isBuiltInDefault : true | undefined = undefined , frame ?: Frame , version ?: number ) {
8492 this . id = isBuiltInDefault ? 0 : environmentIdCounter ++ ;
8593 this . parent = parent ;
86- /* a clone hands its own (then shared) memory in, so the fresh map a new frame needs is not allocated to be dropped */
87- this . memory = memory ?? new Map < BrandedIdentifier , IdentifierDefinition [ ] > ( ) ;
94+ this . frame = frame ?? Frame . empty ( ) ;
95+ this . fv = version ?? this . frame . version ;
96+ if ( frame === undefined ) {
97+ /* the shared empty frame, so the first write forks it */
98+ this . sharedMemory = true ;
99+ }
88100 // do not store if not needed!
89101 if ( isBuiltInDefault ) {
90102 this . builtInEnv = isBuiltInDefault ;
@@ -105,7 +117,7 @@ export class Environment implements IEnvironment {
105117 }
106118
107119 /** Records the lexical frame this one stands in for; see {@link superMemory}. */
108- public standsInFor ( memory : BuiltInMemory ) : this {
120+ public standsInFor ( memory : MemoryView ) : this {
109121 this . superMemory = memory ;
110122 return this ;
111123 }
@@ -134,16 +146,48 @@ export class Environment implements IEnvironment {
134146 return this . parent ;
135147 }
136148
137- /**
138- * This environment's {@link memory}, ready to be written to; every in-place write must go through this rather
139- * than {@link memory} directly, since {@link clone} shares the map and only the first writer copies it.
140- */
141- public get writableMemory ( ) : BuiltInMemory {
149+ /** What the frame binds at the version this sees; a snapshot, not a copy. */
150+ public get memory ( ) : MemoryView {
151+ return this . view ??= new MemoryView ( this . frame , this . fv ) ;
152+ }
153+
154+ /** Whether `other` holds the very bindings this does. */
155+ public sameMemoryAs ( other : Environment ) : boolean {
156+ return this === other || ( this . frame === other . frame && this . fv === other . fv ) ;
157+ }
158+
159+ /** What `name` holds here. {@link memory}'s answer without the view, for the chain walks of name resolution. */
160+ public lookup ( name : BrandedIdentifier ) : IdentifierDefinition [ ] | undefined {
161+ const frame = this . frame ;
162+ return this . fv === frame . version ? frame . live . get ( name ) : frame . get ( name , this . fv ) ;
163+ }
164+
165+ /** the bindings as a plain map where one is at hand, so a merge copies map from map */
166+ private get copyableMemory ( ) : ReadonlyMap < BrandedIdentifier , IdentifierDefinition [ ] > {
167+ return this . frame . settledAt ( this . fv ) ?? this . memory ;
168+ }
169+
170+ /** Takes `map` as the bindings, sharing it until something writes. */
171+ public adoptMap ( map : ReadonlyMap < BrandedIdentifier , IdentifierDefinition [ ] > ) : void {
172+ this . frame = Frame . of ( map ) ;
173+ this . fv = this . frame . version ;
174+ this . view = undefined ;
175+ this . writable = undefined ;
176+ /* the map belongs to whoever handed it over, so the first write forks */
177+ this . sharedMemory = true ;
178+ }
179+
180+ /** {@link memory } ready to be written to; every write goes through this, so the first takes a version of its own. */
181+ public get writableMemory ( ) : WritableMemory {
182+ this . tailCache = undefined ;
142183 if ( this . sharedMemory ) {
143- this . memory = new Map ( this . memory ) ;
184+ this . frame = this . frame . forWrite ( this . fv ) ;
185+ this . fv = this . frame . nextVersion ( ) ;
144186 this . sharedMemory = undefined ;
187+ this . view = undefined ;
188+ this . writable = undefined ;
145189 }
146- return this . memory ;
190+ return this . writable ??= new WritableMemory ( this . frame , this . fv ) ;
147191 }
148192
149193 /**
@@ -155,7 +199,7 @@ export class Environment implements IEnvironment {
155199 return this ; // do not clone the built-in environment
156200 }
157201
158- const clone = new Environment ( this . parent , this . builtInEnv , this . memory ) ;
202+ const clone = new Environment ( this . parent , this . builtInEnv , this . frame , this . fv ) ;
159203 clone . c = this . c ;
160204 clone . n = this . n ;
161205 clone . t = this . t ;
@@ -203,7 +247,7 @@ export class Environment implements IEnvironment {
203247 if ( definition . cds === undefined ) {
204248 this . writableMemory . set ( name , [ definition ] ) ;
205249 } else {
206- const existing = this . memory . get ( name ) ;
250+ const existing = this . lookup ( name ) ;
207251 const inGraphDefinition = definition as InGraphIdentifierDefinition ;
208252 if (
209253 existing !== undefined &&
@@ -267,7 +311,7 @@ export class Environment implements IEnvironment {
267311 do {
268312 /* `<<-` binds in the closest enclosing frame that holds the name, which for an emptied frame is what
269313 * it stood in for when the closure was created (see {@link superMemory}) */
270- if ( current . memory . has ( name ) || current . superMemory ?. has ( name ) ) {
314+ if ( current . lookup ( name ) !== undefined || current . superMemory ?. has ( name ) ) {
271315 current . writableMemory . set ( name , [ definition ] ) ;
272316 found = true ;
273317 break ;
@@ -298,7 +342,7 @@ export class Environment implements IEnvironment {
298342 if ( shortcut !== undefined ) {
299343 return shortcut ;
300344 }
301- const map = new Map ( this . memory ) ;
345+ const map = new Map ( this . copyableMemory ) ;
302346 for ( const [ key , values ] of other . memory ) {
303347 const hasMaybe = applyCds === undefined ? values . length === 0 || values . some ( v => v . cds !== undefined ) : true ;
304348 if ( hasMaybe ) {
@@ -335,7 +379,7 @@ export class Environment implements IEnvironment {
335379 out . t = this . t ;
336380 out . globalEnv = this . globalEnv ;
337381 out . superMemory = this . superMemory ?? other . superMemory ;
338- out . memory = map ;
382+ out . adoptMap ( map ) ;
339383 return out ;
340384 }
341385
@@ -348,7 +392,7 @@ export class Environment implements IEnvironment {
348392 if ( shortcut !== undefined ) {
349393 return shortcut ;
350394 }
351- const map = new Map ( this . memory ) ;
395+ const map = new Map ( this . copyableMemory ) ;
352396 for ( const [ key , value ] of other . memory ) {
353397 const old = map . get ( key ) ;
354398 if ( old ) {
@@ -364,7 +408,7 @@ export class Environment implements IEnvironment {
364408 out . t = this . t ;
365409 out . globalEnv = this . globalEnv ;
366410 out . superMemory = this . superMemory ?? other . superMemory ;
367- out . memory = map ;
411+ out . adoptMap ( map ) ;
368412 return out ;
369413 }
370414
@@ -420,7 +464,7 @@ export class Environment implements IEnvironment {
420464 const cloned = layer . clone ( false ) ;
421465 byName . set ( layer . n , cloned ) ;
422466 order . push ( cloned ) ;
423- } else if ( existing . memory !== layer . memory ) {
467+ } else if ( ! existing . sameMemoryAs ( layer ) ) {
424468 for ( const [ name , value ] of layer . memory ) {
425469 const old = existing . memory . get ( name ) ;
426470 if ( old !== value ) {
@@ -443,7 +487,7 @@ export class Environment implements IEnvironment {
443487 this . writableParent . remove ( id ) ;
444488 return this ;
445489 }
446- const definition = this . memory . get ( name ) ;
490+ const definition = this . lookup ( name ) ;
447491 let cont = true ;
448492 if ( definition !== undefined ) {
449493 this . writableMemory . delete ( name ) ;
@@ -474,11 +518,11 @@ export class Environment implements IEnvironment {
474518 id : this . id ,
475519 parent : this . parent ,
476520 builtInEnv : this . builtInEnv ,
477- memory : this . memory ,
521+ memory : new Map ( this . memory ) ,
478522 } : {
479523 id : this . id ,
480524 parent : this . parent ,
481- memory : this . memory ,
525+ memory : new Map ( this . memory ) ,
482526 // markers needed to rebuild the search path after a round-trip (undefined values are dropped by JSON.stringify)
483527 n : this . n ,
484528 t : this . t ,
@@ -601,7 +645,7 @@ function layersEndWith(this: void, layers: readonly Environment[], tail: readonl
601645 }
602646 for ( let i = 0 ; i < tail . length ; i ++ ) {
603647 const l = layers [ offset + i ] , t = tail [ i ] ;
604- if ( l !== t && ( l . t !== t . t || l . n !== t . n || l . memory !== t . memory ) ) {
648+ if ( l !== t && ( l . t !== t . t || l . n !== t . n || ! l . sameMemoryAs ( t ) ) ) {
605649 return false ;
606650 }
607651 }
0 commit comments