wasm gc: implement enum constants

This commit is contained in:
Alexey Andreev 2024-09-09 20:44:40 +02:00
parent 8184c46bae
commit 2d8556d0a2
13 changed files with 211 additions and 16 deletions

View File

@ -0,0 +1,26 @@
/*
* Copyright 2024 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.classlib.impl.reflection;
import org.teavm.interop.NoSideEffects;
public final class ClassSupport {
private ClassSupport() {
}
@NoSideEffects
public static native Enum<?>[] getEnumConstants(Class<?> cls);
}

View File

@ -29,6 +29,7 @@ import java.util.Set;
import org.teavm.backend.javascript.spi.GeneratedBy;
import org.teavm.backend.javascript.spi.InjectedBy;
import org.teavm.classlib.PlatformDetector;
import org.teavm.classlib.impl.reflection.ClassSupport;
import org.teavm.classlib.impl.reflection.Flags;
import org.teavm.classlib.impl.reflection.JSClass;
import org.teavm.classlib.impl.reflection.JSField;
@ -692,9 +693,13 @@ public final class TClass<T> extends TObject implements TAnnotatedElement, TType
if (!isEnum()) {
return null;
}
if (PlatformDetector.isWebAssemblyGC()) {
return (T[]) ClassSupport.getEnumConstants((Class<?>) (Object) this);
} else {
Platform.initClass(platformClass);
return (T[]) Platform.getEnumConstants(platformClass).clone();
}
}
@SuppressWarnings("unchecked")
public T cast(TObject obj) {

View File

@ -18,6 +18,8 @@ package org.teavm.classlib.java.util;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import org.teavm.classlib.PlatformDetector;
import org.teavm.classlib.impl.reflection.ClassSupport;
import org.teavm.classlib.java.lang.TClass;
import org.teavm.platform.Platform;
import org.teavm.platform.PlatformClass;
@ -43,10 +45,14 @@ class TGenericEnumSet<E extends Enum<E>> extends TEnumSet<E> {
}
static Enum<?>[] getConstants(Class<?> cls) {
if (PlatformDetector.isWebAssemblyGC()) {
return ClassSupport.getEnumConstants(cls);
} else {
PlatformClass platformClass = ((TClass<?>) (Object) cls).getPlatformClass();
Platform.initClass(platformClass);
return Platform.getEnumConstants(platformClass);
}
}
@Override
public Iterator<E> iterator() {

View File

@ -98,8 +98,8 @@ public class WasmGCDependencies {
analyzer.linkMethod(new MethodReference(WasmGCSupport.class, "aiiobe", ArrayIndexOutOfBoundsException.class))
.use();
analyzer.linkMethod(new MethodReference(WasmGCSupport.class, "cce", ClassCastException.class)).use();
analyzer.linkMethod(new MethodReference(WasmGCSupport.class, "throwCloneNotSupportedException",
void.class)).use();
analyzer.linkMethod(new MethodReference(WasmGCSupport.class, "defaultClone", Object.class,
Object.class)).use();
}
private void contributeInitializerUtils() {

View File

@ -31,8 +31,10 @@ 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.TemporaryVariablePool;
import org.teavm.backend.wasm.generate.gc.WasmGCInitializerContributor;
import org.teavm.backend.wasm.generate.gc.WasmGCNameProvider;
import org.teavm.backend.wasm.generate.gc.methods.WasmGCGenerationUtil;
import org.teavm.backend.wasm.generate.gc.strings.WasmGCStringPool;
import org.teavm.backend.wasm.model.WasmArray;
import org.teavm.backend.wasm.model.WasmField;
@ -48,7 +50,9 @@ import org.teavm.backend.wasm.model.expression.WasmArrayCopy;
import org.teavm.backend.wasm.model.expression.WasmArrayGet;
import org.teavm.backend.wasm.model.expression.WasmArrayLength;
import org.teavm.backend.wasm.model.expression.WasmArrayNewDefault;
import org.teavm.backend.wasm.model.expression.WasmBlock;
import org.teavm.backend.wasm.model.expression.WasmCall;
import org.teavm.backend.wasm.model.expression.WasmCallReference;
import org.teavm.backend.wasm.model.expression.WasmCast;
import org.teavm.backend.wasm.model.expression.WasmExpression;
import org.teavm.backend.wasm.model.expression.WasmFloat32Constant;
@ -72,6 +76,7 @@ import org.teavm.backend.wasm.model.expression.WasmStructNewDefault;
import org.teavm.backend.wasm.model.expression.WasmStructSet;
import org.teavm.backend.wasm.runtime.WasmGCSupport;
import org.teavm.model.ClassHierarchy;
import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier;
import org.teavm.model.FieldReference;
@ -131,6 +136,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
private int classSupertypeFunctionOffset;
private int classEnclosingClassOffset;
private int virtualTableFieldOffset;
private int enumConstantsFunctionOffset;
private int arrayLengthOffset = -1;
private int arrayGetOffset = -1;
private int cloneOffset = -1;
@ -140,6 +146,8 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
private WasmFunctionType arrayGetType;
private WasmFunctionType arrayLengthType;
private List<WasmStructure> nonInitializedStructures = new ArrayList<>();
private WasmArray objectArrayType;
private WasmArray enumConstantArray;
public WasmGCClassGenerator(WasmModule module, ClassReaderSource classSource,
ClassHierarchy hierarchy,
@ -494,11 +502,15 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
cloneFunction = generateCloneFunction(classInfo, name);
} else {
cloneFunction = functionProvider.forStaticMethod(new MethodReference(
WasmGCSupport.class, "throwCloneNotSupportedException", void.class));
WasmGCSupport.class, "defaultClone", Object.class, Object.class));
}
cloneFunction.setReferenced(true);
target.add(setClassField(classInfo, cloneOffset, new WasmFunctionReference(cloneFunction)));
}
if (metadataReq.enumConstants() && cls.hasModifier(ElementModifier.ENUM)) {
target.add(setClassField(classInfo, enumConstantsFunctionOffset,
new WasmFunctionReference(createEnumConstantsFunction(classInfo, cls))));
}
}
if (virtualTable != null && virtualTable.isConcrete()) {
fillVirtualTableMethods(target, classStructure, classInfo.pointer, virtualTable);
@ -881,6 +893,11 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
return arrayGetOffset;
}
@Override
public int getEnumConstantsFunctionOffset() {
return enumConstantsFunctionOffset;
}
private void initArrayClass(WasmGCClassInfo classInfo, ValueType.Array type) {
classInfo.initializer = target -> {
var itemTypeInfo = getClassInfo(type.getItemType());
@ -1047,6 +1064,13 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
cloneOffset = fields.size();
fields.add(createClassField(functionTypes.of(standardClasses.objectClass().getType(),
standardClasses.objectClass().getType()).getReference().asStorage(), "clone"));
fields.add(createClassField(WasmType.INT32.asStorage(), "enumConstantCount"));
if (metadataRequirements.hasEnumConstants()) {
enumConstantsFunctionOffset = fields.size();
var enumArrayType = getClassInfo(ValueType.arrayOf(ValueType.object("java.lang.Enum"))).getType();
var enumConstantsType = functionTypes.of(enumArrayType);
fields.add(createClassField(enumConstantsType.getReference().asStorage(), "getEnumConstants"));
}
virtualTableFieldOffset = fields.size();
}
}
@ -1057,6 +1081,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
private void fillArrayFields(WasmGCClassInfo classInfo, ValueType elementType) {
WasmStorageType wasmElementType;
WasmArray wasmArray;
if (elementType instanceof ValueType.Primitive) {
switch (((ValueType.Primitive) elementType).getKind()) {
case BOOLEAN:
@ -1082,12 +1107,21 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
default:
throw new IllegalArgumentException();
}
var wasmArrayName = names.topLevel(names.suggestForType(classInfo.getValueType()) + "$Data");
wasmArray = new WasmArray(wasmArrayName, wasmElementType);
module.types.add(wasmArray);
} else {
wasmElementType = standardClasses.objectClass().getType().asStorage();
}
var wasmArrayName = names.topLevel(names.suggestForType(classInfo.getValueType()) + "$Data");
var wasmArray = new WasmArray(wasmArrayName, wasmElementType);
wasmArray = objectArrayType;
if (wasmArray == null) {
var wasmArrayName = names.topLevel(names.suggestForType(ValueType.arrayOf(
ValueType.object("java.lang.Object"))) + "$Data");
wasmArray = new WasmArray(wasmArrayName, wasmElementType);
module.types.add(wasmArray);
objectArrayType = wasmArray;
}
}
classInfo.structure.getFields().add(new WasmField(wasmArray.getReference().asStorage(),
arrayDataFieldName()));
}
@ -1214,6 +1248,35 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit
return function;
}
private WasmFunction createEnumConstantsFunction(WasmGCClassInfo classInfo, ClassReader cls) {
var enumArrayStruct = getClassInfo(ValueType.parse(Enum[].class)).structure;
var function = new WasmFunction(functionTypes.of(enumArrayStruct.getReference()));
function.setName(names.topLevel(cls.getName() + "@constants"));
module.functions.add(function);
function.setReferenced(true);
var fields = cls.getFields().stream()
.filter(field -> field.hasModifier(ElementModifier.ENUM))
.filter(field -> field.hasModifier(ElementModifier.STATIC))
.map(field -> new WasmGetGlobal(getStaticFieldLocation(field.getReference())))
.collect(Collectors.toList());
if (classInfo.getInitializerPointer() != null) {
function.getBody().add(new WasmCallReference(new WasmGetGlobal(classInfo.getInitializerPointer()),
functionTypes.of(null)));
}
var tempVars = new TemporaryVariablePool(function);
var util = new WasmGCGenerationUtil(this, tempVars);
var local = tempVars.acquire(enumArrayStruct.getReference());
var block = new WasmBlock(false);
block.setType(enumArrayStruct.getReference());
util.allocateArray(ValueType.parse(Enum.class), fields, null, null, block.getBody());
function.getBody().add(new WasmReturn(block));
tempVars.release(local);
return function;
}
private WasmExpression setClassField(WasmGCClassInfo classInfo, int fieldIndex, WasmExpression value) {
return new WasmStructSet(

View File

@ -54,6 +54,8 @@ public interface WasmGCClassInfoProvider {
int getArrayLengthOffset();
int getEnumConstantsFunctionOffset();
int getCloneOffset();
default WasmGCClassInfo getClassInfo(String name) {

View File

@ -16,12 +16,14 @@
package org.teavm.backend.wasm.generate.gc.methods;
import java.util.List;
import java.util.function.Function;
import org.teavm.backend.wasm.generate.TemporaryVariablePool;
import org.teavm.backend.wasm.generate.gc.classes.WasmGCClassInfoProvider;
import org.teavm.backend.wasm.model.WasmArray;
import org.teavm.backend.wasm.model.WasmLocal;
import org.teavm.backend.wasm.model.WasmType;
import org.teavm.backend.wasm.model.expression.WasmArrayNewDefault;
import org.teavm.backend.wasm.model.expression.WasmArrayNewFixed;
import org.teavm.backend.wasm.model.expression.WasmBlock;
import org.teavm.backend.wasm.model.expression.WasmExpression;
import org.teavm.backend.wasm.model.expression.WasmGetGlobal;
@ -43,6 +45,20 @@ public class WasmGCGenerationUtil {
public void allocateArray(ValueType itemType, WasmExpression length, TextLocation location, WasmLocal local,
List<WasmExpression> target) {
allocateArray(itemType, location, local, target, arrayType -> new WasmArrayNewDefault(arrayType, length));
}
public void allocateArray(ValueType itemType, List<? extends WasmExpression> data, TextLocation location,
WasmLocal local, List<WasmExpression> target) {
allocateArray(itemType, location, local, target, arrayType -> {
var expr = new WasmArrayNewFixed(arrayType);
expr.getElements().addAll(data);
return expr;
});
}
public void allocateArray(ValueType itemType, TextLocation location,
WasmLocal local, List<WasmExpression> target, Function<WasmArray, WasmExpression> data) {
var classInfo = classInfoProvider.getClassInfo(ValueType.arrayOf(itemType));
var block = new WasmBlock(false);
block.setType(classInfo.getType());
@ -68,7 +84,7 @@ public class WasmGCGenerationUtil {
classInfo.getStructure(),
new WasmGetLocal(targetVar),
WasmGCClassInfoProvider.ARRAY_DATA_FIELD_OFFSET,
new WasmArrayNewDefault(wasmArray, length)
data.apply(wasmArray)
);
initArrayField.setLocation(location);
target.add(initArrayField);

View File

@ -43,8 +43,7 @@ public class ClassIntrinsic implements WasmGCIntrinsic {
case "getSuperclass": {
var cls = context.generate(invocation.getArguments().get(0));
var clsStruct = context.classInfoProvider().getClassInfo("java.lang.Class").getStructure();
var result = new WasmStructGet(clsStruct, cls,
context.classInfoProvider().getClassParentOffset());
var result = new WasmStructGet(clsStruct, cls, context.classInfoProvider().getClassParentOffset());
result.setLocation(invocation.getLocation());
return result;
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2024 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.backend.wasm.intrinsics.gc;
import org.teavm.ast.InvocationExpr;
import org.teavm.backend.wasm.model.expression.WasmCallReference;
import org.teavm.backend.wasm.model.expression.WasmExpression;
import org.teavm.backend.wasm.model.expression.WasmStructGet;
import org.teavm.model.ValueType;
public class ClassSupportIntrinsic implements WasmGCIntrinsic {
@Override
public WasmExpression apply(InvocationExpr invocation, WasmGCIntrinsicContext context) {
var enumArrayStruct = context.classInfoProvider().getClassInfo(ValueType.arrayOf(
ValueType.object("java.lang.Enum"))).getStructure();
var clsStruct = context.classInfoProvider().getClassInfo("java.lang.Class").getStructure();
var cls = context.generate(invocation.getArguments().get(0));
var fieldIndex = context.classInfoProvider().getEnumConstantsFunctionOffset();
var functionRef = new WasmStructGet(clsStruct, cls, fieldIndex);
return new WasmCallReference(functionRef, context.functionTypes().of(enumArrayStruct.getReference()));
}
}

View File

@ -39,6 +39,7 @@ public class WasmGCIntrinsics implements WasmGCIntrinsicProvider {
fillWasmRuntime();
fillObject();
fillClass();
fillClassSupport();
fillSystem();
fillLongAndInteger();
fillFloat();
@ -89,6 +90,13 @@ public class WasmGCIntrinsics implements WasmGCIntrinsicProvider {
add(new MethodReference(Class.class, "setSimpleNameCache", Class.class, String.class, void.class), intrinsic);
}
private void fillClassSupport() {
var intrinsic = new ClassSupportIntrinsic();
add(new MethodReference("org.teavm.classlib.impl.reflection.ClassSupport",
"getEnumConstants", ValueType.object("java.lang.Class"),
ValueType.arrayOf(ValueType.object("java.lang.Enum"))), intrinsic);
}
private void fillSystem() {
add(new MethodReference(System.class, "arraycopy", Object.class, int.class, Object.class,
int.class, int.class, void.class), new SystemArrayCopyIntrinsic());

View File

@ -35,7 +35,7 @@ public class WasmGCSupport {
return new ClassCastException();
}
public static void throwCloneNotSupportedException() throws CloneNotSupportedException {
public static Object defaultClone(Object value) throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}

View File

@ -16,6 +16,7 @@
package org.teavm.model.analysis;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.teavm.dependency.DependencyInfo;
@ -45,6 +46,7 @@ public class ClassMetadataRequirements {
private Map<ValueType, ClassInfo> requirements = new HashMap<>();
private boolean hasArrayGet;
private boolean hasArrayLength;
private boolean hasEnumConstants;
public ClassMetadataRequirements(DependencyInfo dependencyInfo) {
MethodDependencyInfo getNameMethod = dependencyInfo.getMethod(GET_NAME_METHOD);
@ -128,6 +130,22 @@ public class ClassMetadataRequirements {
requirements.computeIfAbsent(decodeType(className), k -> new ClassInfo()).cloneMethod = true;
}
}
var enumConstants = Arrays.asList(
dependencyInfo.getMethod(new MethodReference("org.teavm.platform.Platform", "getEnumConstants",
ValueType.object("org.teavm.platform.PlatformClass"), ValueType.parse(Enum[].class))),
dependencyInfo.getMethod(new MethodReference("org.teavm.classlib.impl.reflection.ClassSupport",
"getEnumConstants", ValueType.parse(Class.class), ValueType.parse(Enum[].class)))
);
for (var enumConstantsDep : enumConstants) {
if (enumConstantsDep != null) {
hasEnumConstants = true;
var classNames = enumConstantsDep.getVariable(1).getClassValueNode().getTypes();
for (var className : classNames) {
requirements.computeIfAbsent(decodeType(className), k -> new ClassInfo()).enumConstants = true;
}
}
}
}
public Info getInfo(String className) {
@ -150,6 +168,10 @@ public class ClassMetadataRequirements {
return hasArrayLength;
}
public boolean hasEnumConstants() {
return hasEnumConstants;
}
private void addClassesRequiringName(Map<ValueType, ClassInfo> target, String[] source) {
for (String typeName : source) {
target.computeIfAbsent(decodeType(typeName), k -> new ClassInfo()).name = true;
@ -177,6 +199,7 @@ public class ClassMetadataRequirements {
boolean arrayLength;
boolean arrayGet;
boolean cloneMethod;
boolean enumConstants;
@Override
public boolean name() {
@ -227,6 +250,11 @@ public class ClassMetadataRequirements {
public boolean cloneMethod() {
return cloneMethod;
}
@Override
public boolean enumConstants() {
return enumConstants;
}
}
public interface Info {
@ -249,5 +277,7 @@ public class ClassMetadataRequirements {
boolean arrayGet();
boolean cloneMethod();
boolean enumConstants();
}
}

View File

@ -46,7 +46,7 @@ public class EnumDependencySupport extends AbstractDependencyListener {
@Override
public void methodReached(DependencyAgent agent, MethodDependency method) {
if (method.getReference().getClassName().equals(Platform.class.getName())
if (isEnumSupportClass(method.getMethod().getOwnerName())
&& method.getReference().getName().equals("getEnumConstants")) {
allEnums.connect(method.getResult().getArrayItem());
final MethodReference ref = method.getReference();
@ -66,4 +66,9 @@ public class EnumDependencySupport extends AbstractDependencyListener {
}
}
}
private boolean isEnumSupportClass(String className) {
return className.equals(Platform.class.getName())
|| className.endsWith("org.teavm.classlib.impl.reflection.ClassSupport");
}
}