diff options
-rw-r--r-- | src/ir/possible-contents.cpp | 26 | ||||
-rw-r--r-- | src/ir/possible-contents.h | 18 | ||||
-rw-r--r-- | test/gtest/possible-contents.cpp | 18 |
3 files changed, 46 insertions, 16 deletions
diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index b2a0b417a..83762a458 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -197,18 +197,17 @@ void PossibleContents::intersectWithFullCone(const PossibleContents& other) { return; } - if (isLiteral() || isGlobal()) { + if (isLiteral()) { // The information about the value being identical to a particular literal - // or immutable global is not removed by intersection, if the type is in the - // cone we are intersecting with. + // is not removed by intersection, if the type is in the cone we are + // intersecting with. if (isSubType) { return; } - // The type must change, so continue down to the generic code path. - // TODO: for globals we could perhaps refine the type here, but then the - // type on GlobalInfo would not match the module, so that needs some - // refactoring. + // The type must change in a nontrivial manner, so continue down to the + // generic code path. This will stop being a Literal. TODO: can we do better + // here? } // Intersect the cones, as there is no more specific information we can use. @@ -226,6 +225,14 @@ void PossibleContents::intersectWithFullCone(const PossibleContents& other) { newHeapType = heapType; } + // 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<Name> globalName; + if (isGlobal()) { + globalName = getGlobal(); + } + auto newType = Type(newHeapType, nullability); // By assumption |other| has full depth. Consider the other cone in |this|. @@ -261,6 +268,11 @@ void PossibleContents::intersectWithFullCone(const PossibleContents& other) { value = ConeType{newType, newDepth}; } + + if (globalName) { + // Restore the global but keep the new and refined type. + value = GlobalInfo{*globalName, getType()}; + } } bool PossibleContents::haveIntersection(const PossibleContents& a, diff --git a/src/ir/possible-contents.h b/src/ir/possible-contents.h index b7c9bfafd..b4326688e 100644 --- a/src/ir/possible-contents.h +++ b/src/ir/possible-contents.h @@ -66,10 +66,15 @@ class PossibleContents { struct GlobalInfo { Name name; - // The type of the global in the module. We stash this here so that we do - // not need to pass around a module all the time. - // TODO: could we save size in this variant if we did pass around the - // module? + // The type of contents. Note that this may not match the type of the + // global, if we were filtered. For example: + // + // (ref.as_non_null + // (global.get $nullable-global) + // ) + // + // The contents flowing out will be a Global, but of a non-nullable type, + // unlike the original global. Type type; bool operator==(const GlobalInfo& other) const { return name == other.name && type == other.type; @@ -287,6 +292,9 @@ public: return builder.makeConstantExpression(getLiteral()); } else { auto name = getGlobal(); + // Note that we load the type from the module, rather than use the type + // in the GlobalInfo, as that type may not match the global (see comment + // in the GlobalInfo declaration above). return builder.makeGlobalGet(name, wasm.getGlobal(name)->type); } } @@ -321,7 +329,7 @@ public: o << " HT: " << h; } } else if (isGlobal()) { - o << "GlobalInfo $" << getGlobal(); + o << "GlobalInfo $" << getGlobal() << " T: " << getType(); } else if (auto* coneType = std::get_if<ConeType>(&value)) { auto t = coneType->type; o << "ConeType " << t; diff --git a/test/gtest/possible-contents.cpp b/test/gtest/possible-contents.cpp index c6b7d000c..dc10c00bf 100644 --- a/test/gtest/possible-contents.cpp +++ b/test/gtest/possible-contents.cpp @@ -787,18 +787,28 @@ TEST_F(PossibleContentsTest, TestStructCones) { PossibleContents::fullConeType(nnD), none); - // Globals stay as globals if their type is in the cone. Otherwise, they lose - // the global info and we compute a normal cone intersection on them. The - // same for literals. + // Globals stay as globals, but their type might get refined. assertIntersection( funcGlobal, PossibleContents::fullConeType(funcref), funcGlobal); + // No global filtering. auto signature = Type(Signature(Type::none, Type::none), Nullable); assertIntersection( nonNullFunc, PossibleContents::fullConeType(signature), nonNullFunc); + + // Filter a global to a more specific type. assertIntersection(funcGlobal, PossibleContents::fullConeType(signature), - PossibleContents::fullConeType(signature)); + PossibleContents::global("funcGlobal", signature)); + + // Filter a global's nullability only. + auto nonNullFuncRef = Type(HeapType::func, NonNullable); + assertIntersection(funcGlobal, + PossibleContents::fullConeType(nonNullFuncRef), + nonNullFuncGlobal); + + // Incompatible global and cone types have no intersection. + assertIntersection(funcGlobal, PossibleContents::fullConeType(nullE), none); // Incompatible hierarchies have no intersection. assertIntersection( |