diff --git a/src/passes/GlobalTypeOptimization.cpp b/src/passes/GlobalTypeOptimization.cpp index 68e56b20298..5ed3574f4e9 100644 --- a/src/passes/GlobalTypeOptimization.cpp +++ b/src/passes/GlobalTypeOptimization.cpp @@ -372,6 +372,7 @@ struct GlobalTypeOptimization : public Pass { // That is, we can only append fields: we cannot reorder or remove any // field that is in the super. Index numSuperFields = 0; + bool superHasKeptFields = false; if (super) { // We have visited the super before. Get the information about its // fields. @@ -408,6 +409,7 @@ struct GlobalTypeOptimization : public Pass { // The super kept this field, so we must keep it as well. This can // happen when we need the field in both, but also in the corner // case where we don't need the field but the super is public. + superHasKeptFields = true; // We need to keep it at the same index so we remain compatible. indexesAfterRemoval[i] = superIndex; @@ -433,10 +435,10 @@ struct GlobalTypeOptimization : public Pass { } } - // If the type has no supertype (or its supertype has no fields), check - // if its first field becomes prototype-exposing. If so, add a + // If the type has no supertype (or its supertype has no kept fields), + // check if its first field becomes prototype-exposing. If so, add a // placeholder at index 0 and shift all computed indices. - if (isExposedNoProto && (!super || super->getStruct().fields.empty())) { + if (isExposedNoProto && !superHasKeptFields) { // Find the field that will become field 0. Index i = 0; for (; i < fields.size(); ++i) { diff --git a/test/lit/passes/gto-jsinterop.wast b/test/lit/passes/gto-jsinterop.wast index 4a2e24d8073..cae1c28f9ae 100644 --- a/test/lit/passes/gto-jsinterop.wast +++ b/test/lit/passes/gto-jsinterop.wast @@ -1753,3 +1753,64 @@ ) ) ) + +(module + ;; All fields of $super-desc are unread and removed by GTO, leaving + ;; $super-desc empty after optimization. In $sub-desc, the unread supertype + ;; field is also removed and the following externref field shifts to index 0, + ;; so $sub-desc must get an i8 placeholder at index 0 to avoid exposing a + ;; prototype. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (descriptor $super-desc) (struct))) + (type $super (sub (descriptor $super-desc) (struct))) + ;; CHECK: (type $super-desc (sub (describes $super) (struct))) + (type $super-desc (sub (describes $super) (struct (field f64)))) + ;; CHECK: (type $sub (sub final $super (descriptor $sub-desc) (struct))) + (type $sub (sub final $super (descriptor $sub-desc) (struct))) + ;; CHECK: (type $sub-desc (sub final $super-desc (describes $sub) (struct (field i8) (field externref)))) + (type $sub-desc (sub final $super-desc (describes $sub) (struct (field f64) (field externref)))) + ) + + ;; CHECK: (type $4 (func (param (ref $sub-desc)) (result externref))) + + ;; CHECK: (type $5 (func (result externref))) + + ;; CHECK: (import "env" "ext" (global $ext externref)) + (import "env" "ext" (global $ext externref)) + ;; CHECK: (export "export" (func $test)) + (export "export" (func $test)) + + ;; CHECK: (func $test (type $5) (result externref) + ;; CHECK-NEXT: (extern.convert_any + ;; CHECK-NEXT: (struct.new_default_desc $sub + ;; CHECK-NEXT: (struct.new $sub-desc + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (global.get $ext) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (result externref) + ;; Convert to extern to expose the value to JS. + (extern.convert_any + (struct.new_default_desc $sub + (struct.new $sub-desc + (f64.const 0) + (global.get $ext) + ) + ) + ) + ) + + ;; CHECK: (func $use (type $4) (param $d (ref $sub-desc)) (result externref) + ;; CHECK-NEXT: (struct.get $sub-desc 1 + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $use (param $d (ref $sub-desc)) (result externref) + (struct.get $sub-desc 1 + (local.get $d) + ) + ) +)