Skip to content

Commit fa18bdd

Browse files
authored
Update J2CLItableMerging to consider custom descriptors (#7729)
Update J2CLItableMerging to consider types whose vtables are custom descriptors
1 parent d5e8918 commit fa18bdd

File tree

2 files changed

+422
-65
lines changed

2 files changed

+422
-65
lines changed

src/passes/J2CLItableMerging.cpp

Lines changed: 165 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,15 @@
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

5552
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+
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

Comments
 (0)