2626// `Foo[vtable] = { i1, i2, ...., m1, m2, m3, ... }`, and fixes all accesses
2727// and initializations accordingly.
2828
29+ #include < memory>
30+ #include < string_view>
2931#include < unordered_map>
30- #include < unordered_set>
3132
32- #include " ir/effects.h"
33- #include " ir/localize.h"
34- #include " ir/ordering.h"
35- #include " ir/struct-utils.h"
36- #include " ir/subtypes.h"
3733#include " ir/type-updating.h"
38- #include " ir/utils.h"
3934#include " pass.h"
35+ #include " support/utilities.h"
4036#include " wasm-builder.h"
37+ #include " wasm-traversal.h"
4138#include " wasm-type.h"
4239#include " wasm.h"
4340
@@ -53,6 +50,11 @@ struct StructInfo {
5350};
5451
5552struct J2CLItableMerging : public Pass {
53+ // Number of entries at the start of the descriptor that should not change
54+ // index. If the vtable is a custom descriptor, itable fields are inserted at
55+ // index 1. Index 0 is preserved for a possible JS prototype.
56+ static const Index kPreservedDescriptorFields = 1 ;
57+
5658 // Keep track of all the structInfos so that they will be automatically
5759 // released after the pass is done.
5860 std::list<StructInfo> structInfos;
@@ -97,25 +99,48 @@ struct J2CLItableMerging : public Pass {
9799 // Collects all structs corresponding to Java classes, their vtables and
98100 // their itables. This is very tied to the way j2cl emits these constructs.
99101 void collectVtableAndItableTypes (Module& wasm) {
102+ auto hasField =
103+ [](TypeNames& typeNameInfo, int index, std::string_view name) {
104+ auto it = typeNameInfo.fieldNames .find (index);
105+ return it != typeNameInfo.fieldNames .end () && it->second .equals (name);
106+ };
107+
100108 // 1. Collect all structs that correspond that a Java type.
101109 for (auto [heapType, typeNameInfo] : wasm.typeNames ) {
102-
103110 if (!heapType.isStruct ()) {
104111 continue ;
105112 }
106113
107- auto type = heapType.getStruct ();
108- if (typeNameInfo.fieldNames .empty () ||
109- !typeNameInfo.fieldNames [0 ].equals (" vtable" )) {
110- continue ;
111- }
112- if (typeNameInfo.fieldNames .size () < 1 ||
113- !typeNameInfo.fieldNames [1 ].equals (" itable" )) {
114- continue ;
115- }
114+ // The vtable may either be the first field or the custom descriptor.
115+ HeapType vtabletype;
116+ HeapType itabletype;
117+ auto & type = heapType.getStruct ();
118+ if (auto descriptor = heapType.getDescriptorType ()) {
119+ if (!hasField (typeNameInfo, 0 , " itable" )) {
120+ continue ;
121+ }
122+
123+ vtabletype = *descriptor;
124+ // If the vtable is a descriptor, we enforce that it has at least 1
125+ // field for the possible JS prototype and simply assume this
126+ // downstream. In practice, this is necessary anyway to allow vtables to
127+ // subtype each other.
128+ if (vtabletype.getStruct ().fields .size () < kPreservedDescriptorFields ) {
129+ Fatal () << " --merge-j2cl-itables needs to be the first pass to run "
130+ << " on j2cl output. (descriptor has fewer than expected "
131+ << " fields)" ;
132+ }
116133
117- auto vtabletype = type.fields [0 ].type .getHeapType ();
118- auto itabletype = type.fields [1 ].type .getHeapType ();
134+ itabletype = type.fields [0 ].type .getHeapType ();
135+ } else {
136+ if (!hasField (typeNameInfo, 0 , " vtable" ) ||
137+ !hasField (typeNameInfo, 1 , " itable" )) {
138+ continue ;
139+ }
140+
141+ vtabletype = type.fields [0 ].type .getHeapType ();
142+ itabletype = type.fields [1 ].type .getHeapType ();
143+ }
119144
120145 auto structItableSize = itabletype.getStruct ().fields .size ();
121146
@@ -170,33 +195,32 @@ struct J2CLItableMerging : public Pass {
170195 }
171196
172197 void visitStructGet (StructGet* curr) {
173- if (curr->ref ->type == Type::unreachable) {
198+ auto * structInfo = getStructInfoByVtableType (curr->ref ->type );
199+ if (!structInfo) {
174200 return ;
175201 }
176202
177- if (!parent.structInfoByVtableType .count (
178- curr->ref ->type .getHeapType ())) {
179- return ;
180- }
181203 // This is a struct.get on the vtable.
182204 // It is ok to just change the index since the field has moved but
183205 // the type is the same.
184- curr->index += parent.itableSize ;
206+ if (structInfo->javaClass .getDescriptorType ()) {
207+ if (curr->index >= kPreservedDescriptorFields ) {
208+ curr->index += parent.itableSize ;
209+ }
210+ } else {
211+ curr->index += parent.itableSize ;
212+ }
185213 }
186214
187215 void visitStructNew (StructNew* curr) {
188- if (curr->type == Type::unreachable) {
216+ auto * structInfo = getStructInfoByVtableType (curr->type );
217+ if (!structInfo) {
189218 return ;
190219 }
191220
192- auto it = parent.structInfoByVtableType .find (curr->type .getHeapType ());
193- if (it == parent.structInfoByVtableType .end ()) {
194- return ;
195- }
196221 // The struct.new is for a vtable type and structInfo has the
197222 // information relating the struct types for the Java class, its vtable
198223 // and its itable.
199- auto structInfo = it->second ;
200224
201225 // Get the global that holds the corresponding itable instance.
202226 auto * itableGlobal = parent.tableGlobalsByType [structInfo->itable ];
@@ -221,28 +245,44 @@ struct J2CLItableMerging : public Pass {
221245 }
222246 auto & itableFieldInitializers = itableStructNew->operands ;
223247
248+ size_t insertIndex =
249+ structInfo->javaClass .getDescriptorType ().has_value ()
250+ ? kPreservedDescriptorFields
251+ : 0 ;
252+
224253 // Add the initialization for the itable fields.
225254 for (Index i = parent.itableSize ; i > 0 ; i--) {
226255 if (itableFieldInitializers.size () >= i) {
227256 // The itable was initialized with a struct.new, copy the
228257 // initialization values.
229258 curr->operands .insertAt (
230- 0 ,
259+ insertIndex ,
231260 ExpressionManipulator::copy (itableFieldInitializers[i - 1 ],
232261 *getModule ()));
233262 } else {
234263 // The itable was initialized with struct.new_default. So use
235264 // null values to initialize the itable fields.
236265 Builder builder (*getModule ());
237266 curr->operands .insertAt (
238- 0 ,
267+ insertIndex ,
239268 builder.makeRefNull (itableStructNew->type .getHeapType ()
240269 .getStruct ()
241270 .fields [i - 1 ]
242271 .type .getHeapType ()));
243272 }
244273 }
245274 }
275+
276+ StructInfo* getStructInfoByVtableType (Type type) {
277+ if (type == Type::unreachable) {
278+ return nullptr ;
279+ }
280+ if (auto it = parent.structInfoByVtableType .find (type.getHeapType ());
281+ it != parent.structInfoByVtableType .end ()) {
282+ return it->second ;
283+ }
284+ return nullptr ;
285+ }
246286 };
247287
248288 Reindexer reindexer (*this );
@@ -265,17 +305,60 @@ struct J2CLItableMerging : public Pass {
265305 }
266306
267307 void visitStructGet (StructGet* curr) {
268- if (curr->ref ->type == Type::unreachable) {
308+ // Determine if the struct.get is to get a field from the itable or the
309+ // to get the itable itself.
310+
311+ if (auto * structInfo = getStructInfoByItableType (curr->ref ->type )) {
312+ // This is a struct.get that returns an itable field.
313+ updateGetItableField (curr, structInfo->javaClass );
269314 return ;
270315 }
271316
272- if (!curr->type .isStruct () ||
273- !parent.structInfoByITableType .count (curr->type .getHeapType ())) {
317+ if (auto * structInfo = getStructInfoByItableType (curr->type )) {
318+ // This is a struct.get that returns an itable type.
319+ updateGetItable (curr, structInfo->javaClass );
274320 return ;
275321 }
322+ }
276323
277- // This is a struct.get that returns an itable type;
278- // Change to return the corresponding vtable type.
324+ StructInfo* getStructInfoByItableType (Type type) {
325+ if (type == Type::unreachable || !type.isStruct ()) {
326+ return nullptr ;
327+ }
328+ if (auto it = parent.structInfoByITableType .find (type.getHeapType ());
329+ it != parent.structInfoByITableType .end ()) {
330+ return it->second ;
331+ }
332+ return nullptr ;
333+ }
334+
335+ void updateGetItableField (StructGet* curr, HeapType javaClass) {
336+ if (!javaClass.getDescriptorType ()) {
337+ return ;
338+ }
339+
340+ curr->index += kPreservedDescriptorFields ;
341+ if (auto childGet = curr->ref ->dynCast <StructGet>()) {
342+ // The reference is another struct.get. It is getting the itable for
343+ // the type.
344+ // Replace it with a ref.get_desc for the vtable, which is the
345+ // descriptor.
346+ Builder builder (*getModule ());
347+ curr->ref = builder.makeRefGetDesc (childGet->ref );
348+ return ;
349+ }
350+
351+ // We expect the reference to be another struct.get.
352+ Fatal () << " --merge-j2cl-itables needs to be the first pass to run "
353+ << " on j2cl output. (itable getter not found) " ;
354+ }
355+
356+ void updateGetItable (StructGet* curr, HeapType javaClass) {
357+ if (javaClass.getDescriptorType ()) {
358+ return ;
359+ }
360+
361+ // Change to return the corresponding vtable type (field 0).
279362 Builder builder (*getModule ());
280363 replaceCurrent (builder.makeStructGet (
281364 0 ,
@@ -304,32 +387,51 @@ struct J2CLItableMerging : public Pass {
304387 : GlobalTypeRewriter(wasm), parent(parent) {}
305388
306389 void modifyStruct (HeapType oldStructType, Struct& struct_) override {
307- if (parent.structInfoByVtableType .count (oldStructType)) {
308- auto & newFields = struct_.fields ;
309-
310- auto structInfo = parent.structInfoByVtableType [oldStructType];
311- // Add the itable fields to the beginning of the vtable.
312- auto it = structInfo->itable .getStruct ().fields .rbegin ();
313- while (it != structInfo->itable .getStruct ().fields .rend ()) {
314- newFields.insert (newFields.begin (), *it++);
315- newFields[0 ].type = getTempType (newFields[0 ].type );
316- }
390+ auto structInfoIt = parent.structInfoByVtableType .find (oldStructType);
391+ if (structInfoIt == parent.structInfoByVtableType .end ()) {
392+ return ;
393+ }
394+
395+ auto & newFields = struct_.fields ;
317396
318- // Update field names as well. The Type Rewriter cannot do this for
319- // us, as it does not know which old fields map to which new ones
320- // (it just keeps the names in sequence).
321- auto & nameInfo = wasm.typeNames [oldStructType];
322-
323- // Make a copy of the old ones before clearing them.
324- auto oldFieldNames = nameInfo.fieldNames ;
325-
326- // Clear the old names and write the new ones.
327- nameInfo.fieldNames .clear ();
328- // Only need to preserve the field names for the vtable fields; the
329- // itable fields do not have names (in the original .wat file they
330- // are accessed by index).
331- for (Index i = 0 ; i < oldFieldNames.size (); i++) {
332- nameInfo.fieldNames [i + parent.itableSize ] = oldFieldNames[i];
397+ auto * structInfo = structInfoIt->second ;
398+
399+ Index insertIndex =
400+ structInfo->javaClass .getDescriptorType ().has_value ()
401+ ? kPreservedDescriptorFields
402+ : 0 ;
403+
404+ // Add the itable fields to the beginning of the vtable.
405+ auto & itableFields = structInfo->itable .getStruct ().fields ;
406+ newFields.insert (newFields.begin () + insertIndex,
407+ itableFields.begin (),
408+ itableFields.end ());
409+ for (Index i = 0 ; i < parent.itableSize ; i++) {
410+ newFields[insertIndex + i].type =
411+ getTempType (newFields[insertIndex + i].type );
412+ }
413+
414+ // Update field names as well. The Type Rewriter cannot do this for
415+ // us, as it does not know which old fields map to which new ones
416+ // (it just keeps the names in sequence).
417+ auto & nameInfo = wasm.typeNames [oldStructType];
418+
419+ // Make a copy of the old ones before clearing them.
420+ auto oldFieldNames = nameInfo.fieldNames ;
421+
422+ // Clear the old names and write the new ones.
423+ nameInfo.fieldNames .clear ();
424+ // Only need to preserve the field names for the vtable fields; the
425+ // itable fields do not have names (in the original .wat file they
426+ // are accessed by index).
427+ for (Index i = 0 ; i < insertIndex; i++) {
428+ if (auto name = oldFieldNames[i]) {
429+ nameInfo.fieldNames [i] = name;
430+ }
431+ }
432+ for (Index i = insertIndex; i < oldFieldNames.size (); i++) {
433+ if (auto name = oldFieldNames[i]) {
434+ nameInfo.fieldNames [i + parent.itableSize ] = name;
333435 }
334436 }
335437 }
0 commit comments