forked from WebAssembly/binaryen
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpossible-contents.cpp
More file actions
3395 lines (3029 loc) · 125 KB
/
possible-contents.cpp
File metadata and controls
3395 lines (3029 loc) · 125 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
/*
* Copyright 2022 WebAssembly Community Group participants
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <optional>
#include <variant>
#include "analysis/cfg.h"
#include "ir/bits.h"
#include "ir/branch-utils.h"
#include "ir/eh-utils.h"
#include "ir/gc-type-utils.h"
#include "ir/linear-execution.h"
#include "ir/local-graph.h"
#include "ir/module-utils.h"
#include "ir/possible-contents.h"
#include "support/insert_ordered.h"
#ifndef POSSIBLE_CONTENTS_DEBUG
#define POSSIBLE_CONTENTS_DEBUG 0
#endif
#if POSSIBLE_CONTENTS_DEBUG
#include "support/timing.h"
#endif
#include "wasm-type.h"
#include "wasm.h"
namespace std {
std::ostream& operator<<(std::ostream& stream,
const wasm::PossibleContents& contents) {
contents.dump(stream);
return stream;
}
} // namespace std
namespace wasm {
PossibleContents PossibleContents::combine(const PossibleContents& a,
const PossibleContents& b) {
auto aType = a.getType();
auto bType = b.getType();
// First handle the trivial cases of them being equal, or one of them is
// None or Many.
if (a == b) {
return a;
}
if (b.isNone()) {
return a;
}
if (a.isNone()) {
return b;
}
if (a.isMany()) {
return a;
}
if (b.isMany()) {
return b;
}
if (!aType.isRef() || !bType.isRef()) {
// At least one is not a reference. The only possibility left for a useful
// combination here is if they have the same type (since we've already ruled
// out the case of them being equal). If they have the same type then
// neither is a reference and we can emit an exact type (since subtyping is
// not relevant for non-references).
if (aType == bType) {
return ExactType(aType);
} else {
return Many();
}
}
// Special handling for references from here.
if (a.isNull() && b.isNull()) {
// These must be nulls in different hierarchies, otherwise a would have
// been handled by the `a == b` case above.
assert(aType != bType);
return Many();
}
auto lub = Type::getLeastUpperBound(aType, bType);
if (lub == Type::none) {
// The types are not in the same hierarchy.
return Many();
}
// From here we can assume there is a useful LUB.
// Nulls can be combined in by just adding nullability to a type.
if (a.isNull() || b.isNull()) {
// Only one of them can be null here, since we already handled the case
// where they were both null.
assert(!a.isNull() || !b.isNull());
// If only one is a null then we can use the type info from the b, and
// just add in nullability. For example, a literal of type T and a null
// becomes an exact type of T that allows nulls, and so forth.
auto mixInNull = [](ConeType cone) {
cone.type = cone.type.with(Nullable);
return cone;
};
if (!a.isNull()) {
return mixInNull(a.getCone());
} else if (!b.isNull()) {
return mixInNull(b.getCone());
}
}
// Find a ConeType that describes both inputs, using the shared ancestor which
// is the LUB. We need to find how big a cone we need: the cone must be big
// enough to contain both the inputs.
auto aDepth = a.getCone().depth;
auto bDepth = b.getCone().depth;
Index newDepth;
if (aDepth == FullDepth || bDepth == FullDepth) {
// At least one has full (infinite) depth, so we know the new depth must
// be the same.
newDepth = FullDepth;
} else {
// The depth we need under the lub is how far from the lub we are, plus
// the depth of our cone.
// TODO: we could make a single loop that also does the LUB, at the same
// time, and also avoids calling getDepth() which loops once more?
auto aDepthFromRoot = aType.getHeapType().getDepth();
auto bDepthFromRoot = bType.getHeapType().getDepth();
auto lubDepthFromRoot = lub.getHeapType().getDepth();
assert(lubDepthFromRoot <= aDepthFromRoot);
assert(lubDepthFromRoot <= bDepthFromRoot);
Index aDepthUnderLub = aDepthFromRoot - lubDepthFromRoot + aDepth;
Index bDepthUnderLub = bDepthFromRoot - lubDepthFromRoot + bDepth;
// The total cone must be big enough to contain all the above.
newDepth = std::max(aDepthUnderLub, bDepthUnderLub);
}
return ConeType{lub, newDepth};
}
void PossibleContents::intersect(const PossibleContents& other) {
// This does not yet handle all possible content.
assert((other.isConeType() &&
(other.getType().isExact() || other.hasFullCone())) ||
other.isLiteral() || other.isNone());
if (*this == other) {
// Nothing changes.
return;
}
if (!haveIntersection(*this, other)) {
// There is no intersection at all.
// Note that this code path handles |this| or |other| being None.
value = None();
return;
}
if (isSubContents(other, *this)) {
// The intersection is just |other|.
// Note that this code path handles |this| being Many.
value = other.value;
return;
}
if (isSubContents(*this, other)) {
// The intersection is just |this|.
return;
}
if (isLiteral() || other.isLiteral()) {
// We've ruled out either being a subcontents of the other. A literal has
// no other intersection possibility.
value = None();
return;
}
auto type = getType();
auto otherType = other.getType();
auto heapType = type.getHeapType();
auto otherHeapType = otherType.getHeapType();
// Intersect the types.
auto newType = Type::getGreatestLowerBound(type, otherType);
auto setNoneOrNull = [&]() {
if (newType.isNullable()) {
value = Literal::makeNull(heapType);
} else {
value = None();
}
};
if (newType == Type::unreachable || newType.isNull()) {
setNoneOrNull();
return;
}
// The heap types are compatible, so intersect the cones.
auto depthFromRoot = heapType.getDepth();
auto otherDepthFromRoot = otherHeapType.getDepth();
auto newDepthFromRoot = newType.getHeapType().getDepth();
// Note the global's information, if we started as a global. In that case, the
// code below will refine our type but we can remain a global, which we will
// accomplish by restoring our global status at the end.
std::optional<GlobalInfo> global;
if (isGlobal()) {
global = getGlobal();
}
if (hasFullCone() && other.hasFullCone()) {
// Both are full cones, so the result is as well.
value = DefaultConeType(newType);
} else {
// The result is a partial cone. Check whether the cones overlap, and if
// they do, find the new depth.
if (newDepthFromRoot - depthFromRoot > getCone().depth ||
newDepthFromRoot - otherDepthFromRoot > other.getCone().depth) {
setNoneOrNull();
return;
}
Index newDepth = getCone().depth - (newDepthFromRoot - depthFromRoot);
Index otherNewDepth =
other.getCone().depth - (newDepthFromRoot - otherDepthFromRoot);
value = ConeType{newType, std::min(newDepth, otherNewDepth)};
}
if (global) {
// Restore the global but keep the new and refined type.
value = GlobalInfo{global->name, global->kind, getType()};
}
}
bool PossibleContents::haveIntersection(const PossibleContents& a,
const PossibleContents& b) {
if (a.isNone() || b.isNone()) {
// One is the empty set, so nothing can intersect here.
return false;
}
if (a.isMany() || b.isMany()) {
// One is the set of all things, so definitely something can intersect since
// we've ruled out an empty set for both.
return true;
}
if (a == b) {
// The intersection is equal to them.
return true;
}
auto aType = a.getType();
auto bType = b.getType();
if (!aType.isRef() || !bType.isRef()) {
// At least one is not a reference. The only way they can intersect is if
// the type is identical, and they are not both literals (we've already
// ruled out them being identical earlier).
return aType == bType && (!a.isLiteral() || !b.isLiteral());
}
// From here on we focus on references.
auto aHeapType = aType.getHeapType();
auto bHeapType = bType.getHeapType();
if (aType.isNullable() && bType.isNullable() &&
aHeapType.getBottom() == bHeapType.getBottom()) {
// A compatible null is possible on both sides.
return true;
}
// We ruled out having a compatible null on both sides. If one is simply a
// null then no chance for an intersection remains.
if (a.isNull() || b.isNull()) {
return false;
}
auto aSubB = HeapType::isSubType(aHeapType, bHeapType);
auto bSubA = HeapType::isSubType(bHeapType, aHeapType);
if (!aSubB && !bSubA) {
// No type can appear in both a and b, so the types differ, so the values
// do not overlap.
return false;
}
// From here on we focus on references and can ignore the case of null - any
// intersection must be of a non-null value, so we can focus on the heap
// types.
auto aDepthFromRoot = aHeapType.getDepth();
auto bDepthFromRoot = bHeapType.getDepth();
if (aSubB) {
// A is a subtype of B. For there to be an intersection we need their cones
// to intersect, that is, to rule out the case where the cone from B is not
// deep enough to reach A.
assert(aDepthFromRoot >= bDepthFromRoot);
return aDepthFromRoot - bDepthFromRoot <= b.getCone().depth;
} else if (bSubA) {
assert(bDepthFromRoot >= aDepthFromRoot);
return bDepthFromRoot - aDepthFromRoot <= a.getCone().depth;
} else {
WASM_UNREACHABLE("we ruled out no subtyping before");
}
// TODO: we can also optimize things like different Literals, but existing
// passes do such things already so it is low priority.
}
bool PossibleContents::isSubContents(const PossibleContents& a,
const PossibleContents& b) {
if (a == b) {
return true;
}
if (a.isNone()) {
return true;
}
if (b.isNone()) {
return false;
}
if (a.isMany()) {
return false;
}
if (b.isMany()) {
return true;
}
if (a.isLiteral()) {
// Note we already checked for |a == b| above. We need b to be a set that
// contains the literal a.
return !b.isLiteral() && Type::isSubType(a.getType(), b.getType());
}
if (b.isLiteral()) {
return false;
}
if (b.isFullConeType()) {
if (a.isNull()) {
return b.getType().isNullable();
}
return Type::isSubType(a.getType(), b.getType());
}
if (a.isFullConeType()) {
// We've already ruled out b being a full cone type before.
return false;
}
if (b.isGlobal()) {
// We've already ruled out anything but another global or non-full cone type
// for a.
return false;
}
assert(b.isConeType() && (a.isConeType() || a.isGlobal()));
if (!Type::isSubType(a.getType(), b.getType())) {
return false;
}
// Check that a's cone type is enclosed in b's cone type.
return a.getType().getHeapType().getDepth() + a.getCone().depth <=
b.getType().getHeapType().getDepth() + b.getCone().depth;
}
namespace {
// We are going to do a very large flow operation, potentially, as we create
// a Location for every interesting part in the entire wasm, and some of those
// places will have lots of links (like a struct field may link out to every
// single struct.get of that type), so we must make the data structures here
// as efficient as possible. Towards that goal, we work with location
// *indexes* where possible, which are small (32 bits) and do not require any
// complex hashing when we use them in sets or maps.
//
// Note that we do not use indexes everywhere, since the initial analysis is
// done in parallel, and we do not have a fixed indexing of locations yet. When
// we merge the parallel data we create that indexing, and use indexes from then
// on.
using LocationIndex = uint32_t;
#ifndef NDEBUG
// Assert on not having duplicates in a vector.
template<typename T> void disallowDuplicates(const T& targets) {
#if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2
std::unordered_set<LocationIndex> uniqueTargets;
for (const auto& target : targets) {
uniqueTargets.insert(target);
}
assert(uniqueTargets.size() == targets.size());
#endif
}
#endif
// A link indicates a flow of content from one location to another. For
// example, if we do a local.get and return that value from a function, then
// we have a link from the ExpressionLocation of that local.get to a
// ResultLocation.
template<typename T> struct Link {
T from;
T to;
bool operator==(const Link<T>& other) const {
return from == other.from && to == other.to;
}
};
using LocationLink = Link<Location>;
using IndexLink = Link<LocationIndex>;
} // anonymous namespace
} // namespace wasm
namespace std {
template<> struct hash<wasm::LocationLink> {
size_t operator()(const wasm::LocationLink& loc) const {
return std::hash<std::pair<wasm::Location, wasm::Location>>{}(
{loc.from, loc.to});
}
};
template<> struct hash<wasm::IndexLink> {
size_t operator()(const wasm::IndexLink& loc) const {
return std::hash<std::pair<wasm::LocationIndex, wasm::LocationIndex>>{}(
{loc.from, loc.to});
}
};
} // namespace std
namespace wasm {
namespace {
// Information that is shared with InfoCollector.
struct SharedInfo {
// Subtyping info.
const SubTypes& subTypes;
// The names of tables that are imported or exported.
std::unordered_set<Name> publicTables;
SharedInfo(const SubTypes& subTypes) : subTypes(subTypes) {}
};
// The data we gather from each function, as we process them in parallel. Later
// this will be merged into a single big graph.
struct CollectedFuncInfo {
// All the links we found in this function. Rarely are there duplicates
// in this list (say when writing to the same global location from another
// global location), and we do not try to deduplicate here, just store them in
// a plain array for now, which is faster (later, when we merge all the info
// from the functions, we need to deduplicate anyhow).
std::vector<LocationLink> links;
// All the roots of the graph, that is, places that begin by containing some
// particular content. That includes i32.const, ref.func, struct.new, etc. All
// possible contents in the rest of the graph flow from such places.
//
// The vector here is of the location of the root and then its contents.
std::vector<std::pair<Location, PossibleContents>> roots;
// In some cases we need to know the parent of the expression. Consider this:
//
// (struct.set $A k
// (local.get $ref)
// (local.get $value)
// )
//
// Imagine that the first local.get, for $ref, receives a new value. That can
// affect where the struct.set sends values: if previously that local.get had
// no possible contents, and now it does, then we have DataLocations to
// update. Likewise, when the second local.get is updated we must do the same,
// but again which DataLocations we update depends on the ref passed to the
// struct.set. To handle such things, we set add a childParent link, and then
// when we update the child we can find the parent and handle any special
// behavior we need there.
std::unordered_map<Expression*, Expression*> childParents;
// All functions that might be called from the outside. Any RefFunc suggests
// that, in open world. (We could be more precise and use our flow analysis to
// see which, in fact, flow outside, but it is unclear how useful that would
// be. Anyhow, closed-world is more important to optimize, and avoids this.)
std::unordered_set<Name> calledFromOutside;
};
// Does a walk while maintaining a map of names of branch targets to those
// expressions, so they can be found by their name.
// TODO: can this replace ControlFlowWalker in other places?
template<typename SubType, typename VisitorType = Visitor<SubType>>
struct BreakTargetWalker : public PostWalker<SubType, VisitorType> {
std::unordered_map<Name, Expression*> breakTargets;
Expression* findBreakTarget(Name name) { return breakTargets[name]; }
static void scan(SubType* self, Expression** currp) {
auto* curr = *currp;
BranchUtils::operateOnScopeNameDefs(
curr, [&](Name name) { self->breakTargets[name] = curr; });
PostWalker<SubType, VisitorType>::scan(self, currp);
}
};
// Walk the wasm and find all the links we need to care about, and the locations
// and roots related to them. This builds up a CollectedFuncInfo data structure.
// After all InfoCollectors run, those data structures will be merged and the
// main flow will begin.
struct InfoCollector
: public BreakTargetWalker<InfoCollector, OverriddenVisitor<InfoCollector>> {
SharedInfo& shared;
CollectedFuncInfo& info;
const PassOptions& options;
InfoCollector(SharedInfo& shared,
CollectedFuncInfo& info,
const PassOptions& options)
: shared(shared), info(info), options(options) {}
// Check if a type is relevant for us. If not, we can ignore it entirely.
bool isRelevant(Type type) {
if (type == Type::unreachable || type == Type::none) {
return false;
}
if (type.isTuple()) {
for (auto t : type) {
if (isRelevant(t)) {
return true;
}
}
}
return true;
}
bool isRelevant(Signature sig) {
return isRelevant(sig.params) || isRelevant(sig.results);
}
bool isRelevant(Expression* curr) { return curr && isRelevant(curr->type); }
template<typename T> bool isRelevant(const T& vec) {
for (auto* expr : vec) {
if (isRelevant(expr->type)) {
return true;
}
}
return false;
}
// Each visit*() call is responsible for connecting the children of a node to
// that node. Responsibility for connecting the node's output to anywhere
// else (another expression or the function itself, if we are at the top
// level) is the responsibility of the outside.
void visitBlock(Block* curr) {
if (curr->list.empty()) {
return;
}
// The final item in the block can flow a value to here as well.
receiveChildValue(curr->list.back(), curr);
}
void visitIf(If* curr) {
// Each arm may flow out a value.
receiveChildValue(curr->ifTrue, curr);
receiveChildValue(curr->ifFalse, curr);
}
void visitLoop(Loop* curr) { receiveChildValue(curr->body, curr); }
void visitBreak(Break* curr) {
// Connect the value (if present) to the break target.
handleBreakValue(curr);
// The value may also flow through in a br_if (the type will indicate that,
// which receiveChildValue will notice).
receiveChildValue(curr->value, curr);
}
void visitSwitch(Switch* curr) { handleBreakValue(curr); }
void visitLoad(Load* curr) {
// We could infer the exact type here, but as no subtyping is possible, it
// would have no benefit, so just add a generic root (which will be "Many").
// See the comment on the ContentOracle class.
addRoot(curr);
}
void visitStore(Store* curr) {}
void visitAtomicRMW(AtomicRMW* curr) { addRoot(curr); }
void visitAtomicCmpxchg(AtomicCmpxchg* curr) { addRoot(curr); }
void visitAtomicWait(AtomicWait* curr) { addRoot(curr); }
void visitAtomicNotify(AtomicNotify* curr) { addRoot(curr); }
void visitAtomicFence(AtomicFence* curr) {}
void visitPause(Pause* curr) {}
void visitSIMDExtract(SIMDExtract* curr) { addRoot(curr); }
void visitSIMDReplace(SIMDReplace* curr) { addRoot(curr); }
void visitSIMDShuffle(SIMDShuffle* curr) { addRoot(curr); }
void visitSIMDTernary(SIMDTernary* curr) { addRoot(curr); }
void visitSIMDShift(SIMDShift* curr) { addRoot(curr); }
void visitSIMDLoad(SIMDLoad* curr) { addRoot(curr); }
void visitSIMDLoadStoreLane(SIMDLoadStoreLane* curr) { addRoot(curr); }
void visitMemoryInit(MemoryInit* curr) {}
void visitDataDrop(DataDrop* curr) {}
void visitMemoryCopy(MemoryCopy* curr) {}
void visitMemoryFill(MemoryFill* curr) {}
void visitConst(Const* curr) {
addRoot(curr, PossibleContents::literal(curr->value));
}
void visitUnary(Unary* curr) {
// We could optimize cases like this using interpreter integration: if the
// input is a Literal, we could interpret the Literal result. However, if
// the input is a literal then the GUFA pass will emit a Const there, and
// the Precompute pass can use that later to interpret a result. That is,
// the input we need here, a constant, is already something GUFA can emit as
// an output. As a result, integrating the interpreter here would perhaps
// make compilation require fewer steps, but it wouldn't let us optimize
// more than we could before.
addRoot(curr);
}
void visitBinary(Binary* curr) { addRoot(curr); }
void visitSelect(Select* curr) {
receiveChildValue(curr->ifTrue, curr);
receiveChildValue(curr->ifFalse, curr);
}
void visitDrop(Drop* curr) {}
void visitMemorySize(MemorySize* curr) { addRoot(curr); }
void visitMemoryGrow(MemoryGrow* curr) { addRoot(curr); }
void visitRefNull(RefNull* curr) {
addRoot(
curr,
PossibleContents::literal(Literal::makeNull(curr->type.getHeapType())));
}
void visitRefIsNull(RefIsNull* curr) {
// TODO: Optimize when possible. For example, if we can infer an exact type
// here which allows us to know the result then we should do so. This
// is unlike the case in visitUnary, above: the information that lets
// us optimize *cannot* be written into Binaryen IR (unlike a Literal)
// so using it during this pass allows us to optimize new things.
addRoot(curr);
}
void visitRefFunc(RefFunc* curr) {
if (!getModule()->getFunction(curr->func)->imported()) {
// This is not imported, so we know the exact function literal.
addRoot(
curr,
PossibleContents::literal(Literal::makeFunc(curr->func, *getModule())));
} else {
// This is imported, so it is effectively a global.
addRoot(curr,
PossibleContents::global(
curr->func, ExternalKind::Function, curr->type));
}
// The presence of a RefFunc indicates the function may be called
// indirectly, so add the relevant connections for this particular function.
// We do so here in the RefFunc so that we only do it for functions that
// actually have a RefFunc.
auto* func = getModule()->getFunction(curr->func);
for (Index i = 0; i < func->getParams().size(); i++) {
info.links.push_back({SignatureParamLocation{func->type.getHeapType(), i},
ParamLocation{func, i}});
}
for (Index i = 0; i < func->getResults().size(); i++) {
info.links.push_back(
{ResultLocation{func, i},
SignatureResultLocation{func->type.getHeapType(), i}});
}
if (!options.closedWorld) {
info.calledFromOutside.insert(curr->func);
}
}
void visitRefEq(RefEq* curr) { addRoot(curr); }
void visitTableGet(TableGet* curr) {
// TODO: be more precise
addRoot(curr);
}
void visitTableSet(TableSet* curr) {}
void visitTableSize(TableSize* curr) { addRoot(curr); }
void visitTableGrow(TableGrow* curr) { addRoot(curr); }
void visitTableFill(TableFill* curr) { addRoot(curr); }
void visitTableCopy(TableCopy* curr) { addRoot(curr); }
void visitTableInit(TableInit* curr) {}
void visitElemDrop(ElemDrop* curr) {}
void visitNop(Nop* curr) {}
void visitUnreachable(Unreachable* curr) {}
#ifndef NDEBUG
// For now we only handle pops in a catch body, see visitTry(). To check for
// errors, use counter of the pops we handled and all the pops; those sums
// must agree at the end, or else we've seen something we can't handle.
Index totalPops = 0;
Index handledPops = 0;
#endif
void visitPop(Pop* curr) {
#ifndef NDEBUG
totalPops++;
#endif
}
void visitRefI31(RefI31* curr) {
// TODO: optimize like struct references
addRoot(curr);
}
void visitI31Get(I31Get* curr) {
// TODO: optimize like struct references
addRoot(curr);
}
void visitRefCast(RefCast* curr) { receiveChildValue(curr->ref, curr); }
void visitRefTest(RefTest* curr) { addRoot(curr); }
void visitRefGetDesc(RefGetDesc* curr) {
// Parallel to StructGet.
if (!isRelevant(curr->ref)) {
addRoot(curr);
return;
}
addChildParentLink(curr->ref, curr);
}
void visitBrOn(BrOn* curr) {
// TODO: optimize when possible
handleBreakValue(curr);
receiveChildValue(curr->ref, curr);
}
void visitRefAs(RefAs* curr) {
if (curr->op == ExternConvertAny || curr->op == AnyConvertExtern) {
// The external conversion ops emit something of a completely different
// type, which we must mark as a root.
addRoot(curr);
return;
}
// All other RefAs operations flow values through while refining them (the
// filterExpressionContents method will handle the refinement
// automatically).
receiveChildValue(curr->value, curr);
}
void visitLocalSet(LocalSet* curr) {
if (!isRelevant(curr->value->type)) {
return;
}
// Tees flow out the value (receiveChildValue will see if this is a tee
// based on the type, automatically).
receiveChildValue(curr->value, curr);
// We handle connecting local.gets to local.sets below, in visitFunction.
}
void visitLocalGet(LocalGet* curr) {
// We handle connecting local.gets to local.sets below, in visitFunction.
}
// Globals read and write from their location.
void visitGlobalGet(GlobalGet* curr) {
if (isRelevant(curr->type)) {
// FIXME: we allow tuples in globals, so GlobalLocation needs a tupleIndex
// and we should loop here.
assert(!curr->type.isTuple());
info.links.push_back(
{GlobalLocation{curr->name}, ExpressionLocation{curr, 0}});
}
}
void visitGlobalSet(GlobalSet* curr) {
if (isRelevant(curr->value->type)) {
info.links.push_back(
{ExpressionLocation{curr->value, 0}, GlobalLocation{curr->name}});
}
}
// Iterates over a list of children and adds links to parameters and results
// as needed. The param/result functions receive the index and create the
// proper location for it.
template<typename T>
void handleCall(T* curr,
std::function<Location(Index)> makeParamLocation,
std::function<Location(Index)> makeResultLocation) {
Index i = 0;
for (auto* operand : curr->operands) {
if (isRelevant(operand->type)) {
info.links.push_back(
{ExpressionLocation{operand, 0}, makeParamLocation(i)});
}
i++;
}
// Add results, if anything flows out.
for (Index i = 0; i < curr->type.size(); i++) {
if (isRelevant(curr->type[i])) {
info.links.push_back(
{makeResultLocation(i), ExpressionLocation{curr, i}});
}
}
// If this is a return call then send the result to the function return as
// well.
if (curr->isReturn) {
auto results = getFunction()->getResults();
for (Index i = 0; i < results.size(); i++) {
auto result = results[i];
if (isRelevant(result)) {
info.links.push_back(
{makeResultLocation(i), ResultLocation{getFunction(), i}});
}
}
}
}
// Calls send values to params in their possible targets, and receive
// results.
template<typename T> void handleDirectCall(T* curr, Name targetName) {
auto* target = getModule()->getFunction(targetName);
handleCall(
curr,
[&](Index i) {
assert(i <= target->getParams().size());
return ParamLocation{target, i};
},
[&](Index i) {
assert(i <= target->getResults().size());
return ResultLocation{target, i};
});
}
template<typename T>
void handleIndirectCall(T* curr, HeapType targetType, Exactness exact) {
// If the heap type is not a signature, which is the case for a bottom type
// (null) then nothing can be called.
if (!targetType.isSignature()) {
assert(targetType.isBottom());
return;
}
// Connect us to the given type.
auto sig = targetType.getSignature();
handleCall(
curr,
[&](Index i) {
assert(i <= sig.params.size());
return SignatureParamLocation{targetType, i};
},
[&](Index i) {
assert(i <= sig.results.size());
return SignatureResultLocation{targetType, i};
});
// If the type is exact, we only need to read SignatureParamLocation /
// SignatureResultLocation of this exact type, and we are done.
if (exact == Exact) {
return;
}
// Inexact type, so subtyping is relevant: add the relevant links.
// TODO: SignatureParamLocation is handled below in an inefficient way, see
// there.
// TODO: For CallRef, we could do something like readFromData() and use the
// flowing function reference's type, not the static type. We could
// even reuse ConeReadLocation if we generalized it to function types.
for (Index i = 0; i < sig.results.size(); i++) {
if (isRelevant(sig.results[i])) {
shared.subTypes.iterSubTypes(
targetType, [&](HeapType subType, Index depth) {
info.links.push_back({SignatureResultLocation{subType, i},
ExpressionLocation{curr, i}});
});
}
}
}
template<typename T> void handleIndirectCall(T* curr, Type targetType) {
// If the type is unreachable, nothing can be called (and there is no heap
// type to get).
if (targetType != Type::unreachable) {
handleIndirectCall(
curr, targetType.getHeapType(), targetType.getExactness());
}
}
void visitCall(Call* curr) {
Name targetName;
if (!Intrinsics(*getModule()).isCallWithoutEffects(curr)) {
// This is just a normal call.
handleDirectCall(curr, curr->target);
return;
}
// A call-without-effects receives a function reference and calls it, the
// same as a CallRef. When we have a flag for non-closed-world, we should
// handle this automatically by the reference flowing out to an import,
// which is what binaryen intrinsics look like. For now, to support use
// cases of a closed world but that also use this intrinsic, handle the
// intrinsic specifically here. (Without that, the closed world assumption
// makes us ignore the function ref that flows to an import, so we are not
// aware that it is actually called.)
auto* target = curr->operands.back();
// We must ignore the last element when handling the call - the target is
// used to perform the call, and not sent during the call.
curr->operands.pop_back();
if (auto* refFunc = target->dynCast<RefFunc>()) {
// We can see exactly where this goes.
handleDirectCall(curr, refFunc->func);
} else {
// We can't see where this goes. We must be pessimistic and assume it
// can call anything of the proper type, the same as a CallRef. (We could
// look at the possible contents of |target| during the flow, but that
// would require special logic like we have for StructGet etc., and the
// intrinsics will be lowered away anyhow, so just running after that is
// a workaround.)
handleIndirectCall(curr, target->type);
}
// Restore the target.
curr->operands.push_back(target);
}
void visitCallIndirect(CallIndirect* curr) {
// TODO: optimize the call target like CallRef
// CallIndirect only knows a heap type, so it is always inexact.
handleIndirectCall(curr, curr->heapType, Inexact);
// If this goes to a public table, then we must root the output, as the
// table could contain anything at all, and calling functions there could
// return anything at all.
if (shared.publicTables.count(curr->table)) {
addRoot(curr);
}
// TODO: the table identity could also be used here in more ways
}
void visitCallRef(CallRef* curr) {
handleIndirectCall(curr, curr->target->type);
}
Location getTypeLocation(Type type) {
auto location = TypeLocation{type};
addRoot(location, PossibleContents::fromType(type));
return location;
}
Location getNullLocation(Type type) {
auto location = NullLocation{type};
addRoot(location, PossibleContents::literal(Literal::makeZero(type)));
return location;
}
// Iterates over a list of children and adds links from them. The target of
// those link is created using a function that is passed in, which receives
// the index of the child.
void linkChildList(ExpressionList& operands,
std::function<Location(Index)> makeTarget) {
Index i = 0;
for (auto* operand : operands) {
// This helper is not used from places that allow a tuple (hence we can
// hardcode the index 0 a few lines down).
assert(!operand->type.isTuple());
if (isRelevant(operand->type)) {
info.links.push_back({ExpressionLocation{operand, 0}, makeTarget(i)});
}
i++;
}
}
void visitStructNew(StructNew* curr) {
if (curr->type == Type::unreachable) {
return;
}
auto type = curr->type.getHeapType();
if (curr->isWithDefault()) {
// Link the default values to the struct's fields.
auto& fields = type.getStruct().fields;
for (Index i = 0; i < fields.size(); i++) {
info.links.push_back(
{getNullLocation(fields[i].type), DataLocation{type, i}});
}
} else {
// Link the operands to the struct's fields.
linkChildList(curr->operands,
[&](Index i) { return DataLocation{type, i}; });
}
if (curr->desc) {
info.links.push_back({ExpressionLocation{curr->desc, 0},
DataLocation{type, DataLocation::DescriptorIndex}});
}
addRoot(curr, PossibleContents::exactType(curr->type));
}
void visitArrayNew(ArrayNew* curr) {
if (curr->type == Type::unreachable) {