diff --git a/src/passes/GlobalStructInference.cpp b/src/passes/GlobalStructInference.cpp index e2b728fc10b..166c3f58fee 100644 --- a/src/passes/GlobalStructInference.cpp +++ b/src/passes/GlobalStructInference.cpp @@ -45,6 +45,10 @@ // comparison. But we can compare structs, so if the function references are in // vtables, and the vtables follow the above pattern, then we can optimize. // +// This also optimizes some related things - reads from structs created in +// globals - that benefit from the infrastructure here (see unnesting, below), +// even without this type-based approach. +// // TODO: Only do the case with a select when shrinkLevel == 0? // @@ -203,11 +207,6 @@ struct GlobalStructInference : public Pass { } } - if (typeGlobals.empty()) { - // We found nothing we can optimize. - return; - } - // The above loop on typeGlobalsCopy is on an unsorted data structure, and // that can lead to nondeterminism in typeGlobals. Sort the vectors there to // ensure determinism. @@ -328,18 +327,12 @@ struct GlobalStructInference : public Pass { // We must ignore the case of a non-struct heap type, that is, a bottom // type (which is all that is left after we've already ruled out - // unreachable). Such things will not be in typeGlobals, which we are - // checking now anyhow. + // unreachable). auto heapType = type.getHeapType(); - auto iter = parent.typeGlobals.find(heapType); - if (iter == parent.typeGlobals.end()) { + if (!heapType.isStruct()) { return; } - // This cannot be a bottom type as we found it in the typeGlobals map, - // which only contains types of struct.news. - assert(heapType.isStruct()); - // The field must be immutable. std::optional field; if (fieldIndex != DescriptorIndex) { @@ -349,12 +342,36 @@ struct GlobalStructInference : public Pass { } } + auto& wasm = *getModule(); + + // This is a read of an immutable field. See if it is a trivial case, of + // a read from an immutable global. + if (auto* get = ref->dynCast()) { + auto* global = wasm.getGlobal(get->name); + if (!global->mutable_ && !global->imported()) { + if (auto* structNew = global->init->dynCast()) { + auto value = readFromStructNew(structNew, fieldIndex, field); + // TODO: handle non-constant values using unnesting, as below. + if (value.isConstant()) { + replaceCurrent(value.getConstant().makeExpression(wasm)); + return; + } + // Otherwise, continue to try the main type-based optimization + // below. + } + } + } + + auto iter = parent.typeGlobals.find(heapType); + if (iter == parent.typeGlobals.end()) { + return; + } + const auto& globals = iter->second; if (globals.size() == 0) { return; } - auto& wasm = *getModule(); Builder builder(wasm); if (globals.size() == 1) { @@ -388,28 +405,8 @@ struct GlobalStructInference : public Pass { for (Index i = 0; i < globals.size(); i++) { Name global = globals[i]; auto* structNew = wasm.getGlobal(global)->init->cast(); - // The value that is read from this struct.new. - Value value; - - // Find the value read from the struct and represent it as a Value. - PossibleConstantValues constant; - if (field && structNew->isWithDefault()) { - constant.note(Literal::makeZero(field->type)); - value.content = constant; - } else { - Expression* operand; - if (field) { - operand = structNew->operands[fieldIndex]; - } else { - operand = structNew->desc; - } - constant.note(operand, wasm); - if (constant.isConstant()) { - value.content = constant; - } else { - value.content = operand; - } - } + // Find the value read from the struct.new. + auto value = readFromStructNew(structNew, fieldIndex, field); // If the value is constant, it may be grouped as mentioned before. // See if it matches anything we've seen before. @@ -533,6 +530,32 @@ struct GlobalStructInference : public Pass { ReFinalize().walkFunctionInModule(func, getModule()); } } + + Value readFromStructNew(StructNew* structNew, + Index fieldIndex, + std::optional& field) { + // Find the value read from the struct and represent it as a Value. + Value value; + PossibleConstantValues constant; + if (field && structNew->isWithDefault()) { + constant.note(Literal::makeZero(field->type)); + value.content = constant; + } else { + Expression* operand; + if (field) { + operand = structNew->operands[fieldIndex]; + } else { + operand = structNew->desc; + } + constant.note(operand, *getModule()); + if (constant.isConstant()) { + value.content = constant; + } else { + value.content = operand; + } + } + return value; + } }; // Find the optimization opportunitites in parallel. diff --git a/test/lit/passes/gsi-nontype.wast b/test/lit/passes/gsi-nontype.wast new file mode 100644 index 00000000000..efe9082de4e --- /dev/null +++ b/test/lit/passes/gsi-nontype.wast @@ -0,0 +1,206 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: foreach %s %t wasm-opt --gsi -all --closed-world -S -o - | filecheck %s + +;; Non-type-based optimizations in --gsi + +;; Create an immutable vtable in an immutable global, which we can optimize +;; with. +(module + ;; CHECK: (type $vtable (struct (field funcref))) + (type $vtable (struct funcref)) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (import "a" "b" (global $imported funcref)) + (import "a" "b" (global $imported funcref)) + + ;; CHECK: (global $vtable (ref $vtable) (struct.new $vtable + ;; CHECK-NEXT: (global.get $imported) + ;; CHECK-NEXT: )) + (global $vtable (ref $vtable) + (struct.new $vtable + (global.get $imported) + ) + ) + + ;; CHECK: (func $test (type $1) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (global.get $imported) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + ;; This get reads $import. + (drop + (struct.get $vtable 0 + (global.get $vtable) + ) + ) + ) +) + +;; As above, but the global is not immutable, so we cannot optimize. +(module + ;; CHECK: (type $vtable (struct (field funcref))) + (type $vtable (struct funcref)) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (import "a" "b" (global $imported funcref)) + (import "a" "b" (global $imported funcref)) + + ;; CHECK: (global $vtable (mut (ref $vtable)) (struct.new $vtable + ;; CHECK-NEXT: (global.get $imported) + ;; CHECK-NEXT: )) + (global $vtable (mut (ref $vtable)) + (struct.new $vtable + (global.get $imported) + ) + ) + + ;; CHECK: (func $test (type $1) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $vtable 0 + ;; CHECK-NEXT: (global.get $vtable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (drop + (struct.get $vtable 0 + (global.get $vtable) + ) + ) + ) +) + +;; As above, but the global does not contain a struct.new, so we cannot +;; optimize. +(module + ;; CHECK: (type $vtable (struct (field funcref))) + (type $vtable (struct funcref)) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (import "a" "b" (global $imported funcref)) + (import "a" "b" (global $imported funcref)) + + ;; CHECK: (global $vtable (ref null $vtable) (ref.null none)) + (global $vtable (ref null $vtable) (ref.null $vtable)) + + ;; CHECK: (func $test (type $1) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $vtable 0 + ;; CHECK-NEXT: (global.get $vtable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (drop + (struct.get $vtable 0 + (global.get $vtable) + ) + ) + ) +) + +;; As above, but the value in the struct.new is not constant, so we cannot +;; optimize. +(module + ;; CHECK: (type $table (struct (field anyref))) + (type $table (struct anyref)) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (import "a" "b" (global $imported funcref)) + (import "a" "b" (global $imported funcref)) + + ;; CHECK: (global $table (ref $table) (struct.new $table + ;; CHECK-NEXT: (struct.new_default $table) + ;; CHECK-NEXT: )) + (global $table (ref $table) + (struct.new $table + (struct.new_default $table) + ) + ) + + ;; CHECK: (func $test (type $1) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $table 0 + ;; CHECK-NEXT: (global.get $table) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (drop + (struct.get $table 0 + (global.get $table) + ) + ) + ) +) + +;; Test we can optimize a descriptor read. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct (sub (descriptor $desc (struct)))) + (type $struct (sub (descriptor $desc (struct)))) + ;; CHECK: (type $desc (sub (describes $struct (struct)))) + (type $desc (sub (describes $struct (struct)))) + ) + + ;; CHECK: (type $2 (func)) + + ;; CHECK: (import "a" "b" (global $imported (ref (exact $desc)))) + (import "a" "b" (global $imported (ref (exact $desc)))) + + ;; CHECK: (global $struct (ref $struct) (struct.new_default $struct + ;; CHECK-NEXT: (global.get $imported) + ;; CHECK-NEXT: )) + (global $struct (ref $struct) + (struct.new $struct + (global.get $imported) + ) + ) + + ;; CHECK: (func $test (type $2) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (global.get $imported) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + ;; This get reads $import. + (drop + (ref.get_desc $struct + (global.get $struct) + ) + ) + ) +) + +;; Check we do not error on a global.get of an imported global. +(module + ;; CHECK: (type $vtable (struct (field funcref))) + (type $vtable (struct funcref)) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (import "a" "b" (global $imported (ref $vtable))) + (import "a" "b" (global $imported (ref $vtable))) + + ;; CHECK: (func $test (type $1) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $vtable 0 + ;; CHECK-NEXT: (global.get $imported) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (drop + (struct.get $vtable 0 + (global.get $imported) + ) + ) + ) +) + diff --git a/test/lit/passes/gsi.wast b/test/lit/passes/gsi.wast index 7ff7bd4cc6d..dd3f6a6d4ef 100644 --- a/test/lit/passes/gsi.wast +++ b/test/lit/passes/gsi.wast @@ -1901,7 +1901,7 @@ ;; CHECK: (type $struct (struct (field i8))) (type $struct (struct (field i8))) - ;; CHECK: (type $1 (func (result i32))) + ;; CHECK: (type $1 (func (param (ref $struct)) (result i32))) ;; CHECK: (global $A (ref $struct) (struct.new $struct ;; CHECK-NEXT: (i32.const 257) @@ -1917,7 +1917,7 @@ (i32.const 258) )) - ;; CHECK: (func $test (type $1) (result i32) + ;; CHECK: (func $test (type $1) (param $x (ref $struct)) (result i32) ;; CHECK-NEXT: (select ;; CHECK-NEXT: (i32.and ;; CHECK-NEXT: (i32.const 257) @@ -1929,18 +1929,18 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (global.get $A) + ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.get $A) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $test (result i32) + (func $test (param $x (ref $struct)) (result i32) ;; We can infer this value is one of two things since only two objects exist ;; of this type. We must emit the proper truncated value for them, as the ;; values are truncated into i8. (struct.get_u $struct 0 - (global.get $A) + (local.get $x) ) ) )