From 3e19ca341e87bb8717a72b8213d92ac8eab9dd5c Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Fri, 23 Aug 2024 20:46:01 +0200 Subject: [PATCH] wasm gc: fix support of class initialization, inherit arrays from Object, add support for clone in arrays --- .../org/teavm/classlib/java/lang/TObject.java | 3 + .../gc/WasmGCDeclarationsGenerator.java | 2 +- .../gc/classes/WasmGCClassGenerator.java | 211 ++++++++++++++---- .../generate/gc/classes/WasmGCClassInfo.java | 1 + .../generate/gc/classes/WasmGCTypeMapper.java | 8 +- .../backend/wasm/model/WasmStructure.java | 24 +- 6 files changed, 201 insertions(+), 48 deletions(-) diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TObject.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TObject.java index 7fd35a798..0db13182b 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TObject.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TObject.java @@ -343,6 +343,9 @@ public class TObject { @DelegateTo("cloneLowLevel") @PluggableDependency(ObjectDependencyPlugin.class) protected Object clone() throws TCloneNotSupportedException { + if (PlatformDetector.isWebAssemblyGC()) { + throw new TCloneNotSupportedException(); + } if (!(this instanceof TCloneable) && Platform.getPlatformObject(this) .getPlatformClass().getMetadata().getArrayItem() == null) { throw new TCloneNotSupportedException(); diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/WasmGCDeclarationsGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/WasmGCDeclarationsGenerator.java index 4f9e217db..70487cc92 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/WasmGCDeclarationsGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/WasmGCDeclarationsGenerator.java @@ -131,7 +131,7 @@ public class WasmGCDeclarationsGenerator { } 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() { diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassGenerator.java index f63ef2109..466c8474c 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassGenerator.java @@ -29,6 +29,7 @@ import org.teavm.backend.lowlevel.generate.NameProvider; import org.teavm.backend.wasm.BaseWasmFunctionRepository; import org.teavm.backend.wasm.WasmFunctionTypes; 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.generate.gc.WasmGCInitializerContributor; 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.WasmStructure; 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.WasmCast; 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.WasmGetGlobal; import org.teavm.backend.wasm.model.expression.WasmGetLocal; 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.WasmIntBinaryOperation; import org.teavm.backend.wasm.model.expression.WasmIntType; import org.teavm.backend.wasm.model.expression.WasmNullConstant; import org.teavm.backend.wasm.model.expression.WasmReturn; 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.WasmStructSet; import org.teavm.model.ClassReaderSource; @@ -70,6 +79,8 @@ import org.teavm.model.util.ReflectionUtil; public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInitializerContributor { private static final MethodDescriptor CLINIT_METHOD_DESC = new MethodDescriptor("", 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", ValueType.parse(Class.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, 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) { consumer.accept(function); @@ -245,7 +248,9 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit isInterface = true; classInfo.structure = standardClasses.objectClass().structure; } 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); } if (name != null) { @@ -256,17 +261,23 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit classInfo.structure.setSupertype(getClassInfo(classReader.getParent()).structure); } } else { + virtualTable = virtualTables.lookup("java.lang.Object"); classInfo.structure.setSupertype(standardClasses.objectClass().structure); } - if (!isInterface) { - fillFields(classInfo, type); - } } var pointerName = names.forClassInstance(type); classInfo.hasOwnVirtualTable = virtualTable != null && !virtualTable.getEntries().isEmpty(); - var classStructure = classInfo.hasOwnVirtualTable - ? initRegularClassStructure(((ValueType.Object) type).getClassName()) - : standardClasses.classClass().getStructure(); + WasmStructure classStructure; + if (classInfo.hasOwnVirtualTable) { + 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.pointer = new WasmGlobal(pointerName, classStructure.getReference(), new WasmNullConstant(classStructure.getReference())); @@ -397,44 +408,118 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit if (virtualTable != null) { 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 target, WasmStructure structure, WasmGlobal global, WasmGCVirtualTable virtualTable) { for (var entry : virtualTable.getEntries()) { - var implementor = virtualTable.implementor(entry); - if (implementor != null && !entry.getMethod().equals(GET_CLASS_METHOD)) { - var fieldIndex = virtualTableFieldOffset + entry.getIndex(); - var expectedType = (WasmType.CompositeReference) structure.getFields().get(fieldIndex) - .getUnpackedType(); - var expectedFunctionType = (WasmFunctionType) expectedType.composite; - var function = functionProvider.forInstanceMethod(implementor); - if (entry.getOrigin().getClassName().equals(implementor.getClassName()) - || expectedFunctionType != function.getType()) { - var wrapperFunction = new WasmFunction(expectedFunctionType); - module.functions.add(wrapperFunction); - var call = new WasmCall(function); - var instanceParam = new WasmLocal(getClassInfo(virtualTable.getClassName()).getType()); - wrapperFunction.add(instanceParam); - var castTarget = getClassInfo(implementor.getClassName()).getType(); - call.getArguments().add(new WasmCast(new WasmGetLocal(instanceParam), castTarget)); - var params = new WasmLocal[entry.getMethod().parameterCount()]; - for (var i = 0; i < entry.getMethod().parameterCount(); ++i) { - params[i] = new WasmLocal(typeMapper.mapType(entry.getMethod().parameterType(i))); - call.getArguments().add(new WasmGetLocal(params[i])); - wrapperFunction.add(params[i]); - } - wrapperFunction.getBody().add(new WasmReturn(call)); - function = wrapperFunction; - } + fillVirtualTableEntry(target, global, structure, virtualTable, entry); + } + } + + private void fillArrayVirtualTableMethods(List 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 target, WasmGlobal global, + WasmStructure structure, WasmGCVirtualTable virtualTable, WasmGCVirtualTableEntry entry) { + var implementor = virtualTable.implementor(entry); + if (implementor != null && !entry.getMethod().equals(GET_CLASS_METHOD)) { + var fieldIndex = virtualTableFieldOffset + entry.getIndex(); + var expectedType = (WasmType.CompositeReference) structure.getFields().get(fieldIndex) + .getUnpackedType(); + var expectedFunctionType = (WasmFunctionType) expectedType.composite; + var function = functionProvider.forInstanceMethod(implementor); + if (entry.getOrigin().getClassName().equals(implementor.getClassName()) + || expectedFunctionType != function.getType()) { + var wrapperFunction = new WasmFunction(expectedFunctionType); + module.functions.add(wrapperFunction); + var call = new WasmCall(function); + var instanceParam = new WasmLocal(getClassInfo(virtualTable.getClassName()).getType()); + wrapperFunction.add(instanceParam); + var castTarget = getClassInfo(implementor.getClassName()).getType(); + call.getArguments().add(new WasmCast(new WasmGetLocal(instanceParam), castTarget)); + var params = new WasmLocal[entry.getMethod().parameterCount()]; + for (var i = 0; i < entry.getMethod().parameterCount(); ++i) { + params[i] = new WasmLocal(typeMapper.mapType(entry.getMethod().parameterType(i))); + call.getArguments().add(new WasmGetLocal(params[i])); + wrapperFunction.add(params[i]); + } + wrapperFunction.getBody().add(new WasmReturn(call)); + function = wrapperFunction; + } + function.setReferenced(true); + var ref = new WasmFunctionReference(function); + 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) { var virtualTable = virtualTables.lookup(className); var structure = new WasmStructure(names.forClassClass(className)); @@ -472,6 +557,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit new WasmGetGlobal(classInfo.pointer), new WasmGetGlobal(itemTypeInfo.pointer) )); + fillArrayVirtualTableMethods(target, classInfo.pointer, classInfo.structure); }; } @@ -489,7 +575,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit @Override public int getFieldIndex(FieldReference fieldRef) { - getClassInfo(fieldRef.getClassName()); + getClassInfo(fieldRef.getClassName()).structure.init(); return fieldIndexes.getOrDefault(fieldRef, -1); } @@ -514,14 +600,51 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit } 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); return global; } - private void fillFields(WasmGCClassInfo classInfo, ValueType type) { - var fields = classInfo.structure.getFields(); + private WasmExpression initialValue(Object value) { + 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 fields, ValueType type) { addSystemFields(fields); if (type instanceof ValueType.Object) { fillClassFields(fields, ((ValueType.Object) type).getClassName()); diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassInfo.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassInfo.java index eb4ca2ca5..7907d072f 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassInfo.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassInfo.java @@ -47,6 +47,7 @@ public class WasmGCClassInfo { } public WasmArray getArray() { + structure.init(); return array; } diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCTypeMapper.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCTypeMapper.java index 8ffe1b50f..8f56403fa 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCTypeMapper.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCTypeMapper.java @@ -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 javaParamTypes = methodDesc.getParameterTypes(); var paramTypes = new WasmType[javaParamTypes.length + 1]; - paramTypes[0] = classInfoProvider.getClassInfo(className).getType(); + paramTypes[0] = receiverType; for (var i = 0; i < javaParamTypes.length; ++i) { paramTypes[i + 1] = mapType(javaParamTypes[i]); } @@ -130,4 +130,8 @@ public class WasmGCTypeMapper { return functionTypes.of(returnType, paramTypes); } } + + public WasmFunctionType getFunctionType(String className, MethodDescriptor methodDesc, boolean fresh) { + return getFunctionType(classInfoProvider.getClassInfo(className).getType(), methodDesc, fresh); + } } diff --git a/core/src/main/java/org/teavm/backend/wasm/model/WasmStructure.java b/core/src/main/java/org/teavm/backend/wasm/model/WasmStructure.java index 862542cd6..8480c4f84 100644 --- a/core/src/main/java/org/teavm/backend/wasm/model/WasmStructure.java +++ b/core/src/main/java/org/teavm/backend/wasm/model/WasmStructure.java @@ -18,8 +18,10 @@ package org.teavm.backend.wasm.model; import java.util.AbstractList; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; public class WasmStructure extends WasmCompositeType { + private Consumer> fieldsSupplier; private List fieldsStorage = new ArrayList<>(); private WasmStructure supertype; private boolean indexesValid = true; @@ -28,6 +30,11 @@ public class WasmStructure extends WasmCompositeType { super(name); } + public WasmStructure(String name, Consumer> fieldsSupplier) { + super(name); + this.fieldsSupplier = fieldsSupplier; + } + public List getFields() { 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 public void acceptVisitor(WasmCompositeTypeVisitor visitor) { visitor.visit(this); } - private List fields = new AbstractList() { + private List fields = new AbstractList<>() { @Override public WasmField get(int index) { + init(); return fieldsStorage.get(index); } @Override public int size() { + init(); return fieldsStorage.size(); } @Override public void add(int index, WasmField element) { + init(); if (element.structure != null) { throw new IllegalArgumentException("This field already belongs to structure"); } @@ -87,6 +105,7 @@ public class WasmStructure extends WasmCompositeType { @Override public WasmField remove(int index) { + init(); var result = fieldsStorage.remove(index); indexesValid = false; result.structure = null; @@ -95,6 +114,7 @@ public class WasmStructure extends WasmCompositeType { @Override protected void removeRange(int fromIndex, int toIndex) { + init(); var sublist = fieldsStorage.subList(fromIndex, toIndex); for (var field : sublist) { field.structure = null; @@ -105,6 +125,7 @@ public class WasmStructure extends WasmCompositeType { @Override public void clear() { + fieldsSupplier = null; for (var field : fieldsStorage) { field.structure = null; } @@ -114,6 +135,7 @@ public class WasmStructure extends WasmCompositeType { @Override public WasmField set(int index, WasmField element) { + init(); if (element.structure != null) { throw new IllegalArgumentException("This field already belongs to structure"); }