55 * LICENSE file in the root directory of this source tree.
66 */
77
8- import { CompilerError } from '..' ;
8+ import prettyFormat from 'pretty-format' ;
9+ import { CompilerError , SourceLocation } from '..' ;
910import {
1011 Effect ,
1112 HIRFunction ,
1213 Identifier ,
1314 IdentifierId ,
15+ InstructionId ,
1416 makeInstructionId ,
17+ Place ,
1518} from '../HIR/HIR' ;
1619import {
1720 eachInstructionLValue ,
1821 eachInstructionValueOperand ,
1922 eachTerminalOperand ,
2023} from '../HIR/visitors' ;
21- import DisjointSet from '../Utils/DisjointSet' ;
22- import { assertExhaustive } from '../Utils/utils' ;
23- import { debugAliases } from './InferMutableRanges' ;
24- import { inferMutableRangesForAlias } from './InferMutableRangesForAlias' ;
24+ import { assertExhaustive , getOrInsertWith } from '../Utils/utils' ;
25+ import { printFunction } from '../HIR' ;
26+ import { printInstruction } from '../HIR/PrintHIR' ;
27+
28+ const DEBUG = false ;
2529
2630/**
2731 * Infers mutable ranges for all values.
2832 */
2933export function inferMutationAliasingRanges ( fn : HIRFunction ) : void {
34+ if ( DEBUG ) {
35+ console . log ( ) ;
36+ }
3037 /**
31- * Part 1
32- * Infer ranges for transitive mutations, which includes mutations that affect
33- * captured references and not just direct aliases. We build a distjoing set
34- * that tracks capturing and direct aliasing, and look at transitive mutations
35- * only.
38+ * Part 1: Infer mutable ranges for values. We build an abstract model
39+ * of the effect types, distinguishing values which can capture references
40+ * to other values and variables, which can point to one or more values.
41+ *
42+ * Transitive mutation marks value identifiers as mutated and also walks
43+ * into the identifiers captured by that abstract value: local mutation
44+ * only marks the top-level identifiers as mutated.
45+ *
46+ * TODO: add a fixpoint.
3647 */
37- const captures = new DisjointSet < Identifier > ( ) ;
48+ const state = new AliasingState ( ) ;
49+ for ( const param of fn . context ) {
50+ state . create ( param ) ;
51+ }
3852 for ( const block of fn . body . blocks . values ( ) ) {
3953 for ( const phi of block . phis ) {
40- captures . union ( [
41- phi . place . identifier ,
42- ... [ ... phi . operands . values ( ) ] . map ( place => place . identifier ) ,
43- ] ) ;
54+ state . create ( phi . place ) ;
55+ for ( const operand of phi . operands . values ( ) ) {
56+ state . alias ( operand , phi . place ) ;
57+ }
4458 }
4559
4660 for ( const instr of block . instructions ) {
4761 for ( const lvalue of eachInstructionLValue ( instr ) ) {
62+ state . create ( lvalue ) ;
4863 if ( lvalue . identifier . mutableRange . start === 0 ) {
4964 lvalue . identifier . mutableRange . start = instr . id ;
5065 }
@@ -55,84 +70,46 @@ export function inferMutationAliasingRanges(fn: HIRFunction): void {
5570
5671 if ( instr . effects == null ) continue ;
5772 for ( const effect of instr . effects ) {
58- if (
59- effect . kind === 'Assign' ||
60- effect . kind === 'Alias' ||
61- effect . kind === 'CreateFrom' ||
62- effect . kind === 'Capture'
63- ) {
64- captures . union ( [ effect . from . identifier , effect . into . identifier ] ) ;
73+ if ( effect . kind === 'Create' || effect . kind === 'CreateFunction' ) {
74+ state . create ( effect . into ) ;
75+ } else if ( effect . kind === 'Assign' ) {
76+ state . assign ( effect . from , effect . into ) ;
77+ } else if ( effect . kind === 'Alias' || effect . kind === 'CreateFrom' ) {
78+ state . alias ( effect . from , effect . into ) ;
79+ } else if ( effect . kind === 'Capture' ) {
80+ state . capture ( effect . from , effect . into ) ;
6581 } else if (
6682 effect . kind === 'MutateTransitive' ||
6783 effect . kind === 'MutateTransitiveConditionally'
6884 ) {
69- const value = effect . value ;
70- value . identifier . mutableRange . end = makeInstructionId ( instr . id + 1 ) ;
71- }
72- }
73- }
74- }
75- /**
76- * TODO: this will incorrectly mark values as mutated in the following case:
77- * 1. Create value x
78- * 2. Create value y
79- * 3. Transitively mutate y
80- * 4. Capture x -> y
81- *
82- * We will put these all into one alias set in captures, and then InferMutableRangesForAlias
83- * will look at all identifiers in the set that start before the mutation. Thus it will see
84- * that x is created before the mutation, and consider it mutated. But the capture doesn't
85- * occur until after the mutation!
86- *
87- * The fix is to use a graph to precisely describe what is captured where at each instruction,
88- * so that on a transitive mutation we can iterate all the captured values and mark them.
89- *
90- * This then needs a fixpoint: although we would track captures for phi operands, the operands'
91- * own capture values won't be populated until we do a fixpoint.
92- */
93- inferMutableRangesForAlias ( fn , captures ) ;
94-
95- /**
96- * Part 2
97- * Infer ranges for local (non-transitive) mutations. We build a disjoint set
98- * that only tracks direct value aliasing, and look only at local mutations
99- * to extend ranges
100- *
101- * TODO: similar design to the above todo for captures, use a directed graph instead of disjoint union,
102- * with fixpoint.
103- */
104- const aliases = new DisjointSet < Identifier > ( ) ;
105- for ( const block of fn . body . blocks . values ( ) ) {
106- for ( const phi of block . phis ) {
107- aliases . union ( [
108- phi . place . identifier ,
109- ...[ ...phi . operands . values ( ) ] . map ( place => place . identifier ) ,
110- ] ) ;
111- }
112-
113- for ( const instr of block . instructions ) {
114- if ( instr . effects == null ) continue ;
115- for ( const effect of instr . effects ) {
116- if (
117- effect . kind === 'Assign' ||
118- effect . kind === 'Alias' ||
119- effect . kind === 'CreateFrom'
120- ) {
121- aliases . union ( [ effect . from . identifier , effect . into . identifier ] ) ;
85+ state . mutateTransitive (
86+ effect . value . identifier ,
87+ makeInstructionId ( instr . id + 1 ) ,
88+ effect . value . loc ,
89+ ) ;
12290 } else if (
12391 effect . kind === 'Mutate' ||
12492 effect . kind === 'MutateConditionally'
12593 ) {
126- const value = effect . value ;
127- value . identifier . mutableRange . end = makeInstructionId ( instr . id + 1 ) ;
94+ state . mutate (
95+ effect . value . identifier ,
96+ makeInstructionId ( instr . id + 1 ) ,
97+ effect . value . loc ,
98+ ) ;
12899 }
129100 }
101+ if ( DEBUG ) {
102+ console . log ( printInstruction ( instr ) ) ;
103+ console . log ( state . debug ( ) ) ;
104+ }
130105 }
131106 }
132- inferMutableRangesForAlias ( fn , aliases ) ;
107+ if ( DEBUG ) {
108+ console . log ( state . debug ( ) ) ;
109+ }
133110
134111 /**
135- * Part 3
112+ * Part 2
136113 * Add legacy operand-specific effects based on instruction effects and mutable ranges.
137114 * Also fixes up operand mutable ranges, making sure that start is non-zero if the value
138115 * is mutated (depended on by later passes like InferReactiveScopeVariables which uses this
@@ -239,4 +216,138 @@ export function inferMutationAliasingRanges(fn: HIRFunction): void {
239216 operand . effect = Effect . Read ;
240217 }
241218 }
219+
220+ if ( DEBUG ) {
221+ console . log ( printFunction ( fn ) ) ;
222+ }
223+ }
224+
225+ /**
226+ * TODO: similar to alias effect inference state:
227+ * - values represent the conceptual values (context vars, things that got Create*-ed)
228+ * into which other values are captured
229+ * - variables deals with aliases (Assign, Alias, CreateFrom)
230+ *
231+ * Ex:
232+ * `Capture a -> b`:
233+ * - find the values represented by `a` (aValues) by looking up a in .variables, then mapping to .values
234+ * - find the values represented by `b` (bValues) by looking up b in .variables, then mapping to .values
235+ * - add aValues into bValues
236+ */
237+ class AliasingState {
238+ values : Map < Identifier , Set < Identifier > > = new Map ( ) ;
239+ variables : Map < IdentifierId , Set < Identifier > > = new Map ( ) ;
240+
241+ create ( place : Place ) : void {
242+ this . values . set ( place . identifier , new Set ( ) ) ;
243+ this . variables . set ( place . identifier . id , new Set ( [ place . identifier ] ) ) ;
244+ }
245+
246+ clear ( lvalue : Place ) : void {
247+ this . variables . set ( lvalue . identifier . id , new Set ( ) ) ;
248+ }
249+
250+ assign ( from : Place , into : Place ) : void {
251+ const fromVariables = this . variables . get ( from . identifier . id ) ;
252+ if ( fromVariables == null ) {
253+ return ;
254+ }
255+ this . variables . set (
256+ into . identifier . id ,
257+ new Set ( [ ...fromVariables , from . identifier , into . identifier ] ) ,
258+ // fromVariables,
259+ ) ;
260+ }
261+
262+ alias ( from : Place , into : Place ) : void {
263+ const intoVariables = getOrInsertWith (
264+ this . variables ,
265+ into . identifier . id ,
266+ ( ) => new Set ( ) ,
267+ ) ;
268+ intoVariables . add ( from . identifier ) ;
269+ }
270+
271+ capture ( from : Place , into : Place ) : void {
272+ const intoVariables = this . variables . get ( into . identifier . id ) ! ;
273+ for ( const v of intoVariables ) {
274+ const values = this . values . get ( v ) ! ;
275+ values . add ( from . identifier ) ;
276+ }
277+ }
278+
279+ mutateTransitive (
280+ place : Identifier ,
281+ end : InstructionId ,
282+ loc : SourceLocation ,
283+ ) : void {
284+ const variables = this . variables . get ( place . id ) ;
285+ if ( variables == null ) {
286+ return ;
287+ }
288+ for ( const value of variables ) {
289+ value . mutableRange . end = makeInstructionId (
290+ Math . max ( value . mutableRange . end , end ) ,
291+ ) ;
292+ const captured = this . values . get ( value ) ! ;
293+ for ( const capture of captured ) {
294+ this . mutateTransitive ( capture , end , loc ) ;
295+ }
296+ }
297+ }
298+
299+ mutate ( place : Identifier , end : InstructionId , _loc : SourceLocation ) : void {
300+ const variables = this . variables . get ( place . id ) ;
301+ if ( variables == null ) {
302+ return ;
303+ }
304+ place . mutableRange . end = makeInstructionId (
305+ Math . max ( place . mutableRange . end , end ) ,
306+ ) ;
307+ for ( const value of variables ) {
308+ // Unlike mutateTransitive, we don't traverse into captured values
309+ value . mutableRange . end = makeInstructionId (
310+ Math . max ( value . mutableRange . end , end ) ,
311+ ) ;
312+ }
313+ }
314+
315+ canonicalize ( ) : Map < IdentifierId , string > {
316+ const map = new Map < IdentifierId , string > ( ) ;
317+ for ( const value of this . values . keys ( ) ) {
318+ map . set (
319+ value . id ,
320+ `${ value . mutableRange . start } :${ value . mutableRange . end } ` ,
321+ ) ;
322+ }
323+ return map ;
324+ }
325+
326+ debug ( ) : string {
327+ const values = new Map < string , string > ( ) ;
328+ for ( const [ k , v ] of this . values ) {
329+ values . set (
330+ `${ k . name ?. value ?? '' } $${ k . id } :${ k . mutableRange . start } :${ k . mutableRange . end } ` ,
331+ [ ...v ]
332+ . map (
333+ v =>
334+ `${ v . name ?. value ?? '' } $${ v . id } :${ v . mutableRange . start } :${ v . mutableRange . end } ` ,
335+ )
336+ . join ( ', ' ) ,
337+ ) ;
338+ }
339+ const variables = new Map < string , string > ( ) ;
340+ for ( const [ k , v ] of this . variables ) {
341+ variables . set (
342+ `$${ k } ` ,
343+ [ ...v ]
344+ . map (
345+ v =>
346+ `${ v . name ?. value ?? '' } $${ v . id } :${ v . mutableRange . start } :${ v . mutableRange . end } ` ,
347+ )
348+ . join ( ', ' ) ,
349+ ) ;
350+ }
351+ return prettyFormat ( { values, variables} ) ;
352+ }
242353}
0 commit comments