From ebac13a36379722393be22e4571004c5e7426dc6 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Sun, 25 Aug 2024 21:01:31 +0200 Subject: [PATCH] wasm gc: implement Array.getLength and Array.get --- .../ast/optimization/OptimizingVisitor.java | 1 + .../methods/BaseWasmGenerationVisitor.java | 8 +- .../gc/classes/WasmGCClassGenerator.java | 220 +++++++++++++++++- .../generate/gc/classes/WasmGCClassInfo.java | 6 +- .../gc/classes/WasmGCClassInfoProvider.java | 7 + .../gc/methods/WasmGCGenerationVisitor.java | 25 ++ .../wasm/intrinsics/gc/ArrayIntrinsic.java | 74 ++++++ .../intrinsics/gc/WasmGCIntrinsicContext.java | 3 + .../wasm/intrinsics/gc/WasmGCIntrinsics.java | 8 + .../analysis/ClassMetadataRequirements.java | 48 ++++ .../classlib/java/lang/reflect/ArrayTest.java | 23 +- 11 files changed, 407 insertions(+), 16 deletions(-) create mode 100644 core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/ArrayIntrinsic.java diff --git a/core/src/main/java/org/teavm/ast/optimization/OptimizingVisitor.java b/core/src/main/java/org/teavm/ast/optimization/OptimizingVisitor.java index 954967270..f021065b4 100644 --- a/core/src/main/java/org/teavm/ast/optimization/OptimizingVisitor.java +++ b/core/src/main/java/org/teavm/ast/optimization/OptimizingVisitor.java @@ -1097,6 +1097,7 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor { conditionalExpr.setConsequent(firstAssignment.getRightValue()); conditionalExpr.setAlternative(secondAssignment.getRightValue()); conditionalExpr.setLocation(statement.getCondition().getLocation()); + conditionalExpr.setVariableIndex(firstAssignment.getRightValue().getVariableIndex()); AssignmentStatement assignment = new AssignmentStatement(); assignment.setLocation(conditionalExpr.getLocation()); VariableExpr lhs = new VariableExpr(); diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/common/methods/BaseWasmGenerationVisitor.java b/core/src/main/java/org/teavm/backend/wasm/generate/common/methods/BaseWasmGenerationVisitor.java index 14cac5530..5143170d3 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/common/methods/BaseWasmGenerationVisitor.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/common/methods/BaseWasmGenerationVisitor.java @@ -538,12 +538,16 @@ public abstract class BaseWasmGenerationVisitor implements StatementVisitor, Exp var elseType = typeInference.getResult(); conditional.getElseBlock().setType(elseType); - assert thenType == elseType; - conditional.setType(thenType); + conditional.setType(condBlockType(thenType, elseType, expr)); result = conditional; } + protected WasmType condBlockType(WasmType thenType, WasmType elseType, ConditionalExpr conditional) { + assert thenType == elseType; + return thenType; + } + @Override public void visit(SequentialStatement statement) { for (var part : statement.getSequence()) { 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 48f57d310..f31390d12 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 @@ -44,6 +44,7 @@ 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.WasmArrayGet; import org.teavm.backend.wasm.model.expression.WasmArrayLength; import org.teavm.backend.wasm.model.expression.WasmArrayNewDefault; import org.teavm.backend.wasm.model.expression.WasmCall; @@ -71,6 +72,7 @@ import org.teavm.model.ElementModifier; import org.teavm.model.FieldReference; import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodReference; +import org.teavm.model.PrimitiveType; import org.teavm.model.ValueType; import org.teavm.model.analysis.ClassInitializerInfo; import org.teavm.model.analysis.ClassMetadataRequirements; @@ -85,6 +87,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit ValueType.parse(Class.class)); private static final FieldReference FAKE_CLASS_FIELD = new FieldReference(Object.class.getName(), "class"); private static final FieldReference FAKE_MONITOR_FIELD = new FieldReference(Object.class.getName(), "monitor"); + private static final ValueType OBJECT_TYPE = ValueType.parse(Object.class); private final WasmModule module; private ClassReaderSource classSource; @@ -119,6 +122,13 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit private int classNewArrayOffset; private int classSupertypeFunctionOffset; private int virtualTableFieldOffset; + private int arrayLengthOffset = -1; + private int arrayGetOffset = -1; + private WasmStructure arrayVirtualTableStruct; + private WasmFunction arrayGetObjectFunction; + private WasmFunction arrayLengthObjectFunction; + private WasmFunctionType arrayGetType; + private WasmFunctionType arrayLengthType; public WasmGCClassGenerator(WasmModule module, ClassReaderSource classSource, WasmFunctionTypes functionTypes, TagRegistry tagRegistry, @@ -249,9 +259,17 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit classInfo.structure = standardClasses.objectClass().structure; } else { var finalClassInfo = classInfo; - classInfo.structure = new WasmStructure(name != null ? names.forClass(name) : null, - fields -> fillFields(finalClassInfo, fields, type)); - module.types.add(classInfo.structure); + if (type instanceof ValueType.Array) { + var itemType = ((ValueType.Array) type).getItemType(); + if (!(itemType instanceof ValueType.Primitive) && !itemType.equals(OBJECT_TYPE)) { + classInfo.structure = getClassInfo(ValueType.arrayOf(OBJECT_TYPE)).structure; + } + } + if (classInfo.structure == null) { + classInfo.structure = new WasmStructure(name != null ? names.forClass(name) : null, + fields -> fillFields(finalClassInfo, fields, type)); + module.types.add(classInfo.structure); + } } if (name != null) { if (!isInterface) { @@ -272,7 +290,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit if (type instanceof ValueType.Object) { classStructure = initRegularClassStructure(((ValueType.Object) type).getClassName()); } else { - classStructure = standardClasses.objectClass().getVirtualTableStructure(); + classStructure = getArrayVirtualTableStructure(); } } else { classStructure = standardClasses.classClass().getStructure(); @@ -431,10 +449,10 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit } } - private void fillArrayVirtualTableMethods(List target, WasmGlobal global, + private void fillArrayVirtualTableMethods(ValueType type, List target, WasmGlobal global, WasmStructure objectStructure) { var virtualTable = virtualTables.lookup("java.lang.Object"); - var structure = standardClasses.objectClass().getVirtualTableStructure(); + var structure = getArrayVirtualTableStructure(); for (var entry : virtualTable.getEntries()) { if (entry.getMethod().getName().equals("clone")) { @@ -447,6 +465,157 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit fillVirtualTableEntry(target, global, structure, virtualTable, entry); } } + + var itemType = ((ValueType.Array) type).getItemType(); + var info = metadataRequirements.getInfo(type); + if (info.arrayLength()) { + var lengthFunction = getArrayLengthFunction(objectStructure); + target.add(new WasmStructSet(structure, new WasmGetGlobal(global), arrayLengthOffset, + new WasmFunctionReference(lengthFunction))); + } + if (info.arrayGet()) { + var getFunction = getArrayGetFunction(itemType); + target.add(new WasmStructSet(structure, new WasmGetGlobal(global), arrayGetOffset, + new WasmFunctionReference(getFunction))); + } + } + + + private WasmFunction getArrayLengthFunction(WasmStructure objectStructure) { + var arrayTypeRef = (WasmType.CompositeReference) objectStructure.getFields().get(ARRAY_DATA_FIELD_OFFSET) + .getUnpackedType(); + var arrayType = (WasmArray) arrayTypeRef.composite; + var elementType = arrayType.getElementType().asUnpackedType(); + if (elementType instanceof WasmType.Reference) { + if (arrayLengthObjectFunction == null) { + arrayLengthObjectFunction = getArrayLengthFunction(objectStructure, arrayType); + } + return arrayLengthObjectFunction; + } + return getArrayLengthFunction(objectStructure, arrayType); + } + + private WasmFunction getArrayLengthFunction(WasmStructure objectStructure, WasmArray arrayType) { + var function = new WasmFunction(functionTypes.of(WasmType.INT32, standardClasses.objectClass().getType())); + function.setReferenced(true); + module.functions.add(function); + + var objectLocal = new WasmLocal(standardClasses.objectClass().getType()); + function.add(objectLocal); + + var castObject = new WasmCast(new WasmGetLocal(objectLocal), objectStructure.getReference()); + var arrayField = new WasmStructGet(objectStructure, castObject, ARRAY_DATA_FIELD_OFFSET); + var result = new WasmArrayLength(arrayField); + function.getBody().add(new WasmReturn(result)); + return function; + } + + private WasmFunction getArrayGetFunction(ValueType itemType) { + if (itemType instanceof ValueType.Primitive) { + return generateArrayGetPrimitiveFunction(((ValueType.Primitive) itemType).getKind()); + } + return getArrayGetObjectFunction(); + } + + private WasmFunction getArrayGetObjectFunction() { + if (arrayGetObjectFunction == null) { + arrayGetObjectFunction = new WasmFunction(getArrayGetType()); + module.functions.add(arrayGetObjectFunction); + arrayGetObjectFunction.setReferenced(true); + + var arrayStruct = getClassInfo(ValueType.arrayOf(OBJECT_TYPE)).structure; + var arrayDataTypeRef = (WasmType.CompositeReference) arrayStruct.getFields() + .get(ARRAY_DATA_FIELD_OFFSET).getUnpackedType(); + var arrayDataType = (WasmArray) arrayDataTypeRef.composite; + var objectLocal = new WasmLocal(standardClasses.objectClass().getType()); + var indexLocal = new WasmLocal(WasmType.INT32); + arrayGetObjectFunction.add(objectLocal); + arrayGetObjectFunction.add(indexLocal); + + var array = new WasmCast(new WasmGetLocal(objectLocal), arrayStruct.getReference()); + var arrayData = new WasmStructGet(arrayStruct, array, ARRAY_DATA_FIELD_OFFSET); + var result = new WasmArrayGet(arrayDataType, arrayData, new WasmGetLocal(indexLocal)); + arrayGetObjectFunction.getBody().add(new WasmReturn(result)); + } + return arrayGetObjectFunction; + } + + private WasmFunction generateArrayGetPrimitiveFunction(PrimitiveType type) { + var function = new WasmFunction(getArrayGetType()); + module.functions.add(function); + function.setReferenced(true); + + var arrayStruct = getClassInfo(ValueType.arrayOf(ValueType.primitive(type))).structure; + var arrayDataTypeRef = (WasmType.CompositeReference) arrayStruct.getFields() + .get(ARRAY_DATA_FIELD_OFFSET).getUnpackedType(); + var arrayDataType = (WasmArray) arrayDataTypeRef.composite; + var objectLocal = new WasmLocal(standardClasses.objectClass().getType()); + var indexLocal = new WasmLocal(WasmType.INT32); + function.add(objectLocal); + function.add(indexLocal); + + var array = new WasmCast(new WasmGetLocal(objectLocal), arrayStruct.getReference()); + var arrayData = new WasmStructGet(arrayStruct, array, ARRAY_DATA_FIELD_OFFSET); + var result = new WasmArrayGet(arrayDataType, arrayData, new WasmGetLocal(indexLocal)); + Class primitiveType; + Class wrapperType; + switch (type) { + case BOOLEAN: + primitiveType = boolean.class; + wrapperType = Boolean.class; + break; + case BYTE: + primitiveType = byte.class; + wrapperType = Byte.class; + break; + case SHORT: + primitiveType = short.class; + wrapperType = Short.class; + break; + case CHARACTER: + primitiveType = char.class; + wrapperType = Character.class; + break; + case INTEGER: + primitiveType = int.class; + wrapperType = Integer.class; + break; + case LONG: + primitiveType = long.class; + wrapperType = Long.class; + break; + case FLOAT: + primitiveType = float.class; + wrapperType = Float.class; + break; + case DOUBLE: + primitiveType = double.class; + wrapperType = Double.class; + break; + default: + throw new IllegalArgumentException(); + } + var method = new MethodReference(wrapperType, "valueOf", primitiveType, wrapperType); + var wrapFunction = functionProvider.forStaticMethod(method); + var castResult = new WasmCall(wrapFunction, result); + function.getBody().add(new WasmReturn(castResult)); + + return function; + } + + private WasmFunctionType getArrayGetType() { + if (arrayGetType == null) { + arrayGetType = functionTypes.of(standardClasses.objectClass().getType(), + standardClasses.objectClass().getType(), WasmType.INT32); + } + return arrayGetType; + } + + private WasmFunctionType getArrayLengthType() { + if (arrayLengthType == null) { + arrayLengthType = functionTypes.of(WasmType.INT32, standardClasses.objectClass().getType()); + } + return arrayLengthType; } private void fillVirtualTableEntry(List target, WasmGlobal global, @@ -556,6 +725,40 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit } } + @Override + public WasmStructure getArrayVirtualTableStructure() { + if (arrayVirtualTableStruct == null) { + arrayVirtualTableStruct = new WasmStructure(null); + arrayVirtualTableStruct.setSupertype(standardClasses.objectClass().getVirtualTableStructure()); + module.types.add(arrayVirtualTableStruct); + addSystemFields(arrayVirtualTableStruct.getFields()); + fillSimpleClassFields(arrayVirtualTableStruct.getFields(), "java.lang.Class"); + addVirtualTableFields(arrayVirtualTableStruct, virtualTables.lookup("java.lang.Object")); + + if (metadataRequirements.hasArrayLength()) { + arrayLengthOffset = arrayVirtualTableStruct.getFields().size(); + var arrayLengthType = getArrayLengthType(); + arrayVirtualTableStruct.getFields().add(new WasmField(arrayLengthType.getReference().asStorage())); + } + if (metadataRequirements.hasArrayGet()) { + arrayGetOffset = arrayVirtualTableStruct.getFields().size(); + var arrayGetType = getArrayGetType(); + arrayVirtualTableStruct.getFields().add(new WasmField(arrayGetType.getReference().asStorage())); + } + } + return arrayVirtualTableStruct; + } + + @Override + public int getArrayLengthOffset() { + return arrayLengthOffset; + } + + @Override + public int getArrayGetOffset() { + return arrayGetOffset; + } + private void initArrayClass(WasmGCClassInfo classInfo, ValueType.Array type) { classInfo.initializer = target -> { var itemTypeInfo = getClassInfo(type.getItemType()); @@ -564,7 +767,7 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit new WasmGetGlobal(classInfo.pointer), new WasmGetGlobal(itemTypeInfo.pointer) )); - fillArrayVirtualTableMethods(target, classInfo.pointer, classInfo.structure); + fillArrayVirtualTableMethods(classInfo.getValueType(), target, classInfo.pointer, classInfo.structure); }; } @@ -744,12 +947,11 @@ public class WasmGCClassGenerator implements WasmGCClassInfoProvider, WasmGCInit throw new IllegalArgumentException(); } } else { - wasmElementType = WasmType.Reference.STRUCT.asStorage(); + wasmElementType = standardClasses.objectClass().getType().asStorage(); } var wasmArray = new WasmArray(null, wasmElementType); module.types.add(wasmArray); classInfo.structure.getFields().add(new WasmField(wasmArray.getReference().asStorage(), "data")); - classInfo.array = wasmArray; } private WasmFunction getCreatePrimitiveClassFunction() { 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 7907d072f..cdadc67ab 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 @@ -27,7 +27,6 @@ import org.teavm.model.ValueType; public class WasmGCClassInfo { private ValueType valueType; WasmStructure structure; - WasmArray array; boolean hasOwnVirtualTable; WasmStructure virtualTableStructure; WasmGlobal pointer; @@ -47,8 +46,9 @@ public class WasmGCClassInfo { } public WasmArray getArray() { - structure.init(); - return array; + var field = structure.getFields().get(WasmGCClassInfoProvider.ARRAY_DATA_FIELD_OFFSET); + var type = (WasmType.CompositeReference) field.getUnpackedType(); + return (WasmArray) type.composite; } public WasmStructure getVirtualTableStructure() { diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassInfoProvider.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassInfoProvider.java index 1b76788a4..714df47a2 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassInfoProvider.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/classes/WasmGCClassInfoProvider.java @@ -16,6 +16,7 @@ package org.teavm.backend.wasm.generate.gc.classes; import org.teavm.backend.wasm.model.WasmGlobal; +import org.teavm.backend.wasm.model.WasmStructure; import org.teavm.model.FieldReference; import org.teavm.model.ValueType; @@ -27,6 +28,8 @@ public interface WasmGCClassInfoProvider { WasmGCClassInfo getClassInfo(ValueType type); + WasmStructure getArrayVirtualTableStructure(); + int getFieldIndex(FieldReference fieldRef); WasmGlobal getStaticFieldLocation(FieldReference fieldRef); @@ -41,6 +44,10 @@ public interface WasmGCClassInfoProvider { int getClassNameOffset(); + int getArrayGetOffset(); + + int getArrayLengthOffset(); + default WasmGCClassInfo getClassInfo(String name) { return getClassInfo(ValueType.object(name)); } diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationVisitor.java b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationVisitor.java index 822be043d..1b4019f4c 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationVisitor.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/gc/methods/WasmGCGenerationVisitor.java @@ -18,6 +18,7 @@ package org.teavm.backend.wasm.generate.gc.methods; import java.util.List; import org.teavm.ast.ArrayType; import org.teavm.ast.BinaryExpr; +import org.teavm.ast.ConditionalExpr; import org.teavm.ast.Expr; import org.teavm.ast.InvocationExpr; import org.teavm.ast.InvocationType; @@ -26,6 +27,7 @@ import org.teavm.ast.SubscriptExpr; import org.teavm.backend.wasm.BaseWasmFunctionRepository; import org.teavm.backend.wasm.WasmFunctionTypes; import org.teavm.backend.wasm.gc.PreciseTypeInference; +import org.teavm.backend.wasm.generate.ExpressionCache; import org.teavm.backend.wasm.generate.TemporaryVariablePool; import org.teavm.backend.wasm.generate.common.methods.BaseWasmGenerationVisitor; import org.teavm.backend.wasm.generate.gc.classes.WasmGCClassInfoProvider; @@ -533,6 +535,24 @@ public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor { } } + @Override + protected WasmType condBlockType(WasmType thenType, WasmType elseType, ConditionalExpr conditional) { + if (conditional.getVariableIndex() >= 0) { + var javaType = types.typeOf(conditional.getVariableIndex()); + if (javaType != null) { + return mapType(javaType.valueType); + } + } + if (conditional.getConsequent().getVariableIndex() >= 0 + && conditional.getConsequent().getVariableIndex() == conditional.getAlternative().getVariableIndex()) { + var javaType = types.typeOf(conditional.getConsequent().getVariableIndex()); + if (javaType != null) { + return mapType(javaType.valueType); + } + } + return super.condBlockType(thenType, elseType, conditional); + } + private class SimpleCallSite extends CallSiteIdentifier { @Override public void generateRegister(List consumer, TextLocation location) { @@ -593,5 +613,10 @@ public class WasmGCGenerationVisitor extends BaseWasmGenerationVisitor { public TemporaryVariablePool tempVars() { return tempVars; } + + @Override + public ExpressionCache exprCache() { + return exprCache; + } }; } diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/ArrayIntrinsic.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/ArrayIntrinsic.java new file mode 100644 index 000000000..12f54b429 --- /dev/null +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/ArrayIntrinsic.java @@ -0,0 +1,74 @@ +/* + * 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.generate.gc.classes.WasmGCClassInfoProvider; +import org.teavm.backend.wasm.model.WasmFunctionType; +import org.teavm.backend.wasm.model.WasmType; +import org.teavm.backend.wasm.model.expression.WasmBlock; +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.WasmStructGet; + +public class ArrayIntrinsic implements WasmGCIntrinsic { + @Override + public WasmExpression apply(InvocationExpr invocation, WasmGCIntrinsicContext context) { + switch (invocation.getMethod().getName()) { + case "getLength": + return arrayLength(invocation, context); + case "getImpl": + return arrayGet(invocation, context); + default: + throw new IllegalArgumentException("Unknown method: " + invocation.getMethod()); + } + } + + private WasmExpression arrayLength(InvocationExpr invocation, WasmGCIntrinsicContext context) { + return arrayVirtualCall(invocation, context, context.classInfoProvider().getArrayLengthOffset()); + } + + private WasmExpression arrayGet(InvocationExpr invocation, WasmGCIntrinsicContext context) { + return arrayVirtualCall(invocation, context, context.classInfoProvider().getArrayGetOffset()); + } + + private WasmExpression arrayVirtualCall(InvocationExpr invocation, WasmGCIntrinsicContext context, + int offset) { + var objectStruct = context.classInfoProvider().getClassInfo("java.lang.Object").getStructure(); + var vtStruct = context.classInfoProvider().getArrayVirtualTableStructure(); + var type = (WasmType.CompositeReference) vtStruct.getFields().get(offset).getUnpackedType(); + var functionType = (WasmFunctionType) type.composite; + var block = new WasmBlock(false); + block.setType(functionType.getReturnType()); + + var originalObject = context.generate(invocation.getArguments().get(0)); + var object = context.exprCache().create(originalObject, objectStruct.getReference(), + invocation.getLocation(), block.getBody()); + var classRef = new WasmStructGet(objectStruct, object.expr(), WasmGCClassInfoProvider.CLASS_FIELD_OFFSET); + var vt = new WasmCast(classRef, vtStruct.getReference()); + var function = new WasmStructGet(vtStruct, vt, offset); + var call = new WasmCallReference(function, functionType); + call.getArguments().add(object.expr()); + for (var i = 1; i < invocation.getArguments().size(); ++i) { + call.getArguments().add(context.generate(invocation.getArguments().get(i))); + } + block.getBody().add(call); + + object.release(); + return block; + } +} diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsicContext.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsicContext.java index bc60f2c7f..1cc0d2cc4 100644 --- a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsicContext.java +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsicContext.java @@ -19,6 +19,7 @@ import org.teavm.ast.Expr; import org.teavm.backend.wasm.BaseWasmFunctionRepository; import org.teavm.backend.wasm.WasmFunctionTypes; import org.teavm.backend.wasm.gc.PreciseTypeInference; +import org.teavm.backend.wasm.generate.ExpressionCache; import org.teavm.backend.wasm.generate.TemporaryVariablePool; import org.teavm.backend.wasm.generate.gc.classes.WasmGCClassInfoProvider; import org.teavm.backend.wasm.generate.gc.classes.WasmGCTypeMapper; @@ -44,4 +45,6 @@ public interface WasmGCIntrinsicContext { WasmGCClassInfoProvider classInfoProvider(); TemporaryVariablePool tempVars(); + + ExpressionCache exprCache(); } diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java index fa1b4d1be..5de25c989 100644 --- a/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/gc/WasmGCIntrinsics.java @@ -15,6 +15,7 @@ */ package org.teavm.backend.wasm.intrinsics.gc; +import java.lang.reflect.Array; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -33,6 +34,7 @@ public class WasmGCIntrinsics implements WasmGCIntrinsicProvider { fillClass(); fillSystem(); fillLongAndInteger(); + fillArray(); } private void fillWasmRuntime() { @@ -91,6 +93,12 @@ public class WasmGCIntrinsics implements WasmGCIntrinsicProvider { intrinsic); } + private void fillArray() { + var intrinsic = new ArrayIntrinsic(); + intrinsics.put(new MethodReference(Array.class, "getLength", Object.class, int.class), intrinsic); + intrinsics.put(new MethodReference(Array.class, "getImpl", Object.class, int.class, Object.class), intrinsic); + } + @Override public WasmGCIntrinsic get(MethodReference method) { return intrinsics.get(method); diff --git a/core/src/main/java/org/teavm/model/analysis/ClassMetadataRequirements.java b/core/src/main/java/org/teavm/model/analysis/ClassMetadataRequirements.java index c3917609c..b74a2e136 100644 --- a/core/src/main/java/org/teavm/model/analysis/ClassMetadataRequirements.java +++ b/core/src/main/java/org/teavm/model/analysis/ClassMetadataRequirements.java @@ -37,8 +37,14 @@ public class ClassMetadataRequirements { "getEnclosingClass", Class.class); private static final MethodReference NEW_ARRAY = new MethodReference(Array.class, "newInstance", Class.class, int.class, Object.class); + private static final MethodReference ARRAY_GET = new MethodReference(Array.class, + "get", Object.class, int.class, Object.class); + private static final MethodReference ARRAY_LENGTH = new MethodReference(Array.class, + "getLength", Object.class, int.class); private static final ClassInfo EMPTY_INFO = new ClassInfo(); private Map requirements = new HashMap<>(); + private boolean hasArrayGet; + private boolean hasArrayLength; public ClassMetadataRequirements(DependencyInfo dependencyInfo) { MethodDependencyInfo getNameMethod = dependencyInfo.getMethod(GET_NAME_METHOD); @@ -96,6 +102,24 @@ public class ClassMetadataRequirements { requirements.computeIfAbsent(decodeType(className), k -> new ClassInfo()).newArray = true; } } + + var arrayGet = dependencyInfo.getMethod(ARRAY_GET); + if (arrayGet != null) { + hasArrayGet = arrayGet.isUsed(); + var classNames = arrayGet.getVariable(1).getTypes(); + for (var className : classNames) { + requirements.computeIfAbsent(decodeType(className), k -> new ClassInfo()).arrayGet = true; + } + } + + var arrayLength = dependencyInfo.getMethod(ARRAY_LENGTH); + if (arrayLength != null) { + hasArrayLength = arrayLength.isUsed(); + var classNames = arrayLength.getVariable(1).getTypes(); + for (var className : classNames) { + requirements.computeIfAbsent(decodeType(className), k -> new ClassInfo()).arrayLength = true; + } + } } public Info getInfo(String className) { @@ -110,6 +134,14 @@ public class ClassMetadataRequirements { return result; } + public boolean hasArrayGet() { + return hasArrayGet; + } + + public boolean hasArrayLength() { + return hasArrayLength; + } + private void addClassesRequiringName(Map target, String[] source) { for (String typeName : source) { target.computeIfAbsent(decodeType(typeName), k -> new ClassInfo()).name = true; @@ -134,6 +166,8 @@ public class ClassMetadataRequirements { boolean superclass; boolean isAssignable; boolean newArray; + boolean arrayLength; + boolean arrayGet; @Override public boolean name() { @@ -169,6 +203,16 @@ public class ClassMetadataRequirements { public boolean newArray() { return newArray; } + + @Override + public boolean arrayLength() { + return arrayLength; + } + + @Override + public boolean arrayGet() { + return arrayGet; + } } public interface Info { @@ -185,5 +229,9 @@ public class ClassMetadataRequirements { boolean isAssignable(); boolean newArray(); + + boolean arrayLength(); + + boolean arrayGet(); } } diff --git a/tests/src/test/java/org/teavm/classlib/java/lang/reflect/ArrayTest.java b/tests/src/test/java/org/teavm/classlib/java/lang/reflect/ArrayTest.java index 7270613cb..912d285aa 100644 --- a/tests/src/test/java/org/teavm/classlib/java/lang/reflect/ArrayTest.java +++ b/tests/src/test/java/org/teavm/classlib/java/lang/reflect/ArrayTest.java @@ -18,6 +18,8 @@ package org.teavm.classlib.java.lang.reflect; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.teavm.junit.EachTestCompiledSeparately; @@ -43,7 +45,24 @@ public class ArrayTest { } @Test - @SkipPlatform({TestPlatform.C, TestPlatform.WEBASSEMBLY, TestPlatform.WASI}) + public void getWorks() { + var intArray = new int[] { 23, 42 }; + var stringArray = new String[] { "asd", "qwe" }; + var list = new ArrayList<>(); + copyToList(list, intArray); + copyToList(list, stringArray); + assertEquals(List.of(23, 42, "asd", "qwe"), list); + } + + private void copyToList(List target, Object array) { + var length = Array.getLength(array); + for (var i = 0; i < length; ++i) { + target.add(Array.get(array, i)); + } + } + + @Test + @SkipPlatform({TestPlatform.C, TestPlatform.WEBASSEMBLY, TestPlatform.WASI, TestPlatform.WEBASSEMBLY_GC}) public void setWorks() { Object array = Array.newInstance(String.class, 2); Array.set(array, 0, "foo"); @@ -52,7 +71,7 @@ public class ArrayTest { } @Test - @SkipPlatform({TestPlatform.C, TestPlatform.WEBASSEMBLY, TestPlatform.WASI}) + @SkipPlatform({TestPlatform.C, TestPlatform.WEBASSEMBLY, TestPlatform.WASI, TestPlatform.WEBASSEMBLY_GC}) public void setPrimitiveWorks() { Object array = Array.newInstance(int.class, 2); Array.set(array, 0, 23);