From da79a4753f587da102c12a897c662d9bbd7927eb Mon Sep 17 00:00:00 2001 From: Mason Wu Date: Tue, 15 Jul 2025 21:02:30 +0000 Subject: [PATCH] Update J2CLItableMerging to consider types whose vtables are custom descriptors --- src/passes/J2CLItableMerging.cpp | 63 ++++--- test/lit/passes/j2cl-merge-itables.wast | 225 +++++++++++++++++++++++- 2 files changed, 264 insertions(+), 24 deletions(-) diff --git a/src/passes/J2CLItableMerging.cpp b/src/passes/J2CLItableMerging.cpp index be8da22cefa..e10e54e4c2d 100644 --- a/src/passes/J2CLItableMerging.cpp +++ b/src/passes/J2CLItableMerging.cpp @@ -104,20 +104,33 @@ struct J2CLItableMerging : public Pass { continue; } - auto type = heapType.getStruct(); - if (typeNameInfo.fieldNames.empty() || - !typeNameInfo.fieldNames[0].equals("vtable")) { - continue; - } - if (typeNameInfo.fieldNames.size() < 1 || - !typeNameInfo.fieldNames[1].equals("itable")) { + if (typeNameInfo.fieldNames.empty()) { continue; } - auto vtabletype = type.fields[0].type.getHeapType(); - auto itabletype = type.fields[1].type.getHeapType(); + // The vtable may either be the first field or the custom descriptor. + std::optional vtabletype; + std::optional itabletype; + auto type = heapType.getStruct(); + if (auto descriptor = heapType.getDescriptorType()) { + if (!typeNameInfo.fieldNames[0].equals("itable")) { + continue; + } + vtabletype = descriptor; + itabletype = type.fields[0].type.getHeapType(); + } else { + if (!typeNameInfo.fieldNames[0].equals("vtable")) { + continue; + } + if (!typeNameInfo.fieldNames.count(1) || + !typeNameInfo.fieldNames[1].equals("itable")) { + continue; + } + vtabletype = type.fields[0].type.getHeapType(); + itabletype = type.fields[1].type.getHeapType(); + } - auto structItableSize = itabletype.getStruct().fields.size(); + auto structItableSize = itabletype->getStruct().fields.size(); if (itableSize != 0 && itableSize != structItableSize) { Fatal() << "--merge-j2cl-itables needs to be the first pass to run " @@ -128,11 +141,11 @@ struct J2CLItableMerging : public Pass { // Add a new StructInfo to the list by value so that its memory gets // reclaimed automatically on exit. - structInfos.push_back(StructInfo{heapType, vtabletype, itabletype}); + structInfos.push_back(StructInfo{heapType, *vtabletype, *itabletype}); // Point to the StructInfo just added to the list to be able to look it // up by its vtable and itable types. - structInfoByVtableType[vtabletype] = &structInfos.back(); - structInfoByITableType[itabletype] = &structInfos.back(); + structInfoByVtableType[*vtabletype] = &structInfos.back(); + structInfoByITableType[*itabletype] = &structInfos.back(); } // 2. Collect the globals for vtables and itables. @@ -275,16 +288,22 @@ struct J2CLItableMerging : public Pass { } // This is a struct.get that returns an itable type; - // Change to return the corresponding vtable type. + // Change to return the corresponding vtable type. If the vtable is a + // custom descriptor, change to a ref.get_desc instruction. Builder builder(*getModule()); - replaceCurrent(builder.makeStructGet( - 0, - curr->ref, - MemoryOrder::Unordered, - parent.structInfoByITableType[curr->type.getHeapType()] - ->javaClass.getStruct() - .fields[0] - .type)); + + HeapType& javaClass = parent.structInfoByITableType[curr->type.getHeapType()]->javaClass; + if (javaClass.getDescriptorType()) { + replaceCurrent(builder.makeRefGetDesc(curr->ref)); + } else { + replaceCurrent(builder.makeStructGet( + 0, + curr->ref, + MemoryOrder::Unordered, + javaClass.getStruct() + .fields[0] + .type)); + } } }; diff --git a/test/lit/passes/j2cl-merge-itables.wast b/test/lit/passes/j2cl-merge-itables.wast index 8506724fd1a..abe31299bb5 100644 --- a/test/lit/passes/j2cl-merge-itables.wast +++ b/test/lit/passes/j2cl-merge-itables.wast @@ -100,7 +100,7 @@ (global.get $SubObject.vtable) (global.get $SubObject.itable))) (drop - ;; The access to vtable field 0 is offset but the itable size and + ;; The access to vtable field 0 is offset by the itable size and ;; will be an access to field 1. (struct.get $SubObject.vtable 0 (struct.get $SubObject $vtable @@ -214,7 +214,7 @@ (global.get $SubObject.vtable) (global.get $SubObject.itable))) (drop - ;; The access to vtable field 0 is offset but the itable size and + ;; The access to vtable field 0 is offset by the itable size and ;; will be an access to field 1. (struct.get $SubObject.vtable 0 (struct.get $SubObject $vtable @@ -227,3 +227,224 @@ (local.get $o)))) ) ) + +;; Custom descriptors - Shared itable instance. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $Object (sub (descriptor $Object.vtable (struct (field $itable (ref $Object.itable)))))) + (type $Object (sub (descriptor $Object.vtable (struct + (field $itable (ref $Object.itable)))))) + + ;; CHECK: (type $SubObject (sub $Object (descriptor $SubObject.vtable (struct (field $itable (ref $Object.itable)))))) + (type $SubObject (sub $Object (descriptor $SubObject.vtable (struct + (field $itable (ref $Object.itable)))))) + + ;; CHECK: (type $function (func)) + (type $function (func)) + + ;; The $Object.itable field (a structref) will be added as a field to + ;; the beginning of this vtable. + ;; CHECK: (type $Object.vtable (sub (describes $Object (struct (field structref))))) + (type $Object.vtable (sub (describes $Object (struct)))) + + ;; The $Object.itable field (a structref) will be added as a field to + ;; the beginning of this vtable. + ;; CHECK: (type $SubObject.vtable (sub $Object.vtable (describes $SubObject (struct (field structref) (field (ref $function)))))) + (type $SubObject.vtable (sub $Object.vtable (describes $SubObject (struct + (field (ref $function)))))) + + ;; CHECK: (type $Object.itable (struct (field structref))) + (type $Object.itable (struct + (field (ref null struct)))) + ) + + ;; CHECK: (type $6 (func)) + + ;; CHECK: (global $Object.itable (ref $Object.itable) (struct.new_default $Object.itable)) + (global $Object.itable (ref $Object.itable) + (struct.new_default $Object.itable)) + + ;; CHECK: (global $SubObject.itable (ref $Object.itable) (global.get $Object.itable)) + (global $SubObject.itable (ref $Object.itable) + (global.get $Object.itable)) ;; uses shared empty itable instance. + + ;; The initialization for the itable field (null struct) will be added to this + ;; vtable instance. + ;; CHECK: (global $SubObject.vtable (ref (exact $SubObject.vtable)) (struct.new $SubObject.vtable + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.func $SubObject.f) + ;; CHECK-NEXT: )) + (global $SubObject.vtable (ref (exact $SubObject.vtable)) + (struct.new $SubObject.vtable (ref.func $SubObject.f))) + + + ;; The initialization for the itable field (null struct) will be added to this + ;; vtable instance. + ;; CHECK: (global $Object.vtable (ref (exact $Object.vtable)) (struct.new $Object.vtable + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: )) + (global $Object.vtable (ref (exact $Object.vtable)) + (struct.new $Object.vtable)) + + ;; CHECK: (func $SubObject.f (type $function) + ;; CHECK-NEXT: ) + (func $SubObject.f + (type $function) + ) + + ;; CHECK: (func $usages (type $6) + ;; CHECK-NEXT: (local $o (ref null $SubObject)) + ;; CHECK-NEXT: (local.set $o + ;; CHECK-NEXT: (struct.new $SubObject + ;; CHECK-NEXT: (global.get $SubObject.itable) + ;; CHECK-NEXT: (global.get $SubObject.vtable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $SubObject.vtable 1 + ;; CHECK-NEXT: (ref.get_desc $SubObject + ;; CHECK-NEXT: (local.get $o) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $SubObject.vtable 0 + ;; CHECK-NEXT: (ref.get_desc $SubObject + ;; CHECK-NEXT: (local.get $o) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $usages + (local $o (ref null $SubObject)) + (local.set $o + (struct.new $SubObject + (global.get $SubObject.itable) + (global.get $SubObject.vtable))) + (drop + ;; The access to vtable field 0 is offset by the itable size and + ;; will be an access to field 1. + (struct.get $SubObject.vtable 0 + (ref.get_desc $SubObject + (local.get $o)))) + (drop + ;; The access to itable field 0 will be rerouted to be an access to + ;; vtable field 0. + (struct.get $Object.itable 0 + (struct.get $SubObject $itable + (local.get $o)))) + ) +) + +;; Custom descriptors - Each type has its own itable. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $Object (sub (descriptor $Object.vtable (struct (field $itable (ref $Object.itable)))))) + (type $Object (sub (descriptor $Object.vtable (struct + (field $itable (ref $Object.itable)))))) + + ;; CHECK: (type $SubObject (sub $Object (descriptor $SubObject.vtable (struct (field $itable (ref $SubObject.itable)))))) + (type $SubObject (sub $Object (descriptor $SubObject.vtable (struct + (field $itable (ref $SubObject.itable)))))) + + ;; CHECK: (type $function (func)) + (type $function (func)) + + ;; CHECK: (type $Object.itable (sub (struct (field structref)))) + (type $Object.itable (sub (struct + (field (ref null struct))))) + + ;; CHECK: (type $SubObject.itable (sub $Object.itable (struct (field structref)))) + (type $SubObject.itable (sub $Object.itable + (struct (field (ref null struct))))) + + ;; The $Object.itable field (a structref) will be added as a field to + ;; the beginning of this vtable. + ;; CHECK: (type $Object.vtable (sub (describes $Object (struct (field structref))))) + (type $Object.vtable (sub (describes $Object (struct)))) + + ;; The $SubObject.itable field (a structref) will be added as a field to + ;; the beginning of this vtable. + ;; CHECK: (type $SubObject.vtable (sub $Object.vtable (describes $SubObject (struct (field structref) (field (ref $function)))))) + (type $SubObject.vtable (sub $Object.vtable (describes $SubObject + (struct (field (ref $function)))))) + ) + + ;; The initialization for the itable field (null struct) will be added to this + ;; vtable instance. + ;; CHECK: (type $7 (func)) + + ;; CHECK: (global $SubObject.vtable (ref (exact $SubObject.vtable)) (struct.new $SubObject.vtable + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.func $SubObject.f) + ;; CHECK-NEXT: )) + (global $SubObject.vtable (ref (exact $SubObject.vtable)) + (struct.new $SubObject.vtable (ref.func $SubObject.f))) + + ;; CHECK: (global $SubObject.itable (ref $SubObject.itable) (struct.new_default $SubObject.itable)) + (global $SubObject.itable (ref $SubObject.itable) + (struct.new_default $SubObject.itable)) + + ;; The initialization for the itable field (null struct) will be added to this + ;; vtable instance. + ;; CHECK: (global $Object.vtable (ref (exact $Object.vtable)) (struct.new $Object.vtable + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: )) + (global $Object.vtable (ref (exact $Object.vtable)) + (struct.new $Object.vtable)) + + ;; CHECK: (global $Object.itable (ref $Object.itable) (struct.new_default $Object.itable)) + (global $Object.itable (ref $Object.itable) + (struct.new_default $Object.itable)) + + ;; CHECK: (func $SubObject.f (type $function) + ;; CHECK-NEXT: ) + (func $SubObject.f + (type $function) + ) + + ;; CHECK: (func $usages (type $7) + ;; CHECK-NEXT: (local $o (ref null $SubObject)) + ;; CHECK-NEXT: (local.set $o + ;; CHECK-NEXT: (struct.new $SubObject + ;; CHECK-NEXT: (global.get $SubObject.itable) + ;; CHECK-NEXT: (global.get $SubObject.vtable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $SubObject.vtable 1 + ;; CHECK-NEXT: (ref.get_desc $SubObject + ;; CHECK-NEXT: (local.get $o) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $SubObject.vtable 0 + ;; CHECK-NEXT: (ref.get_desc $SubObject + ;; CHECK-NEXT: (local.get $o) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $usages + (local $o (ref null $SubObject)) + (local.set $o + (struct.new $SubObject + (global.get $SubObject.itable) + (global.get $SubObject.vtable))) + (drop + ;; The access to vtable field 0 is offset by the itable size and + ;; will be an access to field 1. + (struct.get $SubObject.vtable 0 + (ref.get_desc $SubObject + (local.get $o)))) + (drop + ;; The access to itable field 0 will be rerouted to be an access to + ;; vtable field 0. + (struct.get $Object.itable 0 + (struct.get $SubObject $itable + (local.get $o)))) + ) +)