@@ -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 } ] node${ range } [` ) ;
273288 for ( const child of node . children ) {
274289 _printNode ( child , out , depth + 1 ) ;
0 commit comments