Skip to content

Update evaluateCastCheck for exact types #7541

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Apr 23, 2025
18 changes: 16 additions & 2 deletions src/ir/gc-type-utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,21 @@ inline EvaluationResult evaluateCastCheck(Type refType, Type castType) {
}

auto castHeapType = castType.getHeapType();
auto refIsHeapSubType = HeapType::isSubType(refHeapType, castHeapType);

// Check whether a value of type `a` is known to also have type `b`, assuming
// it is non-null.
auto isHeapSubtype = [](Type a, Type b) {
// If the heap type of `a` has no subtypes, then we know its value must be
// exactly `a`.
// TODO: Use information from a subtypes analysis, if available.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// TODO: Use information from a subtypes analysis, if available.
// Infer if a must be exact: if it has no subtypes, then it must be.
// TODO: Use information from a subtypes analysis, if available.

if (!a.getHeapType().isBasic() && !a.getHeapType().isOpen()) {
a = a.with(Exact);
}
// Ignore nullability.
return Type::isSubType(a.with(NonNullable), b.with(NonNullable));
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still not following this comment+code. If a is a user-defined heap type, why does it matter if it is open or not?

E.g. I would expect isHeapSubtype(child, parent) to return true regardless of the openness of child, since child "also has type" parent. Or do I not understand what "also has type" means?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "heap type" being checked here includes exactness, which is why we are actually comparing types and selectively ignoring nullability.

We're not just comparing the static type of the cast input with the cast target type. Instead, we're comparing the most specific safe approximation of the dynamic type of the cast input we can get with the cast target. If we know the value has dynamic heap type $foo, and $foo is final and cannot have subtypes, then we also know that the value must have dynamic heap type (exact $foo) because it cannot have any other type and still be a $foo.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, what is a "dynamic type"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type of the value at runtime.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, now I can follow the code here.

I wrote a comment suggestion above.


auto refIsHeapSubType = isHeapSubtype(refType, castType);

if (refIsHeapSubType) {
// The heap type is a subtype. All we need is for nullability to work out as
Expand All @@ -121,7 +135,7 @@ inline EvaluationResult evaluateCastCheck(Type refType, Type castType) {
return SuccessOnlyIfNonNull;
}

auto castIsHeapSubType = HeapType::isSubType(castHeapType, refHeapType);
auto castIsHeapSubType = isHeapSubtype(castType, refType);
bool heapTypesCompatible = refIsHeapSubType || castIsHeapSubType;

if (!heapTypesCompatible || castHeapType.isBottom()) {
Expand Down
3 changes: 2 additions & 1 deletion test/gtest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ endif()

set(unittest_SOURCES
arena.cpp
source-map.cpp
cast-check.cpp
cfg.cpp
dfa_minimization.cpp
disjoint_sets.cpp
Expand All @@ -18,6 +18,7 @@ set(unittest_SOURCES
possible-contents.cpp
printing.cpp
scc.cpp
source-map.cpp
stringify.cpp
suffix_tree.cpp
topological-sort.cpp
Expand Down
212 changes: 212 additions & 0 deletions test/gtest/cast-check.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
/*
* Copyright 2025 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 "ir/gc-type-utils.h"
#include "type-test.h"
#include "wasm-type.h"
#include "gtest/gtest.h"

namespace wasm {

using namespace GCTypeUtils;

class CastCheckTest : public TypeTest {
protected:
HeapType super, sub, subFinal;

void SetUp() override {
TypeBuilder builder(3);
builder[0] = Struct();
builder[0].setOpen();
builder[1] = Struct();
builder[1].subTypeOf(builder[0]).setOpen();
builder[2] = Struct();
builder[2].subTypeOf(builder[0]);

auto built = builder.build();
ASSERT_TRUE(built);

super = (*built)[0];
sub = (*built)[1];
subFinal = (*built)[2];
}
};

TEST_F(CastCheckTest, CastToSelfNonFinal) {
#define EXPECT_CAST( \
srcNullability, srcExactness, castNullability, castExactness, result) \
EXPECT_EQ(evaluateCastCheck(Type(super, srcNullability, srcExactness), \
Type(super, castNullability, castExactness)), \
result);

EXPECT_CAST(Nullable, Inexact, Nullable, Inexact, Success);
EXPECT_CAST(Nullable, Inexact, Nullable, Exact, Unknown);
EXPECT_CAST(Nullable, Inexact, NonNullable, Inexact, SuccessOnlyIfNonNull);
EXPECT_CAST(Nullable, Inexact, NonNullable, Exact, Unknown);
EXPECT_CAST(Nullable, Exact, Nullable, Inexact, Success);
EXPECT_CAST(Nullable, Exact, Nullable, Exact, Success);
EXPECT_CAST(Nullable, Exact, NonNullable, Inexact, SuccessOnlyIfNonNull);
EXPECT_CAST(Nullable, Exact, NonNullable, Exact, SuccessOnlyIfNonNull);
EXPECT_CAST(NonNullable, Inexact, Nullable, Inexact, Success);
EXPECT_CAST(NonNullable, Inexact, Nullable, Exact, Unknown);
EXPECT_CAST(NonNullable, Inexact, NonNullable, Inexact, Success);
EXPECT_CAST(NonNullable, Inexact, NonNullable, Exact, Unknown);
EXPECT_CAST(NonNullable, Exact, Nullable, Inexact, Success);
EXPECT_CAST(NonNullable, Exact, Nullable, Exact, Success);
EXPECT_CAST(NonNullable, Exact, NonNullable, Inexact, Success);
EXPECT_CAST(NonNullable, Exact, NonNullable, Exact, Success);
#undef EXPECT_CAST
}

TEST_F(CastCheckTest, CastToSelfFinal) {
#define EXPECT_CAST( \
srcNullability, srcExactness, castNullability, castExactness, result) \
EXPECT_EQ(evaluateCastCheck(Type(subFinal, srcNullability, srcExactness), \
Type(subFinal, castNullability, castExactness)), \
result);

EXPECT_CAST(Nullable, Inexact, Nullable, Inexact, Success);
EXPECT_CAST(Nullable, Inexact, Nullable, Exact, Success);
EXPECT_CAST(Nullable, Inexact, NonNullable, Inexact, SuccessOnlyIfNonNull);
EXPECT_CAST(Nullable, Inexact, NonNullable, Exact, SuccessOnlyIfNonNull);
EXPECT_CAST(Nullable, Exact, Nullable, Inexact, Success);
EXPECT_CAST(Nullable, Exact, Nullable, Exact, Success);
EXPECT_CAST(Nullable, Exact, NonNullable, Inexact, SuccessOnlyIfNonNull);
EXPECT_CAST(Nullable, Exact, NonNullable, Exact, SuccessOnlyIfNonNull);
EXPECT_CAST(NonNullable, Inexact, Nullable, Inexact, Success);
EXPECT_CAST(NonNullable, Inexact, Nullable, Exact, Success);
EXPECT_CAST(NonNullable, Inexact, NonNullable, Inexact, Success);
EXPECT_CAST(NonNullable, Inexact, NonNullable, Exact, Success);
EXPECT_CAST(NonNullable, Exact, Nullable, Inexact, Success);
EXPECT_CAST(NonNullable, Exact, Nullable, Exact, Success);
EXPECT_CAST(NonNullable, Exact, NonNullable, Inexact, Success);
EXPECT_CAST(NonNullable, Exact, NonNullable, Exact, Success);
#undef EXPECT_CAST
}

TEST_F(CastCheckTest, CastToSuper) {
#define EXPECT_CAST( \
srcNullability, srcExactness, castNullability, castExactness, result) \
EXPECT_EQ(evaluateCastCheck(Type(sub, srcNullability, srcExactness), \
Type(super, castNullability, castExactness)), \
result);

EXPECT_CAST(Nullable, Inexact, Nullable, Inexact, Success);
EXPECT_CAST(Nullable, Inexact, Nullable, Exact, SuccessOnlyIfNull);
EXPECT_CAST(Nullable, Inexact, NonNullable, Inexact, SuccessOnlyIfNonNull);
EXPECT_CAST(Nullable, Inexact, NonNullable, Exact, Failure);
EXPECT_CAST(Nullable, Exact, Nullable, Inexact, Success);
EXPECT_CAST(Nullable, Exact, Nullable, Exact, SuccessOnlyIfNull);
EXPECT_CAST(Nullable, Exact, NonNullable, Inexact, SuccessOnlyIfNonNull);
EXPECT_CAST(Nullable, Exact, NonNullable, Exact, Failure);
EXPECT_CAST(NonNullable, Inexact, Nullable, Inexact, Success);
EXPECT_CAST(NonNullable, Inexact, Nullable, Exact, Failure);
EXPECT_CAST(NonNullable, Inexact, NonNullable, Inexact, Success);
EXPECT_CAST(NonNullable, Inexact, NonNullable, Exact, Failure);
EXPECT_CAST(NonNullable, Exact, Nullable, Inexact, Success);
EXPECT_CAST(NonNullable, Exact, Nullable, Exact, Failure);
EXPECT_CAST(NonNullable, Exact, NonNullable, Inexact, Success);
EXPECT_CAST(NonNullable, Exact, NonNullable, Exact, Failure);
#undef EXPECT_CAST
}

TEST_F(CastCheckTest, CastToSub) {
#define EXPECT_CAST( \
srcNullability, srcExactness, castNullability, castExactness, result) \
EXPECT_EQ(evaluateCastCheck(Type(super, srcNullability, srcExactness), \
Type(sub, castNullability, castExactness)), \
result);

EXPECT_CAST(Nullable, Inexact, Nullable, Inexact, Unknown);
EXPECT_CAST(Nullable, Inexact, Nullable, Exact, Unknown);
EXPECT_CAST(Nullable, Inexact, NonNullable, Inexact, Unknown);
EXPECT_CAST(Nullable, Inexact, NonNullable, Exact, Unknown);
EXPECT_CAST(Nullable, Exact, Nullable, Inexact, SuccessOnlyIfNull);
EXPECT_CAST(Nullable, Exact, Nullable, Exact, SuccessOnlyIfNull);
EXPECT_CAST(Nullable, Exact, NonNullable, Inexact, Failure);
EXPECT_CAST(Nullable, Exact, NonNullable, Exact, Failure);
EXPECT_CAST(NonNullable, Inexact, Nullable, Inexact, Unknown);
EXPECT_CAST(NonNullable, Inexact, Nullable, Exact, Unknown);
EXPECT_CAST(NonNullable, Inexact, NonNullable, Inexact, Unknown);
EXPECT_CAST(NonNullable, Inexact, NonNullable, Exact, Unknown);
EXPECT_CAST(NonNullable, Exact, Nullable, Inexact, Failure);
EXPECT_CAST(NonNullable, Exact, Nullable, Exact, Failure);
EXPECT_CAST(NonNullable, Exact, NonNullable, Inexact, Failure);
EXPECT_CAST(NonNullable, Exact, NonNullable, Exact, Failure);
#undef EXPECT_CAST
}

TEST_F(CastCheckTest, CastToSibling) {
#define EXPECT_CAST( \
srcNullability, srcExactness, castNullability, castExactness, result) \
EXPECT_EQ(evaluateCastCheck(Type(sub, srcNullability, srcExactness), \
Type(subFinal, castNullability, castExactness)), \
result);

EXPECT_CAST(Nullable, Inexact, Nullable, Inexact, SuccessOnlyIfNull);
EXPECT_CAST(Nullable, Inexact, Nullable, Exact, SuccessOnlyIfNull);
EXPECT_CAST(Nullable, Inexact, NonNullable, Inexact, Failure);
EXPECT_CAST(Nullable, Inexact, NonNullable, Exact, Failure);
EXPECT_CAST(Nullable, Exact, Nullable, Inexact, SuccessOnlyIfNull);
EXPECT_CAST(Nullable, Exact, Nullable, Exact, SuccessOnlyIfNull);
EXPECT_CAST(Nullable, Exact, NonNullable, Inexact, Failure);
EXPECT_CAST(Nullable, Exact, NonNullable, Exact, Failure);
EXPECT_CAST(NonNullable, Inexact, Nullable, Inexact, Failure);
EXPECT_CAST(NonNullable, Inexact, Nullable, Exact, Failure);
EXPECT_CAST(NonNullable, Inexact, NonNullable, Inexact, Failure);
EXPECT_CAST(NonNullable, Inexact, NonNullable, Exact, Failure);
EXPECT_CAST(NonNullable, Exact, Nullable, Inexact, Failure);
EXPECT_CAST(NonNullable, Exact, Nullable, Exact, Failure);
EXPECT_CAST(NonNullable, Exact, NonNullable, Inexact, Failure);
EXPECT_CAST(NonNullable, Exact, NonNullable, Exact, Failure);
#undef EXPECT_CAST
}

TEST_F(CastCheckTest, CastToBottom) {
#define EXPECT_CAST(srcNullability, srcExactness, castNullability, result) \
EXPECT_EQ(evaluateCastCheck(Type(super, srcNullability, srcExactness), \
Type(HeapType::none, castNullability, Inexact)), \
result);

EXPECT_CAST(Nullable, Inexact, Nullable, SuccessOnlyIfNull);
EXPECT_CAST(Nullable, Inexact, NonNullable, Failure);
EXPECT_CAST(Nullable, Exact, Nullable, SuccessOnlyIfNull);
EXPECT_CAST(Nullable, Exact, NonNullable, Failure);
EXPECT_CAST(NonNullable, Inexact, Nullable, Failure);
EXPECT_CAST(NonNullable, Inexact, NonNullable, Failure);
EXPECT_CAST(NonNullable, Exact, Nullable, Failure);
EXPECT_CAST(NonNullable, Exact, NonNullable, Failure);
#undef EXPECT_CAST
}

TEST_F(CastCheckTest, CastFromBottom) {
#define EXPECT_CAST(srcNullability, castNullability, castExactness, result) \
EXPECT_EQ(evaluateCastCheck(Type(HeapType::none, srcNullability, Inexact), \
Type(super, castNullability, castExactness)), \
result);

EXPECT_CAST(Nullable, Nullable, Inexact, Success);
EXPECT_CAST(Nullable, Nullable, Exact, Success);
EXPECT_CAST(Nullable, NonNullable, Inexact, Failure);
EXPECT_CAST(Nullable, NonNullable, Exact, Failure);
EXPECT_CAST(NonNullable, Nullable, Inexact, GCTypeUtils::Unreachable);
EXPECT_CAST(NonNullable, Nullable, Exact, GCTypeUtils::Unreachable);
EXPECT_CAST(NonNullable, NonNullable, Inexact, GCTypeUtils::Unreachable);
EXPECT_CAST(NonNullable, NonNullable, Exact, GCTypeUtils::Unreachable);
#undef EXPECT_CAST
}

} // namespace wasm
Loading