Repository navigation
Expand file tree
/
Copy pathlastuse.d
More file actions
1702 lines (1674 loc) · 67.3 KB
/
Copy pathlastuse.d
File metadata and controls
1702 lines (1674 loc) · 67.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Written in the D programming language
// License: http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0
module ast.lastuse;
import astopt;
import util: SetX, MapX, MapSX;
import util.tuple:Q=Tuple,q=tuple;
import std.range, std.algorithm, std.conv, std.format;
import ast.expression,ast.type,ast.declaration,ast.scope_;
bool canAddForget(Expression e){
if(auto le=cast(LetExp)e)
return !!le.isForward(true);
if(auto ce=cast(CompoundExp)e) // (`[stmt]` or `[stmt, forget(...)]`)
return !ce.blscope_&&(ce.s.length==1||ce.s.length==2&&cast(ForgetExp)ce.s[1]&&!(cast(ForgetExp)ce.s[1]).val);
return false;
}
Identifier addForget(Expression e,Declaration decl,Scope sc){
Identifier addToCompound(CompoundExp ce){
assert(!ce.blscope_);
if(ce.s.length==1){
ForgetExp makeForget(){
auto tpl=new TupleExp([]);
tpl.loc=e.loc;
tpl.constLookup=false;
tpl.type=unit;
tpl.setSemCompleted();
auto fe=new ForgetExp(tpl,null);
fe.loc=e.loc;
fe.constLookup=true;
fe.type=unit;
fe.setSemCompleted();
return fe;
}
ce.s~=makeForget();
}
assert(ce.s.length==2);
auto fe=cast(ForgetExp)ce.s[1];
assert(fe&&!fe.val);
auto id=new Identifier(decl.getId);
id.loc=e.loc;
id.scope_=sc;
id.meaning=decl;
id.byRef=true;
id.constLookup=false;
id.type=id.typeFromMeaning;
import ast.semantic_:propErr;
propErr(decl,id);
id.setSemCompleted();
auto tpl=cast(TupleExp)fe.var;
if(!tpl){ // (`forget` in the source, e.g., `let{ x:=e; forget(y); } in x`)
tpl=new TupleExp([fe.var]);
tpl.loc=fe.var.loc;
tpl.constLookup=false;
tpl.type=fe.var.type?tupleTy([fe.var.type]):null;
if(fe.var.isSemError()||!tpl.type) tpl.setSemForceError();
else tpl.setSemCompleted();
fe.var=tpl;
}
tpl.e~=id;
if(id.type&&tpl.type){
tpl.type=tupleTy(tpl.e.map!(e=>e.type).array); // TODO: avoid quadratic scaling
}else tpl.type=null;
assert(id.type||id.isSemError());
if(id.isSemError())
tpl.setSemForceError();
if(tpl.isSemError())
fe.setSemForceError();
if(fe.isSemError())
e.setSemForceError();
return id;
}
if(auto le=cast(LetExp)e){
assert(!!le.isForward(true));
return addToCompound(le.s);
}
if(auto ce=cast(CompoundExp)e)
return addToCompound(ce);
return null;
}
final class LastUse{
private this(LastUse.Kind kind,Scope scope_,Declaration decl,Identifier use,Expression parent){
this.kind=kind;
this.scope_=scope_;
this.decl=decl;
this.use=use;
this.parent=parent;
}
private this(LastUse.Kind kind,Scope scope_,Declaration decl,Identifier use,Expression parent,NestedScope[] nestedScopes){
this(kind,scope_,decl,use,parent);
this.nestedScopes=nestedScopes;
}
override string toString(){
string r="LastUse(";
static foreach(i,alias m;this.tupleof){
static if(!is(typeof(m):LastUse)){
static if(i) r~=",";
r~=text(m);
}
}
r~=")";
return r;
}
LastUse dup(){
// copies all fields; reference fields (decl, scope_, links, nested
// scopes) still refer to the original graph—callers remap them as
// needed (see LastUses.remapFrom)
auto r=new LastUse(kind,scope_,decl,use,parent,nestedScopes.dup);
r.constBlock=constBlock;
r.dep=dep;
r.depAtSplit=depAtSplit;
r.forwardTo=forwardTo;
r.constConsume=constConsume;
r.prevImplicitDup=prevImplicitDup;
r.splitFrom=splitFrom;
r.splitSource=splitSource;
r.prev=prev;
r.next=next;
r.index=index;
r.consumedComponent=consumedComponent;
r.loopGuard=loopGuard;
r.discarded=discarded;
r.atSplit=atSplit;
r.time=time;
r.consumedAt=consumedAt;
return r;
}
enum Kind{
definition,
constPinned,
lazySplitSink,
lazySplit,
lazyMerge,
synthesizedForget,
implicitDup,
constUse,
consumption,
capture,
}
Kind kind; // TODO: sum type?
bool discarded=false; // (from an analysis that was discarded, see `restoreSnapshot`)
bool atSplit=false; // (a forget of a version that is not used after its split, which uncomputes it there)
size_t time,consumedAt; // (in the order of the analysis, see `LastUses.tick`)
static bool isBranch(Scope sc){ auto bs=cast(BlockScope)sc; return bs&&bs.isBranch; }
static bool interleavable(Scope sc){ auto bs=cast(BlockScope)sc; return bs&&bs.isBranch&&!bs.returningConditional; } // (the code generator can interleave the statements of the branches of its conditional, see `checkBranchOrder`)
Scope scope_;
Declaration decl=null;
Identifier use=null;
Identifier[] constBlock;
Expression parent=null;
auto dep=Dependency(true);
auto depAtSplit=Dependency(true); // (of a version in a branch: its dependency at the split, which the consumptions in the branches do not change)
LastUse forwardTo=null;
LastUse constConsume=null;
LastUse prevImplicitDup=null;
LastUse splitFrom=null;
LastUse splitSource=null;
NestedScope[] nestedScopes;
NestedScope[] nestedAfter;
LastUse prev=null,next=null;
size_t index; // (in the order the last uses of the scope were added)
IndexExp consumedComponent=null; // (a consumption allowed because this specific component is not borrowed)
bool loopGuard=false; // (a use in a `while` condition, which is also evaluated on the path that exits the loop)
bool isConsumption(){
if(!!kind.among(Kind.consumption,Kind.synthesizedForget)) return true;
if(forwardTo) return forwardTo.isConsumption();
return false;
}
LastUse getSplitFrom()in{
assert(kind==Kind.lazySplit);
}do{
if(splitFrom) return splitFrom;
auto nsc=cast(NestedScope)scope_;
assert(!!nsc);
auto ndecl=nsc.parent.getSplit(decl);
while(ndecl.splitFrom&&ndecl.splitFrom.scope_.isNestedIn(nsc.parent))
ndecl=ndecl.splitFrom;
auto plu=nsc.parent.lastUses.lastUses.get(ndecl,null);
assert(!!plu,text(ndecl," ",decl," ",nsc.parent.lastUses.lastUses," ",nsc.parent.lastUses.lastUses.get(decl,null)," ",cast(void*)decl," ",cast(void*)ndecl));
return splitFrom=plu;
}
static Declaration versionIn(Declaration decl,NestedScope nsc){
foreach(d;decl.mergedFrom) if(d.scope_ is nsc) return d;
foreach(d;decl.splitInto) if(d.scope_ is nsc) return d;
return decl;
}
static bool isNontrivialMerge(Declaration decl,scope NestedScope[] nestedScopes){
return iota(nestedScopes.length).any!((i){
auto nsc=nestedScopes[i];
auto cdecl=decl;
foreach(d;decl.mergedFrom) if(d.scope_ is nsc) cdecl=d;
if(decl !is cdecl) return true;
auto nlu=nsc.lastUses.lastUses.get(cdecl,null);
if(!nlu) return false;
while(nlu.forwardTo) nlu=nlu.forwardTo;
if(nlu.kind!=Kind.lazySplit) return true;
return false;
});
}
static Forgettability getMergeForgettability(Declaration decl,scope NestedScope[] nestedScopes,bool forceHere,bool forceConsumed=false){
//imported!"util.io".writeln("CAN FORGET MERGE: ",decl," ",cast(void*)decl.scope_," ",decl.mergedFrom," ",nestedScopes.map!(sc=>cast(void*)sc));
return iota(nestedScopes.length).map!((i){
auto nsc=nestedScopes[i];
auto cdecl=versionIn(decl,nestedScopes[i]);
//imported!"util.io".writeln("CHECKING: ",decl," ",nsc.lastUses.getForgettability(cdecl,forceHere,false)," ",nsc.lastUses.getForgettability(decl,forceHere,false)," ",nsc.lastUses.lastUses.get(cdecl,null)," ",cdecl is decl);
return nsc.lastUses.getForgettability(cdecl,forceHere,forceConsumed);
}).fold!((a,b){
auto r=min(a,b);
if(r==Forgettability.none) return r;
if(a==Forgettability.consumable||b==Forgettability.consumable) return Forgettability.consumable;
return r;
})(Forgettability.forgettable);
}
static bool canForgetMerge(Declaration decl,scope NestedScope[] nestedScopes,bool forceHere,bool forceConsumed){
return getMergeForgettability(decl,nestedScopes,forceHere,forceConsumed)>=(forceConsumed?Forgettability.consumable:Forgettability.forgettable);
}
static bool mergesSameValue(Declaration decl,NestedScope nsc){
return decl.isDerivedFrom(versionIn(decl,nsc).canonicalSource);
}
static bool canCancelImplicitDupMerge(Declaration decl,scope NestedScope[] nestedScopes){
return iota(nestedScopes.length).map!((i){
auto nsc=nestedScopes[i];
if(!mergesSameValue(decl,nsc)) return false;
auto cdecl=versionIn(decl,nestedScopes[i]);
return nsc.lastUses.canCancelImplicitDup(cdecl);
}).any;
}
void remove(){
assert(!prev||prev.next is this);
assert(!next||next.prev is this);
if(prev) prev.next=next;
if(next) next.prev=prev;
if(prev) prev.nestedAfter~=nestedAfter;
nestedAfter=[];
prev=next=null;
}
void prepend(LastUse prev)in{
assert(!this.prev);
assert(!prev.next);
}do{
this.prev=prev;
prev.next=this;
// TODO: perform necessary updates
}
void append(LastUse next)in{
assert(!this.next);
assert(!next.prev);
}do{
this.next=next;
next.prev=this;
// TODO: perform necessary updates
//imported!"util.io".writeln("APPENDING: ",this," ⇒ ",next);
}
void pin(){
if(forwardTo) forwardTo.pin();
if(splitFrom) splitFrom.pin();
if(isConsumption()) return;
//imported!"util.io".writeln("PINNING: ",this);
kind=Kind.constPinned;
}
void pinHere(){ // (not the versions it is split from)
if(forwardTo) forwardTo.pinHere();
if(isConsumption()) return;
kind=Kind.constPinned;
}
private void removeFromUnusedSiblings(){
auto nsc=cast(NestedScope)scope_;
if(!nsc) return;
foreach(sib;nsc.getSiblingScopes()){
if(sib is nsc) continue;
if(sib.rnsymtab.get(decl.getId,null) !is decl) continue;
auto slu=sib.lastUses.lastUses.get(decl,null);
while(slu&&slu.kind==Kind.lazySplitSink) slu=slu.forwardTo;
if(!slu||slu.kind!=Kind.lazySplit||slu.forwardTo) continue;
sib.symtabRemove(decl);
slu.markConsumed(null,false);
}
}
private void markConsumed(Identifier theUse,bool isForget,bool fromNested=false,IndexExp component=null,bool copy=false){ // (`copy`: of a version that an enclosing scope consumes)
if(!use) use=theUse;
if(!consumedComponent&&use is theUse) consumedComponent=component; // (consumed by the same use)
//imported!"util.io".writeln("MARKING CONSUMED: ",this," ",use?text(use.loc):"<?>");
if(isConsumption()) return;
if(splitSource&&splitSource.splitFrom)
splitSource.splitFrom.markConsumed(theUse,false,true,component);
if(splitFrom) splitFrom.markConsumed(theUse,false,true,component);
if(isForget){
assert(!dep.isTop);
kind=Kind.synthesizedForget;
scope_.noteDependencyResolved(dep);
}else kind=Kind.consumption;
consumedAt=fromNested?scope_.lastUses.tick():time; // (a nested scope consumes it now)
if(!fromNested&&!copy) noteBranchEvent();
recordCancelable();
if(fromNested&&isBranchSplit()) updateDependenciesOnConsumption(); // (consumed in a nested scope)
else updateDependenciesOnConsumptionLocal(!fromNested);
}
private void noteBranchEvent(){ // (for the order of the branches of the enclosing conditionals, see `checkBranchOrder`)
for(auto nsc=cast(NestedScope)scope_;nsc;nsc=cast(NestedScope)nsc.parent){
auto bs=cast(BlockScope)nsc;
if(!interleavable(bs)) continue;
if(kind==Kind.synthesizedForget) bs.uncomputations~=this;
else if(auto outer=outerVersion(decl,bs)) bs.outerConsumptions~=q(outer,this);
}
}
static Declaration outerVersion(Declaration d,NestedScope sc){ // (the version of a variable of the enclosing scope of `sc` that `d` is split from, or that is split from `d`)
while(d&&d.scope_&&d.scope_.isNestedIn(sc)) d=d.splitFrom;
if(d&&d.scope_&&d.scope_ !is sc.parent&&sc.parent.isNestedIn(d.scope_)) d=sc.parent.getSplit(d); // (a version from further out)
return d&&d.scope_ is sc.parent?d:null;
}
static BlockScope splitBranch(LastUse lu){ // (the innermost branch that splits the variable from its enclosing scope, through blocks that run once)
for(auto nsc=cast(NestedScope)lu.scope_;nsc;nsc=cast(NestedScope)nsc.parent){
auto bs=cast(BlockScope)nsc;
if(!bs||bs.isLoopBody||bs.isLoopEntry||bs.isLoopReset) return null;
if(bs.isBranch&&outerVersion(lu.decl,bs)) return interleavable(bs)?bs:null;
}
return null;
}
Q!(LastUse,Dependency)[] cancelable; // (last uses that could have been cancelled when this consumption happened, with their dependencies at that time)
bool cancelableRecorded=false;
private void recordCancelable(){ // (only those that depend on `decl` indirectly, the others are cancelled directly)
if(cancelableRecorded) return; // (an implicit `dup` that is cancelled later consumes at the time of the `dup`)
cancelableRecorded=true;
static bool version_(Declaration x,Declaration y){ return x is y||x.isSplitFrom(y)||y.isSplitFrom(x); }
Declaration[] reached=[decl];
LastUse[] indirect;
for(bool changed=true;changed;){
changed=false;
foreach(d,lu;scope_.lastUses.lastUses){
while(lu.forwardTo) lu=lu.forwardTo;
if(lu is this||lu.dep.isTop||reached.canFind(d)) continue;
bool viaReached=false,direct=false;
foreach(x;lu.dep.dependencies){
if(reached.any!(y=>version_(x,y))) viaReached=true;
if(version_(x,decl)) direct=true;
}
if(!viaReached) continue;
if(!direct) indirect~=lu;
reached~=d;
changed=true;
}
}
foreach(lu;indirect) if(lu.canCancelImplicitDup()) cancelable~=q(lu,lu.dep.dup);
}
private static Identifier cancelUse(LastUse lu){ // (where `lu` goes out of scope if cancelled)
if(lu.use) return lu.use;
if(lu.constConsume) return cancelUse(lu.constConsume);
foreach(nsc;lu.nestedScopes)
if(auto nlu=nsc.lastUses.get(versionIn(lu.decl,nsc),true))
if(auto r=cancelUse(nlu)) return r;
return null;
}
static LastUse lostDependency(LastUse lu){ // (a last use, also of a version in a branch of a merge, whose dependency was lost through a known consumption, e.g., `x⊕=y` in `t:=x⊕c; if k==1 { u:=dup(t); x⊕=y; }`)
while(lu.forwardTo) lu=lu.forwardTo;
if(lu.dep.isTop&&lu.dep.lostVar) return lu;
foreach(nsc;lu.nestedScopes)
if(auto nlu=nsc.lastUses.get(versionIn(lu.decl,nsc),true))
if(auto r=lostDependency(nlu)) return r;
return null;
}
private bool forgottenOutside(){ // (by the same forget in an enclosing scope)
if(!use) return false;
for(auto lu=this;;){
while(lu.forwardTo) lu=lu.forwardTo;
if(!lu.splitFrom) return false;
lu=lu.splitFrom;
if(lu.isConsumption()&&lu.use is use) return true;
}
}
private bool isBranchSplit(){
return decl.splitFrom&&decl.splitFrom.splitInto.length>1&&decl.splitFrom.splitInto.canFind(decl);
}
private void updateDependenciesOnConsumption(){
//imported!"util.io".writeln("UPDATING ALL: ",this);
if(auto parent=decl.splitFrom){
assert(parent.splitInto.canFind(decl));
foreach(split;parent.splitInto){
if(split !is decl&&interleavable(split.scope_)&&dep.isTop) continue; // (the other branches keep the version from before the split, see `checkBranchOrder`)
if(auto lu=split.scope_.lastUses.get(split,true)){
if(auto source=lu.splitSource)
lu=source;
lu.updateDependenciesOnConsumptionLocal(true,parent.splitInto.length>1,this); // (including forgets in nested scopes of the branches)
}
}
}else updateDependenciesOnConsumptionLocal();
}
private static bool isClassicalDecl(Declaration d){ // (it depends on quantum values only through control)
import ast.semantic_:typeForDecl;
auto ty=typeForDecl(d);
return ty&&ty.isClassical();
}
private bool definedInScope(Declaration d){
import std.algorithm:all;
return d.derivedSequence.all!(x=>x.scope_&&x.scope_.isNestedIn(scope_));
}
private bool readSplitAtDefinition(Declaration d,ref Dependency forgetDep){
return readsSplitAtDefinition(scope_,decl,d,forgetDep);
}
private static bool readsSplitAtDefinition(Scope scope_,Declaration decl,Declaration d,ref Dependency forgetDep){ // (whether the definition of `d` in `scope_` reads a version of `decl` in it, and depends on it only that way)
static bool version_(Declaration x,Declaration y){ return x is y||x.isSplitFrom(y)||y.isSplitFrom(x); }
bool inScope(Declaration x){ return x.scope_&&x.scope_.isNestedIn(scope_); }
SetX!Declaration visited;
bool reads=false;
bool present(Declaration x){ // (not consumed before the forget)
if(forgetDep.isTop) return false;
foreach(y;forgetDep.dependencies) if(version_(y,x)) return true;
return false;
}
bool isOuter(Declaration x){ // (a value from before the split, possibly split further)
for(;x;x=x.splitFrom) if(!inScope(x)) return true;
return false;
}
static Declaration current(Declaration x){ // (a merged version from an earlier fixed-point iteration of a loop stands for the one its splits were moved to, see `fixLoopSplitMergeGraph`)
while(x.splitInto.length&&x.splitInto[0].splitFrom&&x.splitInto[0].splitFrom !is x) x=x.splitInto[0].splitFrom;
return x;
}
bool ok(Declaration x){ // (read by the definitions)
x=current(x);
if(isClassicalDecl(x)) return true;
if(version_(x,decl)){
reads=true;
return true;
}
if(present(x)) return true; // (read at the forget, its definition is not undone)
if(isOuter(x)){ // (only if it was consumed with a known dependency, which has to be read instead, e.g., `f` for `t:=dup(x)&f; forget(f); forget(t);` in a branch)
if(x in visited) return true;
visited.insert(x);
auto lu=x.scope_?x.scope_.lastUses.get(x,true):null;
while(lu&&lu.forwardTo&&!lu.kind.among(Kind.consumption,Kind.synthesizedForget)) lu=lu.forwardTo; // (e.g., the last use before a conditional, consumed in a branch)
if(!lu||!lu.isConsumption()||lu.dep.isTop) return false;
foreach(y;lu.dep.dependencies) if(version_(y,decl)||!ok(y)) return false;
return true;
}
if(x in visited) return true;
visited.insert(x);
if(x.splitFrom) return ok(x.splitFrom);
auto s=x.definitionSources;
if(!s.known) return false;
foreach(r;s.reads) if(!ok(r)) return false;
foreach(c;s.consumes){ // (re-created by undoing the definition)
if(isClassicalDecl(c[0])) continue;
if(version_(current(c[0]),decl)) return false;
if(isOuter(current(c[0]))){ // (its dependency has to be read instead)
if(c[1].isTop) return false;
foreach(y;c[1].dependencies) if(version_(y,decl)||!ok(y)) return false;
}else if(!ok(c[0])) return false;
}
return true;
}
return ok(d)&&reads;
}
private void updateDependenciesOnConsumptionLocal(bool descend=true,bool fromBranch=false,LastUse consumption=null){
//imported!"util.io".writeln("UPDATING DEPENDENCIES FROM: ",this);
// TODO: perform necessary updates
auto cdep=dep.dup;
if(!consumption) consumption=this;
if(cdep.isTop&&!cdep.lostVar&&consumption.use){
cdep.lostVar=consumption.decl;
cdep.lostAt=consumption.use;
}
auto start=next;
if(splitFrom){
if(fromBranch){ // (all last uses in the branch, including those merged from nested scopes)
start=scope_.lastUses.lastLastUse;
while(start&&start.prev) start=start.prev;
if(!start) start=this;
}else for(start=this;start&&start.prev&&start.prev.splitFrom;)
start=start.prev; // TODO: this is a hack, would be better to insert lazy splits in dependency order
}
auto branch=cdep.isTop?splitBranch(consumption):null; // (the uncomputations before the consumption keep their dependencies, see `checkBranchOrder`)
bool before(LastUse lu){ return branch&&lu.time<consumption.consumedAt&&lu.scope_.isNestedIn(branch)&&lu.kind!=Kind.consumption&&!(lu.use&&lu.use.implicitDup); } // (not consumptions of `const` variables and implicit `dup`s, whose variables are forgotten later)
SetX!LastUse visited;
static void replaceVersions(ref Dependency dep,Declaration d,Dependency by,Identifier consumedAt=null){
if(dep.isTop) return;
Declaration[] xs;
foreach(x;dep.dependencies) if(x is d||x.isSplitFrom(d)||d.isSplitFrom(x)) xs~=x;
foreach(x;xs) dep.replace(x,by,consumedAt);
}
static bool hasVersion(ref Dependency dep,Declaration d){
if(dep.isTop) return false;
foreach(x;dep.dependencies) if(x is d||x.isSplitFrom(d)||d.isSplitFrom(x)) return true;
return false;
}
MapX!(Id,Q!(Identifier,Identifier,Identifier,Scope,Scope,Declaration)) outOfScope; // (where a variable goes out of scope, the implicit `dup` before that, the consumption that causes it, the scopes of later uses and of going out of scope, and the version that goes out of scope)
static bool onOtherPath(Scope a,Scope b){ // (in different branches of the same conditional)
for(auto x=cast(NestedScope)a;x;x=cast(NestedScope)x.parent)
for(auto y=cast(NestedScope)b;y;y=cast(NestedScope)y.parent)
if(x.parent is y.parent) return x !is y&&x.getSiblingScopes().canFind(y);
return false;
}
void walk(LastUse start,Dependency cdep,bool nested,Dependency atSplit,NestedScope level){ // (`atSplit`: the dependency at the split into `level`, for the forgets there)
auto beforeSplits=cdep.dup; // (before the splits into the next nested scopes)
for(auto lu=start;lu;lu=lu.next){
scope(success){
if(!lu.forwardTo) beforeSplits=cdep.dup; // (not one of the splits, which precede the nested scopes)
if(descend) foreach(nsc;lu.nestedAfter){
if(nsc.lastUses.discarded) continue;
auto head=nsc.lastUses.lastLastUse;
if(!head) continue;
while(head.prev) head=head.prev;
walk(head,cdep.dup,true,beforeSplits.dup,nsc);
}
}
if(lu is this||lu.discarded||before(lu)) continue;
visited.insert(lu);
if(lu.decl&&lu.use&&!lu.use.isSemError()){
auto oos=outOfScope.get(lu.decl.getId,Q!(Identifier,Identifier,Identifier,Scope,Scope,Declaration).init);
if(oos[2]&&lu.scope_.isNestedIn(oos[3])&&!onOtherPath(lu.scope_,oos[4])&&lu.use !is oos[0]&&lu.use !is oos[1]&&(lu.decl is oos[5]||lu.decl.isDerivedFrom(oos[5]))){ // (used after it went out of scope)
import ast.semantic_:undefinedIdentifierError;
lu.use.setSemForceError();
lu.decl.setSemForceError();
undefinedIdentifierError(lu.use,[new OutOfScopeDecl(lu.decl,oos[0],oos[2],oos[1])],lu.scope_);
outOfScope.remove(lu.decl.getId);
}
}
if(lu.forwardTo&&lu.forwardTo.isConsumption()&&lu.forwardTo !is this&&lu.forwardTo !in visited) continue;
if(lu.kind==Kind.synthesizedForget&&lu.forgottenOutside()) continue; // (checked where it is forgotten)
if(lu.isConsumption()&&lu.splitFrom&&lu.splitFrom.isConsumption()&&lu.splitFrom !is this&&lu.splitFrom !in visited&&lu.kind!=Kind.synthesizedForget) continue; // (a forget in the branch itself is checked here)
if(splitFrom&&(fromBranch||!isConsumption())&&lu.decl&&definedInScope(lu.decl)&&(isClassicalDecl(lu.decl)||readSplitAtDefinition(lu.decl,lu.dep))) continue;
if(!nested&&lu.atSplit){ // (its dependency is the one at the split, which comes before the consumption)
cdep.replace(lu.decl,lu.dep,lu.use);
continue;
}
auto byAtSplit=nested&&lu.scope_ is level&&isBranch(level)?atSplit:cdep; // (for the forgets at the split: `decl` is consumed before the split)
if(nested&&lu.kind==Kind.lazySplit) replaceVersions(lu.depAtSplit,decl,byAtSplit);
//imported!"util.io".writeln("VISITING: ",lu," ",decl," ",cdep," ",use?text(use.loc):"<?>");
if(nested) replaceVersions(lu.dep,decl,lu.atSplit?byAtSplit:cdep);
else lu.dep.replace(decl,cdep);
//imported!"util.io".writeln("REPLACED: ",lu," ",decl," ",cdep);
if(lu.kind.among(Kind.consumption,Kind.synthesizedForget)||lu.use&&lu.use.implicitDup){
if(lu.kind==Kind.synthesizedForget) lu.scope_.noteDependencyResolved(lu.dep);
if(lu.use) foreach(c;lu.cancelable){ // (a last use that would have been cancelled here if the dependency through `decl` had been known)
auto prev=c[0],id=prev.decl.getId;
if(id==lu.decl.getId||id in outOfScope||!hasVersion(c[1],decl)) continue;
auto ndep=c[1].dup;
replaceVersions(ndep,decl,cdep);
if(!hasVersion(ndep,lu.decl)) continue;
replaceVersions(ndep,lu.decl,lu.dep,lu.use);
if(!ndep.isTop) continue;
auto at=cancelUse(prev);
auto dupUse=prev.prevImplicitDup?prev.prevImplicitDup.use:null;
outOfScope[id]=q(at?at:lu.use,dupUse,lu.use,lu.scope_,prev.scope_,prev.decl);
}
if(nested) replaceVersions(cdep,lu.decl,lu.dep,lu.use);
else cdep.replace(lu.decl,lu.dep,lu.use);
if(!lu.decl.isSemError()&&lu.isConsumption()){
//imported!"util.io".writeln("CHECKING: ",lu," ",lu.use?text(lu.use.loc):"<?>"," ",lu.constBlock);
lu.checkConsumable();
}
if(!lu.decl.isSemError()&&lu.dep.isTop){
if(lu.kind==Kind.synthesizedForget){
lu.reportUnforgettable();
}else if(!lu.decl.isSemError()&&lu.use&&lu.use.implicitDup){
//imported!"util.io".writeln("FOUND CANCELABLE DUP AT: ",lu.use.loc);
if(auto vd=cast(VarDecl)lu.decl) if(!vd.outOfScopeCause) vd.outOfScopeCause=lu.dep.lostAt;
if(lu.dep.lostAt&&lu.decl.getId !in outOfScope) outOfScope[lu.decl.getId]=q(lu.use,Identifier.init,lu.dep.lostAt,scope_,lu.scope_,lu.decl); // (the `dup` would not have been implicit if the dependency through `decl` had been known)
lu.cancelImplicitDup();
}
}
}
}
}
walk(splitFrom?start:this,cdep,false,cdep,null);
}
private void reportUnforgettable(){ // (a forget whose dependency is unknown)
if(use&&use.scope_&&cast(ForgetExp)parent){
scope_.error(format("cannot synthesize forget expression for `%s`",use),use.loc);
scope_.explainUnknownDependency(decl,dep);
}else if(use && use.isSemError()){
// already reported
}else if(decl.scope_){
auto d=decl,sc=decl.scope_;
if(cast(Parameter)d) sc.error(format("%s `%s` is not consumed (perhaps return it or annotate it `const`)",d.kind,d.getName),d.loc);
else sc.error(format("%s `%s` is not consumed (perhaps return it)",d.kind,d.getName),d.loc);
if(cast(ReturnExp)parent) sc.note("at function return",parent.loc);
else if(use) sc.note("cannot be forgotten after its last use here",use.loc);
sc.explainUnknownDependency(d,dep);
}else if(use && use.scope_){
scope_.error(format("cannot synthesize forget expression for `%s`",use),use.loc);
}
decl.setSemForceError();
if(use) use.setSemForceError();
if(parent) parent.setSemForceError();
// TODO: the error has to be propagated out further
}
bool checkConsumable(){
//imported!"util.io".writeln(constBlock.map!(id=>id.loc)," ",use?text(use.loc):"<?>");
auto nconstBlock=constBlock.length?constBlock[$-1]:null;
if(!nconstBlock){
if(auto nsc=cast(NestedScope)scope_){
nconstBlock=nsc.parent.isConst(decl); // TODO: should not be needed
//imported!"util.io".writeln("???? ",decl," ",use?text(use.loc):"<?>",nconstBlock is decl," ",scope_ is decl.scope_," ",nsc.parent is decl.scope_," ",this);
}
}
if(nconstBlock) scope_.noteDependencyResolved(dep);
static if(language==silq)
if(nconstBlock&&consumedComponent&&scope_.componentConstBlocksAllow(decl,consumedComponent,use.loc))
nconstBlock=null;
if(nconstBlock&&dep.isTop){
if(!use){
scope_.error(format("variable `%s` is not consumed",decl.getName),decl.loc);
// TODO: needs a note indicating the end of scope
}else if(nconstBlock !is use){
scope_.error(format("cannot consume `const` %s `%s`",decl.kind,use), use.loc);
scope_.note(format("%s was made `const` here", decl.kind), nconstBlock.loc);
}else{
import ast.semantic_:nonLiftedError;
nonLiftedError(use,scope_); // TODO: would be better to locate this around the enclosing `const` expression
}
decl.setSemForceError();
if(use) use.setSemForceError();
return false;
}
return true;
}
bool consume(bool isForget){
assert(!forwardTo);
if(kind==Kind.synthesizedForget) return false;
if(kind==Kind.consumption) return false;
bool ok=checkConsumable();
if(kind==Kind.lazySplit){
auto lu=getSplitFrom();
assert(!!lu);
}
/+imported!"util.io".writeln("CONSUMING: ",this);
scope(exit) imported!"util.io".writeln("CONSUMED: ",this);+/
assert(!use||use.meaning is decl);
auto csc=cast(NestedScope)scope_;
assert(csc&&csc.isNestedIn(decl.scope_));
for(;csc;csc=cast(NestedScope)csc.parent){
auto cdecl=csc.getSplit(decl);
if(auto d=csc.rnsymtab.get(decl.getId,null)){
if(d !is cdecl){
csc.symtabRemove(d);
csc.reinsert(cdecl);
csc.replaceDecl(d,cdecl,!(d.scope_&&d.scope_ !is csc&&d.scope_.isNestedIn(csc))); // (while merging, the table holds a branch's version, which stays in the branch)
}
}else csc.reinsert(cdecl);
if(csc is decl.scope_) break;
}
Declaration result=scope_.getSplit(decl);
Identifier[] oldConstBlock;
if(constBlock.length){
if(auto prop=scope_.updateDeclProps(decl)){
oldConstBlock=prop.constBlock;
prop.constBlock=constBlock;
}
}
result=scope_.consume(result,use);
if(!use&&explanationUse) scope_.recordConsumption(result,explanationUse);
if(constBlock.length) scope_.updateDeclProps(decl).constBlock=oldConstBlock;
assert(!!result);
scope_.unsplit(result);
assert(scope_.getSplit(decl) is result);
if(use&&use.meaning is decl) use.meaning=result; // TODO: this is a bit hacky
assert(!use||use.meaning is result);
void removeCopies(Scope sc){
foreach(nested;sc.activeNestedScopes){ // TODO: need to consider nested scopes at correct time
if(nested.forgottenVarsOnEntry.canFind(result)){
nested.forgottenVarsOnEntry=nested.forgottenVarsOnEntry.filter!(d=>d!is result).array; // TODO: make more efficient
}
auto copy=nested.rnsymtab.get(result.getId,null);
if(copy&© !is result&&!result.splitSequence.canFind(copy)&&!copy.isSplitFrom(result)) copy=null;
if(copy){
nested.symtabRemove(copy);
if(copy is result||nested.dependencyTracked(copy))
nested.pushDependencies(copy,false);
if(use&&!use.constLookup&&!use.implicitDup)
nested.recordConsumption(copy,use);
if(auto lu=nested.lastUses.get(copy,true))
lu.markConsumed(use,isForget&&!lu.dep.isTop,false,null,true);
}
if(nested.forgottenVars.canFind(result)){
nested.forgottenVars=nested.forgottenVars.filter!(d=>d!is result).array; // TODO: make more efficient
}
nested.checkDeclarationCancel(copy?copy:result,use);
removeCopies(nested);
}
}
removeCopies(scope_);
for(auto inner=cast(NestedScope)scope_;inner&&inner !is decl.scope_;inner=cast(NestedScope)inner.parent)
if(!inner.parent.activeNestedScopes.canFind(inner)) removeCopies(inner.parent); // (nested scopes after `inner` that are still being analyzed)
markConsumed(use,isForget);
return ok;
}
static enum Forgettability{
none,
forgettable,
consumable,
}
bool canForget(bool forceConsumed,ReturnExp earlyReturn=null,bool enclosing=false){
return getForgettability(forceConsumed,earlyReturn,enclosing)>=(forceConsumed?Forgettability.consumable:Forgettability.forgettable);
}
bool canForgetBeforeLoops(bool forceConsumed,ReturnExp earlyReturn){ // (a forget on entry of a loop body has to be moved before the loop)
for(auto lu=this;lu&&lu.kind==Kind.lazySplit;lu=lu.splitFrom)
if(auto bs=cast(BlockScope)lu.scope_) if(bs.isLoopBody)
if(lu.splitFrom.getForgettability(forceConsumed,earlyReturn)==Forgettability.none) return false;
return true;
}
bool atLoopEntry(){
auto bs=cast(BlockScope)scope_;
return bs&&bs.isLoopEntry;
}
bool forgettableAtSplit(ReturnExp earlyReturn,bool enclosing){ // (a version in a branch, e.g., at an early return after the branch consumed what it depends on, rather than before the split, as it may be used after the conditional; not at an early return in a nested scope (`enclosing`), after which the branch may continue on another path, e.g., `u` for `if c { if d { return x; } forget(u); }`)
return kind==Kind.lazySplit&&isBranch(scope_)&&!depAtSplit.isTop&&!(earlyReturn&&enclosing);
}
bool isLastSplitSibling(){
auto nsc=cast(NestedScope)scope_;
assert(!!nsc);
auto siblings=nsc.getSiblingScopes();
foreach(ssc;siblings){
if(ssc is nsc) continue;
auto ndecl=decl;
if(decl.scope_ is scope_ && decl.splitFrom && decl.splitFrom.scope_ is nsc.parent)
ndecl=decl.splitFrom;
ndecl=ssc.getSplit(ndecl);
//imported!"util.io".writeln("CHECKING SIBLINGS: ",ssc.forgottenVarsOnEntry," ",decl," ",ndecl," ",ssc.forgottenVarsOnEntry.canFind(ndecl)," ",ssc.forgottenVarsOnEntry.canFind(decl)," ",decl.scope_ is this," ",decl.splitFrom," ",decl.splitFrom&&decl.splitFrom.scope_ is nsc.parent);
if(ssc.forgottenVarsOnEntry.canFind(ndecl)) continue;
if(auto bs=cast(BlockScope)ssc) if(bs.isLoopBody&&bs.diverges){ // (a loop body that diverges without using the variable, which `closeUnreachable` only drops)
auto slu=ssc.lastUses.lastUses.get(ndecl,null);
if(slu&&slu.kind==Kind.lazySplit&&!slu.forwardTo&&!slu.use) continue;
}
return false;
}
return true;
}
Forgettability getForgettability(bool forceConsumed,ReturnExp earlyReturn=null,bool enclosing=false){
import ast.semantic_:typeConstBlocked;
if(typeConstBlocked(decl,scope_)) return Forgettability.none;
if(forwardTo) return forwardTo.getForgettability(forceConsumed,earlyReturn,enclosing);
//imported!"util.io".writeln("GETTING FORGETTABILITY: ",this);
if(kind.among(Kind.consumption,Kind.capture)) return Forgettability.none; // (their uses can be in nested scopes, e.g., the consumption of the version in a loop body)
if(use&&!scope_.canSplit(use.meaning)) return Forgettability.none;
final switch(kind)with(Kind){
case definition:
goto case constUse;
case constPinned:
return Forgettability.none;
case lazySplitSink:
return Forgettability.none;
case lazySplit:
assert(!!splitFrom);
auto r=Forgettability.none;
if(forceConsumed||isLastSplitSibling()||earlyReturn&&dep.isTop&&!forgettableAtSplit(earlyReturn,enclosing)||atLoopEntry())
r=max(r,splitFrom.getForgettability(forceConsumed,earlyReturn,!!earlyReturn));
if(r==Forgettability.none&&(!dep.isTop||forgettableAtSplit(earlyReturn,enclosing))&&scope_.canSplit(decl)&&canForgetBeforeLoops(forceConsumed,earlyReturn))
r=Forgettability.forgettable; // (on entry, also at the split if the branch consumes what it depends on)
//imported!"util.io".writeln("RESULT: ",r);
return r;
case lazyMerge:
auto r=getMergeForgettability(decl,nestedScopes,true,forceConsumed);
foreach(nsc;nestedScopes){
foreach(ssc;nsc.siblingScopes){
if(auto bs=cast(BlockScope)ssc){
if(bs.isLoopBody&&bs.diverges){ // (forgets in the scope of the path that skips such a loop are not generated: the variable can only be forgotten before the loop)
auto nlu=nsc.lastUses.lastUses.get(versionIn(decl,nsc),null);
if(!nlu||nlu.forwardTo||nlu.kind!=Kind.lazySplit||!nlu.isLastSplitSibling()||!nlu.splitFrom.canForget(false))
return Forgettability.none;
}
}
}
}
return r;
case synthesizedForget:
return Forgettability.none; // TODO
case implicitDup:
return Forgettability.consumable;
case constUse:
if(dep.isTop) return Forgettability.none;
if(constConsume&&constConsume.canForget(true))
return Forgettability.consumable;
if(canAddForget(parent)){
import ast.semantic_:typeForDecl;
if(auto ft=cast(ProductTy)typeForDecl(decl)){
if(ft.captureAnnotation==CaptureAnnotation.spent){
return Forgettability.consumable;
}
}
return Forgettability.forgettable;
}
return Forgettability.none;
case consumption:
return Forgettability.none;
case capture:
return Forgettability.none; // TODO
}
}
bool canRedefine(){
if(forwardTo) return forwardTo.canRedefine();
final switch(kind)with(Kind){
case definition,constPinned:
return false;
case lazySplitSink:
return false;
case lazySplit:
return canForget(true);
case lazyMerge:
return canForget(true);
case implicitDup:
return canForget(true);
case constUse:
return canForget(true);
case synthesizedForget,consumption:
return false; // (not handled by `forget`)
case capture:
return false; // TODO?
}
}
bool betterUnforgettableError(Scope sc){
if(forwardTo) return forwardTo.betterUnforgettableError(sc);
final switch(kind)with(Kind){
case definition,constPinned:
return false;
case lazySplitSink:
return false;
case lazySplit:
return false; // TODO
case lazyMerge:
return false; // TODO
case implicitDup:
return false; // TODO
case constUse:
return false;
case synthesizedForget,consumption:
assert(0,text(this," ",sc.getFunction()));
case capture:
return false;
}
}
void forget(bool forceConsumed,ReturnExp earlyReturn=null,bool enclosing=false){
bool isForget=false;
if(forwardTo){
forwardTo.forget(forceConsumed,earlyReturn,enclosing);
while(forwardTo.forwardTo) forwardTo=forwardTo.forwardTo;
assert(isConsumption(),text(this," ",forwardTo," ",forwardTo.splitFrom," ",isConsumption()));
if(scope_.lastUses.lastUses.get(decl,null) is this)
scope_.lastUses.lastUses[decl]=forwardTo;
return;
}
//imported!"util.io".writeln("FORGETTING: ",this," ",canForget(forceConsumed)," ",use?text(use.loc):"?");
//imported!"util.io".writeln("FORGETTING: ",use);
final switch(kind)with(Kind){
case definition:
goto case constUse;
case constPinned:
assert(0);
case lazySplitSink:
assert(0); // always forwarded
case lazySplit:
assert(!!splitFrom);
if(forceConsumed||isLastSplitSibling()||earlyReturn&&dep.isTop&&!forgettableAtSplit(earlyReturn,enclosing)||atLoopEntry()){
if(splitFrom.canForget(forceConsumed,earlyReturn,!!earlyReturn)){
if(earlyReturn&&dep.isTop) // (forgotten before the split because of the early return)
splitFrom.decl.forgottenBeforeEarlyReturn=earlyReturn;
splitFrom.forget(forceConsumed,earlyReturn,!!earlyReturn);
if(scope_.rnsymtab.get(decl.getId,null) is decl){
scope_.symtabRemove(decl);
scope_.pushDependencies(decl,false);
}
markConsumed(use,false);
if(forceConsumed||earlyReturn) removeFromUnusedSiblings(); // (forgotten before the split)
return;
}
}
if(dep.isTop&&forgettableAtSplit(earlyReturn,enclosing)) dep=depAtSplit.dup; // (forgotten at the split, before the branch consumes what it depends on)
assert(!dep.isTop);
assert(!forceConsumed);
//imported!"util.io".writeln("FORGETTING ON ENTRY: ",decl," ",splitFrom," ",splitFrom.canForget(forceConsumed));
scope_.forgetOnEntry(decl);
if(isBranch(scope_)){
atSplit=true;
(cast(NestedScope)scope_).forgottenAtSplit~=q(decl,this);
}
isForget=true;
break;
case lazyMerge:
useFromMergedScopes();
consume(false);
foreach(i,nsc;nestedScopes){
auto cdecl=versionIn(decl,nestedScopes[i]);
//imported!"util.io".writeln("MERGED FROM: ",nsc.lastUses.lastUses.get(cdecl,null)," ",decl.mergedFrom," ",nsc.lastUses.lastUses," ",nsc.lastUses.lastUses.get(decl,null));
assert(nsc.lastUses.canForget(cdecl,false,false));
auto declBefore=decl;
if(!use){
if(auto lu=nsc.lastUses.get(cdecl,true))
use=lu.use;
}
nsc.lastUses.forget(cdecl,false);
//imported!"util.io".writeln("AFTER FORGET: ",declBefore," ",decl," ",declBefore is decl," ",decl.splitInto);
if(nsc.mergedVars.any!(d=>d.mergedInto is decl))
nsc.mergedVars=nsc.mergedVars.filter!(d=>d.mergedInto !is decl).array; // TODO: make more efficient
}
//imported!"util.io".writeln("AFTER LMERGE: ",scope_.rnsymtab);
return;
case synthesizedForget:
assert(0);
case implicitDup:
assert(use.implicitDup);
assert(!use.constLookup);
cancelImplicitDup();
//assert(!scope_.isConst(decl));
return;
case constUse:
assert(!dep.isTop);
if(constConsume&&constConsume.canForget(true)){
constConsume.forget(true);
markConsumed(use,false);
return;
}
assert(canAddForget(parent));
use=addForget(parent,decl,scope_);
assert(!!use);
scope_.updateDeclProps(decl).accesses~=use; // TODO: this is out of order, ok?
isForget=true;
break;
case consumption:
assert(0);
case capture:
assert(0); // TODO
break;
}
consume(isForget);
}
bool canCancelImplicitDup(){
if(forwardTo) return forwardTo.canCancelImplicitDup();
if(splitFrom) return false;
final switch(kind)with(Kind){
case definition,lazySplitSink,lazySplit:
return false;
case lazyMerge:
return canCancelImplicitDupMerge(decl,nestedScopes);
case synthesizedForget:
return false;
case implicitDup,constPinned:
return use&&use.implicitDup;
case constUse:
if(constConsume&&constConsume.canCancelImplicitDup())
return true;
if(prevImplicitDup||isConstLocal(decl))
return canForget(false);
return false;
case consumption:
return false;
case capture:
return false; // TODO
}
return true;
}
Identifier explanationUse;
private void useFromMergedScopes(){
if(use||explanationUse) return;
foreach(nsc;nestedScopes){
if(auto lu=nsc.lastUses.get(versionIn(decl,nsc),true)){
if(lu.use){
explanationUse=lu.use;
return;
}
}
}
}
bool cancelImplicitDup(){
//imported!"util.io".writeln("CANCELING: ",use.loc," ",dep);
if(forwardTo) return forwardTo.cancelImplicitDup();
if(splitFrom) return false;
final switch(kind)with(Kind){
case definition,lazySplitSink,lazySplit:
return assert(0);
case lazyMerge:
bool ok=true;
useFromMergedScopes();
ok&=consume(false);
foreach(i,nsc;nestedScopes){
auto cdecl=versionIn(decl,nestedScopes[i]);
//imported!"util.io".writeln("MERGED FROM: ",nsc.lastUses.lastUses.get(cdecl,null)," ",decl.mergedFrom," ",nsc.lastUses.lastUses," ",nsc.lastUses.lastUses.get(decl,null));
if(!use){
if(auto lu=nsc.lastUses.get(cdecl,true))
use=lu.use;
}
if(nsc.lastUses.canCancelImplicitDup(cdecl)){
nsc.lastUses.cancelImplicitDup(cdecl);
if(nsc.mergedVars.any!(d=>d.mergedInto is decl))
nsc.mergedVars=nsc.mergedVars.filter!(d=>d.mergedInto !is decl).array; // TODO: make more efficient
}else if(nsc.lastUses.canForget(cdecl,false,false)){
nsc.lastUses.forget(cdecl,false);