26
26
// `Foo[vtable] = { i1, i2, ...., m1, m2, m3, ... }`, and fixes all accesses
27
27
// and initializations accordingly.
28
28
29
+ #include < memory>
30
+ #include < string_view>
29
31
#include < unordered_map>
30
- #include < unordered_set>
31
32
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"
37
33
#include " ir/type-updating.h"
38
- #include " ir/utils.h"
39
34
#include " pass.h"
35
+ #include " support/utilities.h"
40
36
#include " wasm-builder.h"
37
+ #include " wasm-traversal.h"
41
38
#include " wasm-type.h"
42
39
#include " wasm.h"
43
40
@@ -53,6 +50,11 @@ struct StructInfo {
53
50
};
54
51
55
52
struct 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
+
56
58
// Keep track of all the structInfos so that they will be automatically
57
59
// released after the pass is done.
58
60
std::list<StructInfo> structInfos;
@@ -97,25 +99,48 @@ struct J2CLItableMerging : public Pass {
97
99
// Collects all structs corresponding to Java classes, their vtables and
98
100
// their itables. This is very tied to the way j2cl emits these constructs.
99
101
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
+
100
108
// 1. Collect all structs that correspond that a Java type.
101
109
for (auto [heapType, typeNameInfo] : wasm.typeNames ) {
102
-
103
110
if (!heapType.isStruct ()) {
104
111
continue ;
105
112
}
106
113
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
+ }
116
133
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
+ }
119
144
120
145
auto structItableSize = itabletype.getStruct ().fields .size ();
121
146
@@ -170,33 +195,32 @@ struct J2CLItableMerging : public Pass {
170
195
}
171
196
172
197
void visitStructGet (StructGet* curr) {
173
- if (curr->ref ->type == Type::unreachable) {
198
+ auto * structInfo = getStructInfoByVtableType (curr->ref ->type );
199
+ if (!structInfo) {
174
200
return ;
175
201
}
176
202
177
- if (!parent.structInfoByVtableType .count (
178
- curr->ref ->type .getHeapType ())) {
179
- return ;
180
- }
181
203
// This is a struct.get on the vtable.
182
204
// It is ok to just change the index since the field has moved but
183
205
// 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
+ }
185
213
}
186
214
187
215
void visitStructNew (StructNew* curr) {
188
- if (curr->type == Type::unreachable) {
216
+ auto * structInfo = getStructInfoByVtableType (curr->type );
217
+ if (!structInfo) {
189
218
return ;
190
219
}
191
220
192
- auto it = parent.structInfoByVtableType .find (curr->type .getHeapType ());
193
- if (it == parent.structInfoByVtableType .end ()) {
194
- return ;
195
- }
196
221
// The struct.new is for a vtable type and structInfo has the
197
222
// information relating the struct types for the Java class, its vtable
198
223
// and its itable.
199
- auto structInfo = it->second ;
200
224
201
225
// Get the global that holds the corresponding itable instance.
202
226
auto * itableGlobal = parent.tableGlobalsByType [structInfo->itable ];
@@ -221,28 +245,44 @@ struct J2CLItableMerging : public Pass {
221
245
}
222
246
auto & itableFieldInitializers = itableStructNew->operands ;
223
247
248
+ size_t insertIndex =
249
+ structInfo->javaClass .getDescriptorType ().has_value ()
250
+ ? kPreservedDescriptorFields
251
+ : 0 ;
252
+
224
253
// Add the initialization for the itable fields.
225
254
for (Index i = parent.itableSize ; i > 0 ; i--) {
226
255
if (itableFieldInitializers.size () >= i) {
227
256
// The itable was initialized with a struct.new, copy the
228
257
// initialization values.
229
258
curr->operands .insertAt (
230
- 0 ,
259
+ insertIndex ,
231
260
ExpressionManipulator::copy (itableFieldInitializers[i - 1 ],
232
261
*getModule ()));
233
262
} else {
234
263
// The itable was initialized with struct.new_default. So use
235
264
// null values to initialize the itable fields.
236
265
Builder builder (*getModule ());
237
266
curr->operands .insertAt (
238
- 0 ,
267
+ insertIndex ,
239
268
builder.makeRefNull (itableStructNew->type .getHeapType ()
240
269
.getStruct ()
241
270
.fields [i - 1 ]
242
271
.type .getHeapType ()));
243
272
}
244
273
}
245
274
}
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
+ }
246
286
};
247
287
248
288
Reindexer reindexer (*this );
@@ -265,17 +305,60 @@ struct J2CLItableMerging : public Pass {
265
305
}
266
306
267
307
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 );
269
314
return ;
270
315
}
271
316
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 );
274
320
return ;
275
321
}
322
+ }
276
323
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).
279
362
Builder builder (*getModule ());
280
363
replaceCurrent (builder.makeStructGet (
281
364
0 ,
@@ -304,32 +387,51 @@ struct J2CLItableMerging : public Pass {
304
387
: GlobalTypeRewriter(wasm), parent(parent) {}
305
388
306
389
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 ;
317
396
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;
333
435
}
334
436
}
335
437
}
0 commit comments