wasm gc: fix support of class initialization, inherit arrays from Object, add support for clone in arrays

This commit is contained in:
Alexey Andreev 2024-08-23 20:46:01 +02:00
parent 335e2da4cf
commit 3e19ca341e
6 changed files with 201 additions and 48 deletions

View File

@ -343,6 +343,9 @@ public class TObject {
@DelegateTo("cloneLowLevel") @DelegateTo("cloneLowLevel")
@PluggableDependency(ObjectDependencyPlugin.class) @PluggableDependency(ObjectDependencyPlugin.class)
protected Object clone() throws TCloneNotSupportedException { protected Object clone() throws TCloneNotSupportedException {
if (PlatformDetector.isWebAssemblyGC()) {
throw new TCloneNotSupportedException();
}
if (!(this instanceof TCloneable) && Platform.getPlatformObject(this) if (!(this instanceof TCloneable) && Platform.getPlatformObject(this)
.getPlatformClass().getMetadata().getArrayItem() == null) { .getPlatformClass().getMetadata().getArrayItem() == null) {
throw new TCloneNotSupportedException(); throw new TCloneNotSupportedException();

View File

@ -131,7 +131,7 @@ public class WasmGCDeclarationsGenerator {
} }
private static WasmGCVirtualTableProvider createVirtualTableProvider(ListableClassHolderSource classes) { private static WasmGCVirtualTableProvider createVirtualTableProvider(ListableClassHolderSource classes) {
return new WasmGCVirtualTableProvider(classes, VirtualTableBuilder.getMethodsUsedOnCallSites(classes, false)); return new WasmGCVirtualTableProvider(classes, VirtualTableBuilder.getMethodsUsedOnCallSites(classes, true));
} }
public WasmFunction dummyInitializer() { public WasmFunction dummyInitializer() {

View File

@ -29,6 +29,7 @@ import org.teavm.backend.lowlevel.generate.NameProvider;
import org.teavm.backend.wasm.BaseWasmFunctionRepository; import org.teavm.backend.wasm.BaseWasmFunctionRepository;
import org.teavm.backend.wasm.WasmFunctionTypes; import org.teavm.backend.wasm.WasmFunctionTypes;
import org.teavm.backend.wasm.gc.vtable.WasmGCVirtualTable; import org.teavm.backend.wasm.gc.vtable.WasmGCVirtualTable;
import org.teavm.backend.wasm.gc.vtable.WasmGCVirtualTableEntry;
import org.teavm.backend.wasm.gc.vtable.WasmGCVirtualTableProvider; import org.teavm.backend.wasm.gc.vtable.WasmGCVirtualTableProvider;
import org.teavm.backend.wasm.generate.gc.WasmGCInitializerContributor; import org.teavm.backend.wasm.generate.gc.WasmGCInitializerContributor;
import org.teavm.backend.wasm.generate.gc.strings.WasmGCStringPool; import org.teavm.backend.wasm.generate.gc.strings.WasmGCStringPool;
@ -42,19 +43,27 @@ import org.teavm.backend.wasm.model.WasmModule;
import org.teavm.backend.wasm.model.WasmStorageType; import org.teavm.backend.wasm.model.WasmStorageType;
import org.teavm.backend.wasm.model.WasmStructure; import org.teavm.backend.wasm.model.WasmStructure;
import org.teavm.backend.wasm.model.WasmType; import org.teavm.backend.wasm.model.WasmType;
import org.teavm.backend.wasm.model.expression.WasmArrayCopy;
import org.teavm.backend.wasm.model.expression.WasmArrayLength;
import org.teavm.backend.wasm.model.expression.WasmArrayNewDefault;
import org.teavm.backend.wasm.model.expression.WasmCall; import org.teavm.backend.wasm.model.expression.WasmCall;
import org.teavm.backend.wasm.model.expression.WasmCast; import org.teavm.backend.wasm.model.expression.WasmCast;
import org.teavm.backend.wasm.model.expression.WasmExpression; import org.teavm.backend.wasm.model.expression.WasmExpression;
import org.teavm.backend.wasm.model.expression.WasmFloat32Constant;
import org.teavm.backend.wasm.model.expression.WasmFloat64Constant;
import org.teavm.backend.wasm.model.expression.WasmFunctionReference; import org.teavm.backend.wasm.model.expression.WasmFunctionReference;
import org.teavm.backend.wasm.model.expression.WasmGetGlobal; import org.teavm.backend.wasm.model.expression.WasmGetGlobal;
import org.teavm.backend.wasm.model.expression.WasmGetLocal; import org.teavm.backend.wasm.model.expression.WasmGetLocal;
import org.teavm.backend.wasm.model.expression.WasmInt32Constant; import org.teavm.backend.wasm.model.expression.WasmInt32Constant;
import org.teavm.backend.wasm.model.expression.WasmInt64Constant;
import org.teavm.backend.wasm.model.expression.WasmIntBinary; import org.teavm.backend.wasm.model.expression.WasmIntBinary;
import org.teavm.backend.wasm.model.expression.WasmIntBinaryOperation; import org.teavm.backend.wasm.model.expression.WasmIntBinaryOperation;
import org.teavm.backend.wasm.model.expression.WasmIntType; import org.teavm.backend.wasm.model.expression.WasmIntType;
import org.teavm.backend.wasm.model.expression.WasmNullConstant; import org.teavm.backend.wasm.model.expression.WasmNullConstant;
import org.teavm.backend.wasm.model.expression.WasmReturn; import org.teavm.backend.wasm.model.expression.WasmReturn;
import org.teavm.backend.wasm.model.expression.WasmSetGlobal; import org.teavm.backend.wasm.model.expression.WasmSetGlobal;
import org.teavm.backend.wasm.model.expression.WasmSetLocal;
import org.teavm.backend.wasm.model.expression.WasmStructGet;
import org.teavm.backend.wasm.model.expression.WasmStructNewDefault; import org.teavm.backend.wasm.model.expression.WasmStructNewDefault;
import org.teavm.backend.wasm.model.expression.WasmStructSet; import org.teavm.backend.wasm.model.expression.WasmStructSet;
import org.teavm.model.ClassReaderSource; import org.teavm.model.ClassReaderSource;
@ -70,6 +79,8 @@ import org.teavm.model.util.ReflectionUtil;
public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInitializerContributor { public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInitializerContributor {
private static final MethodDescriptor CLINIT_METHOD_DESC = new MethodDescriptor("<clinit>", ValueType.VOID); private static final MethodDescriptor CLINIT_METHOD_DESC = new MethodDescriptor("<clinit>", ValueType.VOID);
private static final MethodDescriptor CLONE_METHOD_DESC = new MethodDescriptor("clone",
ValueType.object("java.lang.Object"));
private static final MethodDescriptor GET_CLASS_METHOD = new MethodDescriptor("getClass", private static final MethodDescriptor GET_CLASS_METHOD = new MethodDescriptor("getClass",
ValueType.parse(Class.class)); ValueType.parse(Class.class));
private static final FieldReference FAKE_CLASS_FIELD = new FieldReference(Object.class.getName(), "class"); private static final FieldReference FAKE_CLASS_FIELD = new FieldReference(Object.class.getName(), "class");
@ -213,14 +224,6 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
} }
function.getBody().add(setClassField(classInfo, CLASS_FIELD_OFFSET, function.getBody().add(setClassField(classInfo, CLASS_FIELD_OFFSET,
new WasmGetGlobal(classClass.pointer))); new WasmGetGlobal(classClass.pointer)));
if (classInfo.initializerPointer != null) {
var className = ((ValueType.Object) classInfo.getValueType()).getClassName();
var initFunction = functionProvider.forStaticMethod(new MethodReference(className,
CLINIT_METHOD_DESC));
initFunction.setReferenced(true);
function.getBody().add(new WasmSetGlobal(classInfo.initializerPointer,
new WasmFunctionReference(initFunction)));
}
} }
for (var consumer : staticFieldInitializers) { for (var consumer : staticFieldInitializers) {
consumer.accept(function); consumer.accept(function);
@ -245,7 +248,9 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
isInterface = true; isInterface = true;
classInfo.structure = standardClasses.objectClass().structure; classInfo.structure = standardClasses.objectClass().structure;
} else { } else {
classInfo.structure = new WasmStructure(name != null ? names.forClass(name) : null); var finalClassInfo = classInfo;
classInfo.structure = new WasmStructure(name != null ? names.forClass(name) : null,
fields -> fillFields(finalClassInfo, fields, type));
module.types.add(classInfo.structure); module.types.add(classInfo.structure);
} }
if (name != null) { if (name != null) {
@ -256,17 +261,23 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
classInfo.structure.setSupertype(getClassInfo(classReader.getParent()).structure); classInfo.structure.setSupertype(getClassInfo(classReader.getParent()).structure);
} }
} else { } else {
virtualTable = virtualTables.lookup("java.lang.Object");
classInfo.structure.setSupertype(standardClasses.objectClass().structure); classInfo.structure.setSupertype(standardClasses.objectClass().structure);
} }
if (!isInterface) {
fillFields(classInfo, type);
}
} }
var pointerName = names.forClassInstance(type); var pointerName = names.forClassInstance(type);
classInfo.hasOwnVirtualTable = virtualTable != null && !virtualTable.getEntries().isEmpty(); classInfo.hasOwnVirtualTable = virtualTable != null && !virtualTable.getEntries().isEmpty();
var classStructure = classInfo.hasOwnVirtualTable WasmStructure classStructure;
? initRegularClassStructure(((ValueType.Object) type).getClassName()) if (classInfo.hasOwnVirtualTable) {
: standardClasses.classClass().getStructure(); if (type instanceof ValueType.Object) {
classStructure = initRegularClassStructure(((ValueType.Object) type).getClassName());
} else {
classStructure = standardClasses.objectClass().getVirtualTableStructure();
}
} else {
classStructure = standardClasses.classClass().getStructure();
}
classInfo.virtualTableStructure = classStructure; classInfo.virtualTableStructure = classStructure;
classInfo.pointer = new WasmGlobal(pointerName, classStructure.getReference(), classInfo.pointer = new WasmGlobal(pointerName, classStructure.getReference(),
new WasmNullConstant(classStructure.getReference())); new WasmNullConstant(classStructure.getReference()));
@ -397,12 +408,42 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
if (virtualTable != null) { if (virtualTable != null) {
fillVirtualTableMethods(target, classStructure, classInfo.pointer, virtualTable); fillVirtualTableMethods(target, classStructure, classInfo.pointer, virtualTable);
} }
if (classInfo.initializerPointer != null) {
var initFunction = functionProvider.forStaticMethod(new MethodReference(name,
CLINIT_METHOD_DESC));
initFunction.setReferenced(true);
target.add(new WasmSetGlobal(classInfo.initializerPointer, new WasmFunctionReference(initFunction)));
}
}; };
} }
private void fillVirtualTableMethods(List<WasmExpression> target, WasmStructure structure, WasmGlobal global, private void fillVirtualTableMethods(List<WasmExpression> target, WasmStructure structure, WasmGlobal global,
WasmGCVirtualTable virtualTable) { WasmGCVirtualTable virtualTable) {
for (var entry : virtualTable.getEntries()) { for (var entry : virtualTable.getEntries()) {
fillVirtualTableEntry(target, global, structure, virtualTable, entry);
}
}
private void fillArrayVirtualTableMethods(List<WasmExpression> target, WasmGlobal global,
WasmStructure objectStructure) {
var virtualTable = virtualTables.lookup("java.lang.Object");
var structure = standardClasses.objectClass().getVirtualTableStructure();
for (var entry : virtualTable.getEntries()) {
if (entry.getMethod().getName().equals("clone")) {
var function = generateArrayCloneMethod(objectStructure);
function.setReferenced(true);
var ref = new WasmFunctionReference(function);
var fieldIndex = virtualTableFieldOffset + entry.getIndex();
target.add(new WasmStructSet(structure, new WasmGetGlobal(global), fieldIndex, ref));
} else {
fillVirtualTableEntry(target, global, structure, virtualTable, entry);
}
}
}
private void fillVirtualTableEntry(List<WasmExpression> target, WasmGlobal global,
WasmStructure structure, WasmGCVirtualTable virtualTable, WasmGCVirtualTableEntry entry) {
var implementor = virtualTable.implementor(entry); var implementor = virtualTable.implementor(entry);
if (implementor != null && !entry.getMethod().equals(GET_CLASS_METHOD)) { if (implementor != null && !entry.getMethod().equals(GET_CLASS_METHOD)) {
var fieldIndex = virtualTableFieldOffset + entry.getIndex(); var fieldIndex = virtualTableFieldOffset + entry.getIndex();
@ -433,6 +474,50 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
target.add(new WasmStructSet(structure, new WasmGetGlobal(global), fieldIndex, ref)); target.add(new WasmStructSet(structure, new WasmGetGlobal(global), fieldIndex, ref));
} }
} }
private WasmFunction generateArrayCloneMethod(WasmStructure objectStructure) {
var arrayTypeRef = (WasmType.CompositeReference) objectStructure.getFields().get(
WasmGCClassInfoProvider.ARRAY_DATA_FIELD_OFFSET).getUnpackedType();
var arrayType = (WasmArray) arrayTypeRef.composite;
var type = typeMapper.getFunctionType(standardClasses.objectClass().getType(), CLONE_METHOD_DESC, false);
var function = new WasmFunction(type);
module.functions.add(function);
var instanceLocal = new WasmLocal(standardClasses.objectClass().getType());
var originalLocal = new WasmLocal(objectStructure.getReference());
var resultLocal = new WasmLocal(objectStructure.getReference());
var originalDataLocal = new WasmLocal(arrayType.getReference());
var dataCopyLocal = new WasmLocal(arrayType.getReference());
function.add(instanceLocal);
function.add(originalLocal);
function.add(resultLocal);
function.add(originalDataLocal);
function.add(dataCopyLocal);
function.getBody().add(new WasmSetLocal(originalLocal,
new WasmCast(new WasmGetLocal(instanceLocal), objectStructure.getReference())));
function.getBody().add(new WasmSetLocal(resultLocal, new WasmStructNewDefault(objectStructure)));
var classValue = new WasmStructGet(objectStructure, new WasmGetLocal(originalLocal),
WasmGCClassInfoProvider.CLASS_FIELD_OFFSET);
function.getBody().add(new WasmStructSet(objectStructure, new WasmGetLocal(resultLocal),
WasmGCClassInfoProvider.CLASS_FIELD_OFFSET, classValue));
var originalDataValue = new WasmStructGet(objectStructure, new WasmGetLocal(originalLocal),
WasmGCClassInfoProvider.ARRAY_DATA_FIELD_OFFSET);
function.getBody().add(new WasmSetLocal(originalDataLocal, originalDataValue));
var originalLength = new WasmArrayLength(new WasmGetLocal(originalDataLocal));
function.getBody().add(new WasmSetLocal(dataCopyLocal, new WasmArrayNewDefault(arrayType, originalLength)));
function.getBody().add(new WasmStructSet(objectStructure, new WasmGetLocal(resultLocal),
WasmGCClassInfoProvider.ARRAY_DATA_FIELD_OFFSET, new WasmGetLocal(dataCopyLocal)));
function.getBody().add(new WasmArrayCopy(arrayType, new WasmGetLocal(dataCopyLocal),
new WasmInt32Constant(0), arrayType, new WasmGetLocal(originalDataLocal),
new WasmInt32Constant(0), new WasmArrayLength(new WasmGetLocal(originalDataLocal))));
function.getBody().add(new WasmGetLocal(resultLocal));
return function;
} }
private WasmStructure initRegularClassStructure(String className) { private WasmStructure initRegularClassStructure(String className) {
@ -472,6 +557,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
new WasmGetGlobal(classInfo.pointer), new WasmGetGlobal(classInfo.pointer),
new WasmGetGlobal(itemTypeInfo.pointer) new WasmGetGlobal(itemTypeInfo.pointer)
)); ));
fillArrayVirtualTableMethods(target, classInfo.pointer, classInfo.structure);
}; };
} }
@ -489,7 +575,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
@Override @Override
public int getFieldIndex(FieldReference fieldRef) { public int getFieldIndex(FieldReference fieldRef) {
getClassInfo(fieldRef.getClassName()); getClassInfo(fieldRef.getClassName()).structure.init();
return fieldIndexes.getOrDefault(fieldRef, -1); return fieldIndexes.getOrDefault(fieldRef, -1);
} }
@ -514,14 +600,51 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
} }
var type = typeMapper.mapType(javaType); var type = typeMapper.mapType(javaType);
var global = new WasmGlobal(names.forStaticField(fieldRef), type, WasmExpression.defaultValueOfType(type)); var wasmInitialValue = initValue != null ? initialValue(initValue) : WasmExpression.defaultValueOfType(type);
var global = new WasmGlobal(names.forStaticField(fieldRef), type, wasmInitialValue);
dynamicInitialValue(global, initValue);
module.globals.add(global); module.globals.add(global);
return global; return global;
} }
private void fillFields(WasmGCClassInfo classInfo, ValueType type) { private WasmExpression initialValue(Object value) {
var fields = classInfo.structure.getFields(); if (value instanceof Boolean) {
return new WasmInt32Constant((Boolean) value ? 1 : 0);
} else if (value instanceof Byte) {
return new WasmInt32Constant((Byte) value);
} else if (value instanceof Short) {
return new WasmInt32Constant((Short) value);
} else if (value instanceof Character) {
return new WasmInt32Constant((Character) value);
} else if (value instanceof Integer) {
return new WasmInt32Constant((Integer) value);
} else if (value instanceof Long) {
return new WasmInt64Constant((Long) value);
} else if (value instanceof Float) {
return new WasmFloat32Constant((Float) value);
} else if (value instanceof Double) {
return new WasmFloat64Constant((Double) value);
} else {
return new WasmNullConstant(standardClasses.stringClass().getType());
}
}
private void dynamicInitialValue(WasmGlobal global, Object value) {
if (value instanceof String) {
var constant = strings.getStringConstant((String) value).global;
staticFieldInitializers.add(function -> {
function.getBody().add(new WasmSetGlobal(global, new WasmGetGlobal(constant)));
});
} else if (value instanceof ValueType) {
var constant = getClassInfo((ValueType) value).pointer;
staticFieldInitializers.add(function -> {
function.getBody().add(new WasmSetGlobal(global, new WasmGetGlobal(constant)));
});
}
}
private void fillFields(WasmGCClassInfo classInfo, List<WasmField> fields, ValueType type) {
addSystemFields(fields); addSystemFields(fields);
if (type instanceof ValueType.Object) { if (type instanceof ValueType.Object) {
fillClassFields(fields, ((ValueType.Object) type).getClassName()); fillClassFields(fields, ((ValueType.Object) type).getClassName());

View File

@ -47,6 +47,7 @@ public class WasmGCClassInfo {
} }
public WasmArray getArray() { public WasmArray getArray() {
structure.init();
return array; return array;
} }

View File

@ -114,11 +114,11 @@ public class WasmGCTypeMapper {
} }
} }
public WasmFunctionType getFunctionType(String className, MethodDescriptor methodDesc, boolean fresh) { public WasmFunctionType getFunctionType(WasmType receiverType, MethodDescriptor methodDesc, boolean fresh) {
var returnType = mapType(methodDesc.getResultType()); var returnType = mapType(methodDesc.getResultType());
var javaParamTypes = methodDesc.getParameterTypes(); var javaParamTypes = methodDesc.getParameterTypes();
var paramTypes = new WasmType[javaParamTypes.length + 1]; var paramTypes = new WasmType[javaParamTypes.length + 1];
paramTypes[0] = classInfoProvider.getClassInfo(className).getType(); paramTypes[0] = receiverType;
for (var i = 0; i < javaParamTypes.length; ++i) { for (var i = 0; i < javaParamTypes.length; ++i) {
paramTypes[i + 1] = mapType(javaParamTypes[i]); paramTypes[i + 1] = mapType(javaParamTypes[i]);
} }
@ -130,4 +130,8 @@ public class WasmGCTypeMapper {
return functionTypes.of(returnType, paramTypes); return functionTypes.of(returnType, paramTypes);
} }
} }
public WasmFunctionType getFunctionType(String className, MethodDescriptor methodDesc, boolean fresh) {
return getFunctionType(classInfoProvider.getClassInfo(className).getType(), methodDesc, fresh);
}
} }

View File

@ -18,8 +18,10 @@ package org.teavm.backend.wasm.model;
import java.util.AbstractList; import java.util.AbstractList;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Consumer;
public class WasmStructure extends WasmCompositeType { public class WasmStructure extends WasmCompositeType {
private Consumer<List<WasmField>> fieldsSupplier;
private List<WasmField> fieldsStorage = new ArrayList<>(); private List<WasmField> fieldsStorage = new ArrayList<>();
private WasmStructure supertype; private WasmStructure supertype;
private boolean indexesValid = true; private boolean indexesValid = true;
@ -28,6 +30,11 @@ public class WasmStructure extends WasmCompositeType {
super(name); super(name);
} }
public WasmStructure(String name, Consumer<List<WasmField>> fieldsSupplier) {
super(name);
this.fieldsSupplier = fieldsSupplier;
}
public List<WasmField> getFields() { public List<WasmField> getFields() {
return fields; return fields;
} }
@ -59,24 +66,35 @@ public class WasmStructure extends WasmCompositeType {
} }
} }
public void init() {
if (fieldsSupplier != null) {
var supplier = fieldsSupplier;
fieldsSupplier = null;
supplier.accept(fieldsStorage);
}
}
@Override @Override
public void acceptVisitor(WasmCompositeTypeVisitor visitor) { public void acceptVisitor(WasmCompositeTypeVisitor visitor) {
visitor.visit(this); visitor.visit(this);
} }
private List<WasmField> fields = new AbstractList<WasmField>() { private List<WasmField> fields = new AbstractList<>() {
@Override @Override
public WasmField get(int index) { public WasmField get(int index) {
init();
return fieldsStorage.get(index); return fieldsStorage.get(index);
} }
@Override @Override
public int size() { public int size() {
init();
return fieldsStorage.size(); return fieldsStorage.size();
} }
@Override @Override
public void add(int index, WasmField element) { public void add(int index, WasmField element) {
init();
if (element.structure != null) { if (element.structure != null) {
throw new IllegalArgumentException("This field already belongs to structure"); throw new IllegalArgumentException("This field already belongs to structure");
} }
@ -87,6 +105,7 @@ public class WasmStructure extends WasmCompositeType {
@Override @Override
public WasmField remove(int index) { public WasmField remove(int index) {
init();
var result = fieldsStorage.remove(index); var result = fieldsStorage.remove(index);
indexesValid = false; indexesValid = false;
result.structure = null; result.structure = null;
@ -95,6 +114,7 @@ public class WasmStructure extends WasmCompositeType {
@Override @Override
protected void removeRange(int fromIndex, int toIndex) { protected void removeRange(int fromIndex, int toIndex) {
init();
var sublist = fieldsStorage.subList(fromIndex, toIndex); var sublist = fieldsStorage.subList(fromIndex, toIndex);
for (var field : sublist) { for (var field : sublist) {
field.structure = null; field.structure = null;
@ -105,6 +125,7 @@ public class WasmStructure extends WasmCompositeType {
@Override @Override
public void clear() { public void clear() {
fieldsSupplier = null;
for (var field : fieldsStorage) { for (var field : fieldsStorage) {
field.structure = null; field.structure = null;
} }
@ -114,6 +135,7 @@ public class WasmStructure extends WasmCompositeType {
@Override @Override
public WasmField set(int index, WasmField element) { public WasmField set(int index, WasmField element) {
init();
if (element.structure != null) { if (element.structure != null) {
throw new IllegalArgumentException("This field already belongs to structure"); throw new IllegalArgumentException("This field already belongs to structure");
} }