@@ -24,6 +24,17 @@ import {
2424}  from  "../HIR/visitors" ; 
2525import  {  getPlaceScope  }  from  "./BuildReactiveBlocks" ; 
2626
27+ type  InstructionRange  =  MutableRange ; 
28+ function  retainWhere_Set < T > ( 
29+   items : Set < T > , 
30+   predicate : ( item : T )  =>  boolean 
31+ ) : void { 
32+   for  ( const  item  of  items )  { 
33+     if  ( ! predicate ( item ) )  { 
34+       items . delete ( item ) ; 
35+     } 
36+   } 
37+ } 
2738/* 
2839 * Note: this is the 2nd of 4 passes that determine how to break a function into discrete 
2940 * reactive scopes (independently memoizeable units of code): 
@@ -66,18 +77,20 @@ import { getPlaceScope } from "./BuildReactiveBlocks";
6677 * will be the updated end for that scope). 
6778 */ 
6879export  function  alignReactiveScopesToBlockScopesHIR ( fn : HIRFunction ) : void { 
69-   const  blockNodes  =  new  Map < BlockId ,  BlockNode > ( ) ; 
70-   const  rootNode : BlockNode  =  { 
71-     kind : "node" , 
72-     valueRange : null , 
73-     children : [ ] , 
74-     id : makeInstructionId ( 0 ) , 
75-   } ; 
76-   blockNodes . set ( fn . body . entry ,  rootNode ) ; 
80+   const  activeInnerBlockRanges : Array < { 
81+     range : InstructionRange ; 
82+     fallthrough : BlockId ; 
83+   } >  =  [ ] ; 
84+   const  activeScopes  =  new  Set < ReactiveScope > ( ) ; 
7785  const  seen  =  new  Set < ReactiveScope > ( ) ; 
86+   const  valueBlockNodes  =  new  Map < BlockId ,  ValueBlockNode > ( ) ; 
7887  const  placeScopes  =  new  Map < Place ,  ReactiveScope > ( ) ; 
7988
80-   function  recordPlace ( id : InstructionId ,  place : Place ,  node : BlockNode ) : void { 
89+   function  recordPlace ( 
90+     id : InstructionId , 
91+     place : Place , 
92+     node : ValueBlockNode  |  null 
93+   ) : void { 
8194    if  ( place . identifier . scope  !==  null )  { 
8295      placeScopes . set ( place ,  place . identifier . scope ) ; 
8396    } 
@@ -86,13 +99,14 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
8699    if  ( scope  ==  null )  { 
87100      return ; 
88101    } 
89-     node . children . push ( {  kind : "scope" ,  scope,  id } ) ; 
102+     activeScopes . add ( scope ) ; 
103+     node ?. children . push ( {  kind : "scope" ,  scope,  id } ) ; 
90104
91105    if  ( seen . has ( scope ) )  { 
92106      return ; 
93107    } 
94108    seen . add ( scope ) ; 
95-     if  ( node . valueRange  !==  null )  { 
109+     if  ( node   !=   null   &&   node . valueRange  !==  null )  { 
96110      scope . range . start  =  makeInstructionId ( 
97111        Math . min ( node . valueRange . start ,  scope . range . start ) 
98112      ) ; 
@@ -103,16 +117,23 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
103117  } 
104118
105119  for  ( const  [ ,  block ]  of  fn . body . blocks )  { 
106-     const  {  instructions,  terminal }  =  block ; 
107-     const  node  =  blockNodes . get ( block . id ) ; 
108-     if  ( node  ===  undefined )  { 
109-       CompilerError . invariant ( false ,  { 
110-         reason : `Expected a node to be initialized for block` , 
111-         loc : instructions [ 0 ] ?. loc  ??  terminal . loc , 
112-         description : `No node for block bb${ block . id } ${ block . kind }  , 
113-       } ) ; 
120+     const  startingId  =  block . instructions [ 0 ] ?. id  ??  block . terminal . id ; 
121+     retainWhere_Set ( activeScopes ,  ( scope )  =>  scope . range . end  >=  startingId ) ; 
122+     const  top  =  activeInnerBlockRanges . at ( - 1 ) ; 
123+     if  ( top ?. fallthrough  ===  block . id )  { 
124+       activeInnerBlockRanges . pop ( ) ; 
125+       // All active scopes must have either started before or within the last 
126+       // block-fallthrough range. In either case, they overlap this block- 
127+       // fallthrough range and can have their ranges extended. 
128+       for  ( const  scope  of  activeScopes )  { 
129+         scope . range . start  =  makeInstructionId ( 
130+           Math . min ( scope . range . start ,  top . range . start ) 
131+         ) ; 
132+       } 
114133    } 
115134
135+     const  {  instructions,  terminal }  =  block ; 
136+     const  node  =  valueBlockNodes . get ( block . id )  ??  null ; 
116137    for  ( const  instr  of  instructions )  { 
117138      for  ( const  lvalue  of  eachInstructionLValue ( instr ) )  { 
118139        recordPlace ( instr . id ,  lvalue ,  node ) ; 
@@ -125,36 +146,42 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
125146      recordPlace ( terminal . id ,  operand ,  node ) ; 
126147    } 
127148
128-     // Save the current node for the fallback block, where this block scope continues 
129149    const  fallthrough  =  terminalFallthrough ( terminal ) ; 
130-     if  ( fallthrough  !==  null   &&   ! blockNodes . has ( fallthrough ) )  { 
150+     if  ( fallthrough  !==  null )  { 
131151      /* 
132-        * Any scopes that carried over across a terminal->fallback need their range extended 
133-        * to at least the first instruction of the fallback 
134-        * 
135-        * Note that it's possible for a terminal such as an if or switch to have a null fallback, 
136-        * indicating that all control-flow paths diverge instead of reaching the fallthrough. 
137-        * In this case there isn't an instruction id in the program that we can point to for the 
138-        * updated range. Since the output is correct in this case we leave it, but it would be 
139-        * more correct to find the maximum instuction id in the whole program and set the range.end 
140-        * to one greater. Alternatively, we could leave in an unreachable fallthrough (with a new 
141-        * "unreachable" terminal variant, perhaps) and use that instruction id. 
152+        * Any currently active scopes that overlaps the block-fallthrough range 
153+        * need their range extended to at least the first instruction of the 
154+        * fallthrough 
142155       */ 
143156      const  fallthroughBlock  =  fn . body . blocks . get ( fallthrough ) ! ; 
144157      const  nextId  = 
145158        fallthroughBlock . instructions [ 0 ] ?. id  ??  fallthroughBlock . terminal . id ; 
146-       for  ( const  child  of  node . children )  { 
147-         if  ( child . kind  !==  "scope" )  { 
148-           continue ; 
149-         } 
150-         const  scope  =  child . scope ; 
159+       for  ( const  scope  of  activeScopes )  { 
151160        if  ( scope . range . end  >  terminal . id )  { 
152161          scope . range . end  =  makeInstructionId ( 
153162            Math . max ( scope . range . end ,  nextId ) 
154163          ) ; 
155164        } 
156165      } 
157-       blockNodes . set ( fallthrough ,  node ) ; 
166+       /** 
167+        * We also record the block-fallthrough range for future scopes that begin 
168+        * within the range (and overlap with the range end). 
169+        */ 
170+       activeInnerBlockRanges . push ( { 
171+         fallthrough, 
172+         range : { 
173+           start : terminal . id , 
174+           end : nextId , 
175+         } , 
176+       } ) ; 
177+ 
178+       CompilerError . invariant ( ! valueBlockNodes . has ( fallthrough ) ,  { 
179+         reason : "Expect hir blocks to have unique fallthroughs" , 
180+         loc : terminal . loc , 
181+       } ) ; 
182+       if  ( node  !=  null )  { 
183+         valueBlockNodes . set ( fallthrough ,  node ) ; 
184+       } 
158185    } 
159186
160187    /* 
@@ -166,48 +193,35 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
166193     * just those that are direct successors for normal control-flow ordering. 
167194     */ 
168195    mapTerminalSuccessors ( terminal ,  ( successor )  =>  { 
169-       if  ( blockNodes . has ( successor ) )  { 
196+       if  ( valueBlockNodes . has ( successor ) )  { 
170197        return  successor ; 
171198      } 
172199
173200      const  successorBlock  =  fn . body . blocks . get ( successor ) ! ; 
174-       /* 
175-        * we need the block kind check here because the do..while terminal's successor 
176-        * is a block, and try's successor is a catch block 
177-        */ 
178201      if  ( successorBlock . kind  ===  "block"  ||  successorBlock . kind  ===  "catch" )  { 
179-         const  childNode : BlockNode  =  { 
180-           kind : "node" , 
181-           id : terminal . id , 
182-           children : [ ] , 
183-           valueRange : null , 
184-         } ; 
185-         node . children . push ( childNode ) ; 
186-         blockNodes . set ( successor ,  childNode ) ; 
202+         /* 
203+          * we need the block kind check here because the do..while terminal's 
204+          * successor is a block, and try's successor is a catch block 
205+          */ 
187206      }  else  if  ( 
188-         node . valueRange   = ==null  || 
207+         node   ==  null  || 
189208        terminal . kind  ===  "ternary"  || 
190209        terminal . kind  ===  "logical"  || 
191210        terminal . kind  ===  "optional" 
192211      )  { 
193212        /** 
194-          * Create a new scope  node whenever we transition from block scope  -> value scope . 
213+          * Create a new node whenever we transition from non-value  -> value block . 
195214         * 
196215         * For compatibility with the previous ReactiveFunction-based scope merging logic, 
197216         * we also create new scope nodes for ternary, logical, and optional terminals. 
198-          * However, inside  value blocks we always store a range (valueRange) that is the 
217+          * Inside  value blocks we always store a range (valueRange) that is the 
199218         * start/end instruction ids at the nearest parent block scope level, so that 
200219         * scopes inside the value blocks can be extended to align with block scope 
201220         * instructions. 
202221         */ 
203-         const  childNode  =  { 
204-           kind : "node" , 
205-           id : terminal . id , 
206-           children : [ ] , 
207-           valueRange : null , 
208-         }  as  BlockNode ; 
209-         if  ( node . valueRange  ===  null )  { 
210-           // Transition from block->value scope, derive the outer block scope range 
222+         let  valueRange : MutableRange ; 
223+         if  ( node  ==  null )  { 
224+           // Transition from block->value block, derive the outer block range 
211225          CompilerError . invariant ( fallthrough  !==  null ,  { 
212226            reason : `Expected a fallthrough for value block` , 
213227            loc : terminal . loc , 
@@ -216,46 +230,50 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
216230          const  nextId  = 
217231            fallthroughBlock . instructions [ 0 ] ?. id  ?? 
218232            fallthroughBlock . terminal . id ; 
219-           childNode . valueRange  =  { 
233+           valueRange  =  { 
220234            start : terminal . id , 
221235            end : nextId , 
222236          } ; 
223237        }  else  { 
224238          // else value->value transition, reuse the range 
225-           childNode . valueRange  =  node . valueRange ; 
239+           valueRange  =  node . valueRange ; 
226240        } 
227-         node . children . push ( childNode ) ; 
228-         blockNodes . set ( successor ,  childNode ) ; 
241+         const  childNode : ValueBlockNode  =  { 
242+           kind : "node" , 
243+           id : terminal . id , 
244+           children : [ ] , 
245+           valueRange, 
246+         } ; 
247+         node ?. children . push ( childNode ) ; 
248+         valueBlockNodes . set ( successor ,  childNode ) ; 
229249      }  else  { 
230250        // this is a value -> value block transition, reuse the node 
231-         blockNodes . set ( successor ,  node ) ; 
251+         valueBlockNodes . set ( successor ,  node ) ; 
232252      } 
233253      return  successor ; 
234254    } ) ; 
235255  } 
236- 
237-   // console.log(_debug(rootNode)); 
238256} 
239257
240- type  BlockNode  =  { 
258+ type  ValueBlockNode  =  { 
241259  kind : "node" ; 
242260  id : InstructionId ; 
243-   valueRange : MutableRange   |   null ; 
244-   children : Array < BlockNode  |  ReactiveScopeNode > ; 
261+   valueRange : MutableRange ; 
262+   children : Array < ValueBlockNode  |  ReactiveScopeNode > ; 
245263} ; 
246264type  ReactiveScopeNode  =  { 
247265  kind : "scope" ; 
248266  id : InstructionId ; 
249267  scope : ReactiveScope ; 
250268} ; 
251269
252- function  _debug ( node : BlockNode ) : string  { 
270+ function  _debug ( node : ValueBlockNode ) : string  { 
253271  const  buf : Array < string >  =  [ ] ; 
254272  _printNode ( node ,  buf ,  0 ) ; 
255273  return  buf . join ( "\n" ) ; 
256274} 
257275function  _printNode ( 
258-   node : BlockNode  |  ReactiveScopeNode , 
276+   node : ValueBlockNode  |  ReactiveScopeNode , 
259277  out : Array < string > , 
260278  depth : number  =  0 
261279) : void { 
@@ -265,10 +283,7 @@ function _printNode(
265283      `${ prefix } ${ node . id } ${ node . scope . id } ${ node . scope . range . start } ${ node . scope . range . end }  
266284    ) ; 
267285  }  else  { 
268-     let  range  = 
269-       node . valueRange  !==  null 
270-         ? ` [${ node . valueRange . start } ${ node . valueRange . end }  
271-         : "" ; 
286+     let  range  =  ` (range=[${ node . valueRange . start } ${ node . valueRange . end }  ; 
272287    out . push ( `${ prefix } ${ node . id } ${ range }  ) ; 
273288    for  ( const  child  of  node . children )  { 
274289      _printNode ( child ,  out ,  depth  +  1 ) ; 
0 commit comments