diff options
-rw-r--r-- | src/passes/TypeRefining.cpp | 26 | ||||
-rw-r--r-- | test/lit/passes/type-refining.wast | 137 |
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) + ) + ) + ) +) |