summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/passes/TypeRefining.cpp26
-rw-r--r--test/lit/passes/type-refining.wast137
2 files changed, 158 insertions, 5 deletions
diff --git a/src/passes/TypeRefining.cpp b/src/passes/TypeRefining.cpp
index c7a1fce6e..ee12c388d 100644
--- a/src/passes/TypeRefining.cpp
+++ b/src/passes/TypeRefining.cpp
@@ -137,6 +137,11 @@ struct TypeRefining : public Pass {
// that we can avoid wasteful work later if not.
bool canOptimize = false;
+ // We cannot modify public types.
+ auto publicTypes = ModuleUtils::getPublicHeapTypes(*module);
+ std::unordered_set<HeapType> publicTypesSet(publicTypes.begin(),
+ publicTypes.end());
+
// We have combined all the information we have about writes to the fields,
// but we still need to make sure that the new types makes sense. In
// particular, subtyping cares about things like mutability, and we also
@@ -155,6 +160,14 @@ struct TypeRefining : public Pass {
while (!work.empty()) {
auto type = work.pop();
+ for (auto subType : subTypes.getImmediateSubTypes(type)) {
+ work.push(subType);
+ }
+
+ if (publicTypesSet.count(type)) {
+ continue;
+ }
+
// First, find fields that have nothing written to them at all, and set
// their value to their old type. We must pick some type for the field,
// and we have nothing better to go on. (If we have a super, and it does
@@ -173,7 +186,14 @@ struct TypeRefining : public Pass {
if (auto super = type.getDeclaredSuperType()) {
auto& superFields = super->getStruct().fields;
for (Index i = 0; i < superFields.size(); i++) {
- auto newSuperType = finalInfos[*super][i].getLUB();
+ // The super's new type is either what we propagated, or, if it is
+ // public, unchanged since we cannot optimize it
+ Type newSuperType;
+ if (!publicTypesSet.count(*super)) {
+ newSuperType = finalInfos[*super][i].getLUB();
+ } else {
+ newSuperType = superFields[i].type;
+ }
auto& info = finalInfos[type][i];
auto newType = info.getLUB();
if (!Type::isSubType(newType, newSuperType)) {
@@ -215,10 +235,6 @@ struct TypeRefining : public Pass {
canOptimize = true;
}
}
-
- for (auto subType : subTypes.getImmediateSubTypes(type)) {
- work.push(subType);
- }
}
if (canOptimize) {
diff --git a/test/lit/passes/type-refining.wast b/test/lit/passes/type-refining.wast
index 5689ff3dc..8e8ccf24c 100644
--- a/test/lit/passes/type-refining.wast
+++ b/test/lit/passes/type-refining.wast
@@ -1383,3 +1383,140 @@
)
)
)
+
+;; We cannot refine the fields of public types. One of $A's children is public
+;; here, $B. That makes $A public as well, leaving only $C as theoretically
+;; optimizable, but we cannot refine a mutable field in a way that makes it
+;; differ from the super, so we end up doing nothing here.
+(module
+ ;; CHECK: (type $A (sub (struct (field (mut anyref)))))
+ (type $A (sub (struct (field (mut anyref)))))
+
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $B (sub $A (struct (field (mut anyref)))))
+ (type $B (sub $A (struct (field (mut anyref)))))
+ ;; CHECK: (type $brand (struct))
+ (type $brand (struct))
+ )
+
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $C (sub $A (struct (field (mut anyref)))))
+ (type $C (sub $A (struct (field (mut anyref)))))
+ ;; CHECK: (type $brand2 (struct))
+ (type $brand2 (struct))
+ ;; CHECK: (type $brand3 (struct))
+ (type $brand3 (struct))
+ )
+
+ ;; CHECK: (type $6 (func (param (ref any))))
+
+ ;; CHECK: (global $global (ref null $B) (ref.null none))
+ (global $global (ref null $B) (ref.null $B))
+
+ ;; CHECK: (export "global" (global $global))
+ (export "global" (global $global))
+
+ ;; CHECK: (func $work (type $6) (param $nn (ref any))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $A
+ ;; CHECK-NEXT: (local.get $nn)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $B
+ ;; CHECK-NEXT: (local.get $nn)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $C
+ ;; CHECK-NEXT: (local.get $nn)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $work (param $nn (ref any))
+ ;; All the types look refinable, as we write a non-nullable value.
+ (drop
+ (struct.new $A
+ (local.get $nn)
+ )
+ )
+ (drop
+ (struct.new $B
+ (local.get $nn)
+ )
+ )
+ (drop
+ (struct.new $C
+ (local.get $nn)
+ )
+ )
+ )
+)
+
+;; As above, but now the fields are all immutable. This allows us to refine $C,
+;; but nothing else.
+(module
+ ;; CHECK: (type $A (sub (struct (field anyref))))
+ (type $A (sub (struct (field anyref))))
+
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $B (sub $A (struct (field anyref))))
+ (type $B (sub $A (struct (field anyref))))
+ ;; CHECK: (type $brand (struct))
+ (type $brand (struct))
+ )
+
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $C (sub $A (struct (field (ref any)))))
+ (type $C (sub $A (struct (field anyref))))
+ (type $brand2 (struct))
+ (type $brand3 (struct))
+ )
+
+ ;; CHECK: (type $4 (func (param (ref any))))
+
+ ;; CHECK: (global $global (ref null $B) (ref.null none))
+ (global $global (ref null $B) (ref.null $B))
+
+ ;; CHECK: (export "global" (global $global))
+ (export "global" (global $global))
+
+ ;; CHECK: (func $work (type $4) (param $nn (ref any))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $A
+ ;; CHECK-NEXT: (local.get $nn)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $B
+ ;; CHECK-NEXT: (local.get $nn)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $C
+ ;; CHECK-NEXT: (local.get $nn)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $work (param $nn (ref any))
+ (drop
+ (struct.new $A
+ (local.get $nn)
+ )
+ )
+ (drop
+ (struct.new $B
+ (local.get $nn)
+ )
+ )
+ (drop
+ (struct.new $C
+ (local.get $nn)
+ )
+ )
+ )
+)